package khatru import ( "context" "math" "math/rand/v2" "net/http/httptest" "testing" "time" "fiatjaf.com/nostr" "fiatjaf.com/nostr/eventstore/lmdb" "github.com/stretchr/testify/require" ) func FuzzReplaceableEvents(f *testing.F) { f.Add(uint(1), uint(2)) f.Fuzz(func(t *testing.T, seed uint, nevents uint) { if nevents == 0 { return } relay := NewRelay() store := &lmdb.LMDBBackend{Path: "/tmp/fuzz"} store.Init() relay.UseEventstore(store) defer store.Close() // start test server server := httptest.NewServer(relay) defer server.Close() // create test keys sk1 := nostr.Generate() pk1 := nostr.GetPublicKey(sk1) // helper to create signed events createEvent := func(sk nostr.SecretKey, kind nostr.Kind, content string, tags nostr.Tags) nostr.Event { pk := nostr.GetPublicKey(sk) evt := nostr.Event{ PubKey: pk, CreatedAt: nostr.Now(), Kind: kind, Tags: tags, Content: content, } evt.Sign(sk) return evt } url := "ws" + server.URL[4:] client1, err := nostr.RelayConnect(context.Background(), url, nostr.RelayOptions{}) if err != nil { t.Skip("failed to connect client1") } defer client1.Close() client2, err := nostr.RelayConnect(context.Background(), url, nostr.RelayOptions{}) if err != nil { t.Skip("failed to connect client2") } defer client2.Close() t.Run("replaceable events", func(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() rnd := rand.New(rand.NewPCG(uint64(seed), 0)) newest := nostr.Timestamp(0) for range nevents { evt := createEvent(sk1, 0, `{"name":"blblbl"}`, nil) evt.CreatedAt = nostr.Timestamp(rnd.Int64() % math.MaxUint32) evt.Sign(sk1) err = client1.Publish(ctx, evt) if err != nil { t.Fatalf("failed to publish event: %v", err) } if evt.CreatedAt > newest { newest = evt.CreatedAt } } // query to verify only the newest event exists sub, err := client2.Subscribe(ctx, nostr.Filter{ Authors: []nostr.PubKey{pk1}, Kinds: []nostr.Kind{0}, }, nostr.SubscriptionOptions{}) if err != nil { t.Fatalf("failed to subscribe: %v", err) } defer sub.Unsub() // should only get one event back (the newest one) var receivedEvents []nostr.Event for { select { case evt := <-sub.Events: receivedEvents = append(receivedEvents, evt) case <-sub.EndOfStoredEvents: require.Len(t, receivedEvents, 1) require.Equal(t, newest, receivedEvents[0].CreatedAt) return case <-ctx.Done(): t.Fatal("timeout waiting for events") } } }) }) }