query-events-multiple-param-replaceable_test.go raw

   1  package database
   2  
   3  import (
   4  	"fmt"
   5  	"testing"
   6  
   7  	"next.orly.dev/pkg/nostr/encoders/event"
   8  	"next.orly.dev/pkg/nostr/encoders/filter"
   9  	"next.orly.dev/pkg/nostr/encoders/hex"
  10  	"next.orly.dev/pkg/nostr/encoders/kind"
  11  	"next.orly.dev/pkg/nostr/encoders/tag"
  12  	"next.orly.dev/pkg/nostr/encoders/timestamp"
  13  	"next.orly.dev/pkg/nostr/interfaces/signer/p8k"
  14  	"next.orly.dev/pkg/lol/chk"
  15  	"next.orly.dev/pkg/utils"
  16  )
  17  
  18  // TestMultipleParameterizedReplaceableEvents tests that when multiple parameterized
  19  // replaceable events with the same pubkey, kind, and d-tag exist, only the newest one
  20  // is returned in query results.
  21  func TestMultipleParameterizedReplaceableEvents(t *testing.T) {
  22  	// Needs fresh database (modifies data)
  23  	db, ctx, cleanup := setupFreshTestDB(t)
  24  	defer cleanup()
  25  
  26  	sign := p8k.MustNew()
  27  	if err := sign.Generate(); chk.E(err) {
  28  		t.Fatal(err)
  29  	}
  30  
  31  	// Create a base parameterized replaceable event
  32  	baseEvent := event.New()
  33  	baseEvent.Kind = kind.ParameterizedReplaceableStart.K // Kind 30000+ is parameterized replaceable
  34  	baseEvent.CreatedAt = timestamp.Now().V - 7200        // 2 hours ago
  35  	baseEvent.Content = []byte("Original parameterized event")
  36  	baseEvent.Tags = tag.NewS()
  37  	// Add a d-tag
  38  	*baseEvent.Tags = append(
  39  		*baseEvent.Tags, tag.NewFromAny("d", "test-d-tag"),
  40  	)
  41  	baseEvent.Sign(sign)
  42  
  43  	// Save the base parameterized replaceable event
  44  	if _, err := db.SaveEvent(ctx, baseEvent); err != nil {
  45  		t.Fatalf("Failed to save base parameterized replaceable event: %v", err)
  46  	}
  47  
  48  	// Create a newer parameterized replaceable event with the same pubkey, kind, and d-tag
  49  	newerEvent := event.New()
  50  	newerEvent.Kind = baseEvent.Kind                // Same parameterized kind
  51  	newerEvent.CreatedAt = timestamp.Now().V - 3600 // 1 hour ago (newer than base event)
  52  	newerEvent.Content = []byte("Newer parameterized event")
  53  	newerEvent.Tags = tag.NewS()
  54  	// Add the same d-tag
  55  	*newerEvent.Tags = append(
  56  		*newerEvent.Tags,
  57  		tag.NewFromAny("d", "test-d-tag"),
  58  	)
  59  	newerEvent.Sign(sign)
  60  
  61  	// Save the newer parameterized replaceable event
  62  	if _, err := db.SaveEvent(ctx, newerEvent); err != nil {
  63  		t.Fatalf(
  64  			"Failed to save newer parameterized replaceable event: %v", err,
  65  		)
  66  	}
  67  
  68  	// Create an even newer parameterized replaceable event with the same pubkey, kind, and d-tag
  69  	newestEvent := event.New()
  70  	newestEvent.Kind = baseEvent.Kind         // Same parameterized kind
  71  	newestEvent.CreatedAt = timestamp.Now().V // Current time (newest)
  72  	newestEvent.Content = []byte("Newest parameterized event")
  73  	newestEvent.Tags = tag.NewS()
  74  	// Add the same d-tag
  75  	*newestEvent.Tags = append(
  76  		*newestEvent.Tags,
  77  		tag.NewFromAny("d", "test-d-tag"),
  78  	)
  79  	newestEvent.Sign(sign)
  80  
  81  	// Save the newest parameterized replaceable event
  82  	if _, err := db.SaveEvent(ctx, newestEvent); err != nil {
  83  		t.Fatalf(
  84  			"Failed to save newest parameterized replaceable event: %v", err,
  85  		)
  86  	}
  87  
  88  	// Query for all events of this kind and pubkey
  89  	paramKindFilter := kind.NewS(kind.New(baseEvent.Kind))
  90  	paramAuthorFilter := tag.NewFromBytesSlice(baseEvent.Pubkey)
  91  
  92  	evs, err := db.QueryEvents(
  93  		ctx, &filter.F{
  94  			Kinds:   paramKindFilter,
  95  			Authors: paramAuthorFilter,
  96  		},
  97  	)
  98  	if err != nil {
  99  		t.Fatalf(
 100  			"Failed to query for parameterized replaceable events: %v", err,
 101  		)
 102  	}
 103  
 104  	// Print debug info about the returned events
 105  	fmt.Printf("Debug: Got %d events\n", len(evs))
 106  	for i, ev := range evs {
 107  		fmt.Printf(
 108  			"Debug: Event %d: kind=%d, pubkey=%s, created_at=%d, content=%s\n",
 109  			i, ev.Kind, hex.Enc(ev.Pubkey), ev.CreatedAt, ev.Content,
 110  		)
 111  		dTag := ev.Tags.GetFirst([]byte("d"))
 112  		if dTag != nil && dTag.Len() > 1 {
 113  			fmt.Printf("Debug: Event %d: d-tag=%s\n", i, dTag.Value())
 114  		}
 115  	}
 116  
 117  	// Verify we get exactly one event (the newest one)
 118  	if len(evs) != 1 {
 119  		t.Fatalf(
 120  			"Expected 1 event when querying for parameterized replaceable events, got %d",
 121  			len(evs),
 122  		)
 123  	}
 124  
 125  	// Verify it's the newest event
 126  	if !utils.FastEqual(evs[0].ID, newestEvent.ID) {
 127  		t.Fatalf(
 128  			"Event ID doesn't match the newest event. Got %x, expected %x",
 129  			evs[0].ID, newestEvent.ID,
 130  		)
 131  	}
 132  
 133  	// Verify the content is from the newest event
 134  	if string(evs[0].Content) != string(newestEvent.Content) {
 135  		t.Fatalf(
 136  			"Event content doesn't match the newest event. Got %s, expected %s",
 137  			evs[0].Content, newestEvent.Content,
 138  		)
 139  	}
 140  
 141  	// Query for the base event by ID
 142  	evs, err = db.QueryEvents(
 143  		ctx, &filter.F{
 144  			Ids: tag.NewFromBytesSlice(baseEvent.ID),
 145  		},
 146  	)
 147  	if err != nil {
 148  		t.Fatalf("Failed to query for base event by ID: %v", err)
 149  	}
 150  
 151  	// Verify we get 1 event when querying for the base event by ID
 152  	// Replaced events should still be accessible by their ID
 153  	if len(evs) != 1 {
 154  		t.Fatalf(
 155  			"Expected 1 event when querying for replaced base event by ID, got %d",
 156  			len(evs),
 157  		)
 158  	}
 159  }
 160