package pipeline import ( "testing" "time" "smesh.lol/pkg/acl" "smesh.lol/pkg/nostr/event" "smesh.lol/pkg/nostr/kind" "smesh.lol/pkg/nostr/signer/p8k" "smesh.lol/pkg/nostr/tag" "smesh.lol/pkg/relay/ratelimit" "smesh.lol/pkg/store" ) func setup(t *testing.T) (*Pipeline, *p8k.Signer) { t.Helper() eng, err := store.Open(t.TempDir()) if err != nil { t.Fatal(err) } t.Cleanup(func() { eng.Close() }) s := p8k.MustNew() if err := s.Generate(); err != nil { t.Fatal(err) } p := New(eng, acl.Open{}, nil, DefaultConfig()) return p, s } func makeEv(t *testing.T, s *p8k.Signer, k uint16, ts int64, tags *tag.S, content string) *event.E { t.Helper() ev := &event.E{ CreatedAt: ts, Kind: k, Tags: tags, Content: []byte(content), } if err := ev.Sign(s); err != nil { t.Fatal(err) } return ev } func TestIngestRegular(t *testing.T) { p, s := setup(t) ev := makeEv(t, s, 1, time.Now().Unix(), nil, "hello") r := p.Ingest(ev) if !r.OK { t.Fatalf("expected OK, got: %s", r.Reason) } } func TestIngestDuplicate(t *testing.T) { p, s := setup(t) ev := makeEv(t, s, 1, time.Now().Unix(), nil, "hello") r := p.Ingest(ev) if !r.OK { t.Fatalf("first ingest failed: %s", r.Reason) } r = p.Ingest(ev) if r.OK { t.Fatal("expected duplicate rejection") } if string(r.Reason) != "duplicate: already have this event" { t.Fatalf("unexpected reason: %s", r.Reason) } } func TestIngestBadSignature(t *testing.T) { p, s := setup(t) ev := makeEv(t, s, 1, time.Now().Unix(), nil, "hello") // Corrupt signature. ev.Sig[0] ^= 0xFF r := p.Ingest(ev) if r.OK { t.Fatal("expected rejection for bad sig") } } func TestIngestACLBlocked(t *testing.T) { eng, err := store.Open(t.TempDir()) if err != nil { t.Fatal(err) } defer eng.Close() s := p8k.MustNew() if err := s.Generate(); err != nil { t.Fatal(err) } // Whitelist with a different pubkey. other := p8k.MustNew() if err := other.Generate(); err != nil { t.Fatal(err) } wl := &acl.Whitelist{Pubkeys: [][]byte{other.Pub()}} p := New(eng, wl, nil, DefaultConfig()) ev := makeEv(t, s, 1, time.Now().Unix(), nil, "blocked") r := p.Ingest(ev) if r.OK { t.Fatal("expected ACL rejection") } if string(r.Reason) != "blocked: pubkey not allowed" { t.Fatalf("unexpected reason: %s", r.Reason) } } func TestIngestRateLimit(t *testing.T) { eng, err := store.Open(t.TempDir()) if err != nil { t.Fatal(err) } defer eng.Close() s := p8k.MustNew() if err := s.Generate(); err != nil { t.Fatal(err) } // 1 token/sec, burst 2. lim := ratelimit.New(1.0, 2) p := New(eng, acl.Open{}, lim, DefaultConfig()) now := time.Now().Unix() for i := 0; i < 2; i++ { ev := makeEv(t, s, 1, now+int64(i), nil, "msg") r := p.Ingest(ev) if !r.OK { t.Fatalf("event %d should be allowed: %s", i, r.Reason) } } // Third event should be rate-limited. ev := makeEv(t, s, 1, now+2, nil, "msg") r := p.Ingest(ev) if r.OK { t.Fatal("expected rate limit rejection") } } func TestIngestEphemeral(t *testing.T) { p, s := setup(t) // Kind 20001 is ephemeral. ev := makeEv(t, s, 20001, time.Now().Unix(), nil, "ephemeral") r := p.Ingest(ev) if !r.OK { t.Fatalf("ephemeral should be accepted: %s", r.Reason) } } func TestIngestReplaceable(t *testing.T) { p, s := setup(t) now := time.Now().Unix() // Store initial kind-0 (metadata). ev1 := makeEv(t, s, 0, now, nil, `{"name":"old"}`) r := p.Ingest(ev1) if !r.OK { t.Fatalf("first replaceable failed: %s", r.Reason) } // Newer replaces it. ev2 := makeEv(t, s, 0, now+1, nil, `{"name":"new"}`) r = p.Ingest(ev2) if !r.OK { t.Fatalf("newer replaceable failed: %s", r.Reason) } // Older is rejected. ev3 := makeEv(t, s, 0, now-1, nil, `{"name":"ancient"}`) r = p.Ingest(ev3) if r.OK { t.Fatal("older replaceable should be rejected") } } func TestIngestParamReplaceable(t *testing.T) { p, s := setup(t) now := time.Now().Unix() mkTags := func(dval string) *tag.S { return tag.NewS(tag.NewFromBytesSlice([]byte("d"), []byte(dval))) } ev1 := makeEv(t, s, 30023, now, mkTags("article-1"), "v1") r := p.Ingest(ev1) if !r.OK { t.Fatalf("first param replaceable failed: %s", r.Reason) } // Same d-tag, newer — replaces. ev2 := makeEv(t, s, 30023, now+1, mkTags("article-1"), "v2") r = p.Ingest(ev2) if !r.OK { t.Fatalf("newer param replaceable failed: %s", r.Reason) } // Same d-tag, older — rejected. ev3 := makeEv(t, s, 30023, now-1, mkTags("article-1"), "v0") r = p.Ingest(ev3) if r.OK { t.Fatal("older param replaceable should be rejected") } // Different d-tag — accepted (independent). ev4 := makeEv(t, s, 30023, now, mkTags("article-2"), "other") r = p.Ingest(ev4) if !r.OK { t.Fatalf("different d-tag should be accepted: %s", r.Reason) } } func TestIngestExpired(t *testing.T) { p, s := setup(t) past := time.Now().Unix() - 60 expTags := tag.NewS(tag.NewFromBytesSlice( []byte("expiration"), []byte("1000000000"), )) ev := makeEv(t, s, 1, past, expTags, "expired") r := p.Ingest(ev) if r.OK { t.Fatal("expired event should be rejected") } } func TestIngestDeletion(t *testing.T) { p, s := setup(t) now := time.Now().Unix() // Store a target event. target := makeEv(t, s, 1, now, nil, "to be deleted") r := p.Ingest(target) if !r.OK { t.Fatalf("target ingest failed: %s", r.Reason) } // Create deletion event referencing the target. eTag := tag.NewFromBytesSlice([]byte("e"), target.ID) delTags := tag.NewS(eTag) del := makeEv(t, s, kind.EventDeletion.K, now+1, delTags, "") r = p.Ingest(del) if !r.OK { t.Fatalf("deletion ingest failed: %s", r.Reason) } } func TestIngestFutureTimestamp(t *testing.T) { p, s := setup(t) future := time.Now().Unix() + 3600 // 1 hour ahead ev := makeEv(t, s, 1, future, nil, "future") r := p.Ingest(ev) if r.OK { t.Fatal("event too far in future should be rejected") } } func TestIngestIDMismatch(t *testing.T) { p, s := setup(t) ev := makeEv(t, s, 1, time.Now().Unix(), nil, "hello") // Corrupt the ID without corrupting the sig. ev.ID[0] ^= 0xFF r := p.Ingest(ev) if r.OK { t.Fatal("event with mismatched ID should be rejected") } }