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