From 62d31434ddbadff18580826576e1169f539e23f0 Mon Sep 17 00:00:00 2001 From: bndw Date: Fri, 13 Feb 2026 17:48:36 -0800 Subject: feat: add gRPC handler with event validation and publishing Handler implementation: - EventStore interface (consumer-side) - Server with PublishEvent, QueryEvents, CountEvents, PublishBatch - pb.Event <-> nostr.Event conversion helpers - Signature and ID validation using existing nostr package - Canonical JSON generation for storage 9 tests passing --- internal/handler/grpc/server_test.go | 281 +++++++++++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 internal/handler/grpc/server_test.go (limited to 'internal/handler/grpc/server_test.go') diff --git a/internal/handler/grpc/server_test.go b/internal/handler/grpc/server_test.go new file mode 100644 index 0000000..12dde92 --- /dev/null +++ b/internal/handler/grpc/server_test.go @@ -0,0 +1,281 @@ +package grpc + +import ( + "context" + "fmt" + "testing" + "time" + + pb "northwest.io/nostr-grpc/api/nostr/v1" + "northwest.io/nostr-grpc/internal/nostr" + "northwest.io/nostr-grpc/internal/storage" +) + +func TestPublishEvent(t *testing.T) { + store, err := storage.New(":memory:") + if err != nil { + t.Fatalf("failed to create storage: %v", err) + } + defer store.Close() + + server := NewServer(store) + ctx := context.Background() + + key, err := nostr.GenerateKey() + if err != nil { + t.Fatalf("failed to generate key: %v", err) + } + + nostrEvent := &nostr.Event{ + PubKey: key.Public(), + CreatedAt: time.Now().Unix(), + Kind: 1, + Tags: nostr.Tags{}, + Content: "test event", + } + + if err := key.Sign(nostrEvent); err != nil { + t.Fatalf("failed to sign event: %v", err) + } + + pbEvent := NostrToPB(nostrEvent) + + resp, err := server.PublishEvent(ctx, &pb.PublishEventRequest{Event: pbEvent}) + if err != nil { + t.Fatalf("PublishEvent failed: %v", err) + } + + if !resp.Accepted { + t.Errorf("expected event to be accepted, got: %s", resp.Message) + } + if resp.Message != "success" { + t.Errorf("expected success message, got: %s", resp.Message) + } + if len(resp.CanonicalJson) == 0 { + t.Error("expected canonical JSON to be returned") + } +} + +func TestPublishEventDuplicate(t *testing.T) { + store, err := storage.New(":memory:") + if err != nil { + t.Fatalf("failed to create storage: %v", err) + } + defer store.Close() + + server := NewServer(store) + ctx := context.Background() + + key, err := nostr.GenerateKey() + if err != nil { + t.Fatalf("failed to generate key: %v", err) + } + + nostrEvent := &nostr.Event{ + PubKey: key.Public(), + CreatedAt: time.Now().Unix(), + Kind: 1, + Tags: nostr.Tags{}, + Content: "duplicate test", + } + + if err := key.Sign(nostrEvent); err != nil { + t.Fatalf("failed to sign event: %v", err) + } + + pbEvent := NostrToPB(nostrEvent) + + resp1, err := server.PublishEvent(ctx, &pb.PublishEventRequest{Event: pbEvent}) + if err != nil { + t.Fatalf("first PublishEvent failed: %v", err) + } + if !resp1.Accepted { + t.Fatalf("first event should be accepted") + } + + resp2, err := server.PublishEvent(ctx, &pb.PublishEventRequest{Event: pbEvent}) + if err != nil { + t.Fatalf("second PublishEvent failed: %v", err) + } + if resp2.Accepted { + t.Error("duplicate event should not be accepted") + } + if resp2.Message != "duplicate: event already exists" { + t.Errorf("expected duplicate message, got: %s", resp2.Message) + } +} + +func TestPublishEventInvalidSignature(t *testing.T) { + store, err := storage.New(":memory:") + if err != nil { + t.Fatalf("failed to create storage: %v", err) + } + defer store.Close() + + server := NewServer(store) + ctx := context.Background() + + key, err := nostr.GenerateKey() + if err != nil { + t.Fatalf("failed to generate key: %v", err) + } + + nostrEvent := &nostr.Event{ + PubKey: key.Public(), + CreatedAt: time.Now().Unix(), + Kind: 1, + Tags: nostr.Tags{}, + Content: "test event", + } + + if err := key.Sign(nostrEvent); err != nil { + t.Fatalf("failed to sign event: %v", err) + } + + pbEvent := NostrToPB(nostrEvent) + pbEvent.Sig = "invalid_signature" + + resp, err := server.PublishEvent(ctx, &pb.PublishEventRequest{Event: pbEvent}) + if err != nil { + t.Fatalf("PublishEvent failed: %v", err) + } + + if resp.Accepted { + t.Error("event with invalid signature should not be accepted") + } + if resp.Message != "invalid signature" { + t.Errorf("expected invalid signature message, got: %s", resp.Message) + } +} + +func TestPublishEventInvalidID(t *testing.T) { + store, err := storage.New(":memory:") + if err != nil { + t.Fatalf("failed to create storage: %v", err) + } + defer store.Close() + + server := NewServer(store) + ctx := context.Background() + + pbEvent := &pb.Event{ + Id: "wrong_id", + Pubkey: "pubkey123", + CreatedAt: time.Now().Unix(), + Kind: 1, + Tags: []*pb.Tag{}, + Content: "test", + Sig: "sig123", + } + + resp, err := server.PublishEvent(ctx, &pb.PublishEventRequest{Event: pbEvent}) + if err != nil { + t.Fatalf("PublishEvent failed: %v", err) + } + + if resp.Accepted { + t.Error("event with invalid ID should not be accepted") + } + if resp.Message != "invalid event ID" { + t.Errorf("expected invalid ID message, got: %s", resp.Message) + } +} + +func TestQueryEvents(t *testing.T) { + store, err := storage.New(":memory:") + if err != nil { + t.Fatalf("failed to create storage: %v", err) + } + defer store.Close() + + server := NewServer(store) + ctx := context.Background() + + key, err := nostr.GenerateKey() + if err != nil { + t.Fatalf("failed to generate key: %v", err) + } + + for i := 0; i < 3; i++ { + nostrEvent := &nostr.Event{ + PubKey: key.Public(), + CreatedAt: time.Now().Unix(), + Kind: 1, + Tags: nostr.Tags{}, + Content: fmt.Sprintf("test event %d", i), + } + + if err := key.Sign(nostrEvent); err != nil { + t.Fatalf("failed to sign event: %v", err) + } + + pbEvent := NostrToPB(nostrEvent) + if _, err := server.PublishEvent(ctx, &pb.PublishEventRequest{Event: pbEvent}); err != nil { + t.Fatalf("failed to publish event: %v", err) + } + + time.Sleep(time.Millisecond) + } + + resp, err := server.QueryEvents(ctx, &pb.QueryRequest{ + Filters: []*pb.Filter{ + {Authors: []string{key.Public()}}, + }, + }) + if err != nil { + t.Fatalf("QueryEvents failed: %v", err) + } + + if len(resp.Events) != 3 { + t.Errorf("expected 3 events, got %d", len(resp.Events)) + } +} + +func TestPublishBatch(t *testing.T) { + store, err := storage.New(":memory:") + if err != nil { + t.Fatalf("failed to create storage: %v", err) + } + defer store.Close() + + server := NewServer(store) + ctx := context.Background() + + key, err := nostr.GenerateKey() + if err != nil { + t.Fatalf("failed to generate key: %v", err) + } + + var events []*pb.Event + for i := 0; i < 3; i++ { + nostrEvent := &nostr.Event{ + PubKey: key.Public(), + CreatedAt: time.Now().Unix(), + Kind: 1, + Tags: nostr.Tags{}, + Content: fmt.Sprintf("batch test %d", i), + } + + if err := key.Sign(nostrEvent); err != nil { + t.Fatalf("failed to sign event: %v", err) + } + + events = append(events, NostrToPB(nostrEvent)) + time.Sleep(time.Millisecond) + } + + resp, err := server.PublishBatch(ctx, &pb.PublishBatchRequest{Events: events}) + if err != nil { + t.Fatalf("PublishBatch failed: %v", err) + } + + if len(resp.Results) != 3 { + t.Fatalf("expected 3 results, got %d", len(resp.Results)) + } + + for i, result := range resp.Results { + if !result.Accepted { + t.Errorf("event %d should be accepted, got: %s", i, result.Message) + } + } +} -- cgit v1.2.3