query-events_test.go raw
1 package database
2
3 import (
4 "context"
5 "fmt"
6 "os"
7 "testing"
8
9 "next.orly.dev/pkg/nostr/encoders/event"
10 "next.orly.dev/pkg/nostr/encoders/filter"
11 "next.orly.dev/pkg/nostr/encoders/hex"
12 "next.orly.dev/pkg/nostr/encoders/kind"
13 "next.orly.dev/pkg/nostr/encoders/tag"
14 "next.orly.dev/pkg/nostr/encoders/timestamp"
15 "next.orly.dev/pkg/nostr/interfaces/signer/p8k"
16 "next.orly.dev/pkg/lol/chk"
17 "next.orly.dev/pkg/utils"
18 )
19
20 // setupFreshTestDB creates a new isolated test database for tests that modify data.
21 // Use this for tests that need to write/delete events.
22 func setupFreshTestDB(t *testing.T) (*D, context.Context, func()) {
23 if testing.Short() {
24 t.Skip("skipping test that requires fresh database in short mode")
25 }
26
27 tempDir, err := os.MkdirTemp("", "test-db-*")
28 if err != nil {
29 t.Fatalf("Failed to create temporary directory: %v", err)
30 }
31
32 ctx, cancel := context.WithCancel(context.Background())
33
34 db, err := New(ctx, cancel, tempDir, "info")
35 if err != nil {
36 os.RemoveAll(tempDir)
37 t.Fatalf("Failed to create database: %v", err)
38 }
39
40 cleanup := func() {
41 db.Close()
42 cancel()
43 os.RemoveAll(tempDir)
44 }
45
46 return db, ctx, cleanup
47 }
48
49 func TestQueryEventsByID(t *testing.T) {
50 // Use shared database (read-only test)
51 db, ctx := GetSharedDB(t)
52 events := GetSharedEvents(t)
53
54 if len(events) < 4 {
55 t.Fatalf("Need at least 4 saved events, got %d", len(events))
56 }
57 testEvent := events[3]
58
59 evs, err := db.QueryEvents(
60 ctx, &filter.F{
61 Ids: tag.NewFromBytesSlice(testEvent.ID),
62 },
63 )
64 if err != nil {
65 t.Fatalf("Failed to query events by ID: %v", err)
66 }
67
68 if len(evs) != 1 {
69 t.Fatalf("Expected 1 event, got %d", len(evs))
70 }
71
72 if !utils.FastEqual(evs[0].ID, testEvent.ID) {
73 t.Fatalf(
74 "Event ID doesn't match. Got %x, expected %x", evs[0].ID,
75 testEvent.ID,
76 )
77 }
78 }
79
80 func TestQueryEventsByKind(t *testing.T) {
81 // Use shared database (read-only test)
82 db, ctx := GetSharedDB(t)
83
84 testKind := kind.New(1) // Kind 1 is typically text notes
85 kindFilter := kind.NewS(testKind)
86
87 evs, err := db.QueryEvents(
88 ctx, &filter.F{
89 Kinds: kindFilter,
90 Tags: tag.NewS(),
91 },
92 )
93 if err != nil {
94 t.Fatalf("Failed to query events by kind: %v", err)
95 }
96
97 if len(evs) == 0 {
98 t.Fatal("Expected events with kind 1, but got none")
99 }
100
101 for i, ev := range evs {
102 if ev.Kind != testKind.K {
103 t.Fatalf(
104 "Event %d has incorrect kind. Got %d, expected %d", i,
105 ev.Kind, testKind.K,
106 )
107 }
108 }
109 }
110
111 func TestQueryEventsByAuthor(t *testing.T) {
112 // Use shared database (read-only test)
113 db, ctx := GetSharedDB(t)
114 events := GetSharedEvents(t)
115
116 if len(events) < 2 {
117 t.Fatalf("Need at least 2 saved events, got %d", len(events))
118 }
119
120 authorFilter := tag.NewFromBytesSlice(events[1].Pubkey)
121
122 evs, err := db.QueryEvents(
123 ctx, &filter.F{
124 Authors: authorFilter,
125 },
126 )
127 if err != nil {
128 t.Fatalf("Failed to query events by author: %v", err)
129 }
130
131 if len(evs) == 0 {
132 t.Fatal("Expected events from author, but got none")
133 }
134
135 for i, ev := range evs {
136 if !utils.FastEqual(ev.Pubkey, events[1].Pubkey) {
137 t.Fatalf(
138 "Event %d has incorrect author. Got %x, expected %x",
139 i, ev.Pubkey, events[1].Pubkey,
140 )
141 }
142 }
143 }
144
145 func TestReplaceableEventsAndDeletion(t *testing.T) {
146 // Needs fresh database (modifies data)
147 db, ctx, cleanup := setupFreshTestDB(t)
148 defer cleanup()
149
150 // Seed with a few events for pubkey reference
151 events := GetSharedEvents(t)
152 if len(events) == 0 {
153 t.Fatal("Need at least 1 event for pubkey reference")
154 }
155
156 sign := p8k.MustNew()
157 if err := sign.Generate(); chk.E(err) {
158 t.Fatal(err)
159 }
160
161 // Create a replaceable event
162 replaceableEvent := event.New()
163 replaceableEvent.Kind = kind.ProfileMetadata.K
164 replaceableEvent.Pubkey = events[0].Pubkey
165 replaceableEvent.CreatedAt = timestamp.Now().V - 7200
166 replaceableEvent.Content = []byte("Original profile")
167 replaceableEvent.Tags = tag.NewS()
168 replaceableEvent.Sign(sign)
169
170 if _, err := db.SaveEvent(ctx, replaceableEvent); err != nil {
171 t.Errorf("Failed to save replaceable event: %v", err)
172 }
173
174 // Create a newer version
175 newerEvent := event.New()
176 newerEvent.Kind = kind.ProfileMetadata.K
177 newerEvent.Pubkey = replaceableEvent.Pubkey
178 newerEvent.CreatedAt = timestamp.Now().V - 3600
179 newerEvent.Content = []byte("Updated profile")
180 newerEvent.Tags = tag.NewS()
181 newerEvent.Sign(sign)
182
183 if _, err := db.SaveEvent(ctx, newerEvent); err != nil {
184 t.Errorf("Failed to save newer event: %v", err)
185 }
186
187 // Query for the original event by ID
188 evs, err := db.QueryEvents(
189 ctx, &filter.F{
190 Ids: tag.NewFromAny(replaceableEvent.ID),
191 },
192 )
193 if err != nil {
194 t.Errorf("Failed to query for replaced event by ID: %v", err)
195 }
196
197 if len(evs) != 1 {
198 t.Errorf("Expected 1 event when querying for replaced event by ID, got %d", len(evs))
199 }
200
201 if !utils.FastEqual(evs[0].ID, replaceableEvent.ID) {
202 t.Errorf(
203 "Event ID doesn't match when querying for replaced event. Got %x, expected %x",
204 evs[0].ID, replaceableEvent.ID,
205 )
206 }
207
208 // Query for all events of this kind and pubkey
209 kindFilter := kind.NewS(kind.ProfileMetadata)
210 authorFilter := tag.NewFromAny(replaceableEvent.Pubkey)
211
212 evs, err = db.QueryEvents(
213 ctx, &filter.F{
214 Kinds: kindFilter,
215 Authors: authorFilter,
216 },
217 )
218 if err != nil {
219 t.Errorf("Failed to query for replaceable events: %v", err)
220 }
221
222 if len(evs) != 1 {
223 t.Errorf(
224 "Expected 1 event when querying for replaceable events, got %d",
225 len(evs),
226 )
227 }
228
229 if !utils.FastEqual(evs[0].ID, newerEvent.ID) {
230 t.Fatalf(
231 "Event ID doesn't match when querying for replaceable events. Got %x, expected %x",
232 evs[0].ID, newerEvent.ID,
233 )
234 }
235
236 // Test deletion events
237 deletionEvent := event.New()
238 deletionEvent.Kind = kind.Deletion.K
239 deletionEvent.Pubkey = replaceableEvent.Pubkey
240 deletionEvent.CreatedAt = timestamp.Now().V
241 deletionEvent.Content = []byte("Deleting the replaceable event")
242 deletionEvent.Tags = tag.NewS()
243 deletionEvent.Sign(sign)
244
245 *deletionEvent.Tags = append(
246 *deletionEvent.Tags,
247 tag.NewFromAny("e", hex.Enc(replaceableEvent.ID)),
248 )
249
250 if _, err = db.SaveEvent(ctx, deletionEvent); err != nil {
251 t.Fatalf("Failed to save deletion event: %v", err)
252 }
253
254 // Query for all events of this kind and pubkey again
255 evs, err = db.QueryEvents(
256 ctx, &filter.F{
257 Kinds: kindFilter,
258 Authors: authorFilter,
259 },
260 )
261 if err != nil {
262 t.Errorf(
263 "Failed to query for replaceable events after deletion: %v", err,
264 )
265 }
266
267 if len(evs) != 1 {
268 t.Fatalf(
269 "Expected 1 event when querying for replaceable events after deletion, got %d",
270 len(evs),
271 )
272 }
273
274 if !utils.FastEqual(evs[0].ID, newerEvent.ID) {
275 t.Fatalf(
276 "Event ID doesn't match after deletion. Got %x, expected %x",
277 evs[0].ID, newerEvent.ID,
278 )
279 }
280
281 // Query for the original event by ID
282 evs, err = db.QueryEvents(
283 ctx, &filter.F{
284 Ids: tag.NewFromBytesSlice(replaceableEvent.ID),
285 },
286 )
287 if err != nil {
288 t.Errorf("Failed to query for deleted event by ID: %v", err)
289 }
290
291 if len(evs) != 0 {
292 t.Errorf("Expected 0 events when querying for deleted event by ID, got %d", len(evs))
293 }
294 }
295
296 func TestParameterizedReplaceableEventsAndDeletion(t *testing.T) {
297 // Needs fresh database (modifies data)
298 db, ctx, cleanup := setupFreshTestDB(t)
299 defer cleanup()
300
301 events := GetSharedEvents(t)
302 if len(events) == 0 {
303 t.Fatal("Need at least 1 event for pubkey reference")
304 }
305
306 sign := p8k.MustNew()
307 if err := sign.Generate(); chk.E(err) {
308 t.Fatal(err)
309 }
310
311 // Create a parameterized replaceable event
312 paramEvent := event.New()
313 paramEvent.Kind = 30000
314 paramEvent.Pubkey = events[0].Pubkey
315 paramEvent.CreatedAt = timestamp.Now().V - 7200
316 paramEvent.Content = []byte("Original parameterized event")
317 paramEvent.Tags = tag.NewS()
318 *paramEvent.Tags = append(
319 *paramEvent.Tags, tag.NewFromAny([]byte{'d'}, []byte("test-d-tag")),
320 )
321 paramEvent.Sign(sign)
322
323 if _, err := db.SaveEvent(ctx, paramEvent); err != nil {
324 t.Fatalf("Failed to save parameterized replaceable event: %v", err)
325 }
326
327 // Create a deletion event
328 paramDeletionEvent := event.New()
329 paramDeletionEvent.Kind = kind.Deletion.K
330 paramDeletionEvent.Pubkey = paramEvent.Pubkey
331 paramDeletionEvent.CreatedAt = timestamp.Now().V
332 paramDeletionEvent.Content = []byte("Deleting the parameterized replaceable event")
333 paramDeletionEvent.Tags = tag.NewS()
334 aTagValue := fmt.Sprintf(
335 "%d:%s:%s",
336 paramEvent.Kind,
337 hex.Enc(paramEvent.Pubkey),
338 "test-d-tag",
339 )
340 *paramDeletionEvent.Tags = append(
341 *paramDeletionEvent.Tags,
342 tag.NewFromAny([]byte{'a'}, []byte(aTagValue)),
343 )
344 paramDeletionEvent.Sign(sign)
345
346 if _, err := db.SaveEvent(ctx, paramDeletionEvent); err != nil {
347 t.Fatalf("Failed to save parameterized deletion event: %v", err)
348 }
349
350 // Create deletion with e-tag too
351 paramDeletionEvent2 := event.New()
352 paramDeletionEvent2.Kind = kind.Deletion.K
353 paramDeletionEvent2.Pubkey = paramEvent.Pubkey
354 paramDeletionEvent2.CreatedAt = timestamp.Now().V
355 paramDeletionEvent2.Content = []byte("Deleting with e-tag")
356 paramDeletionEvent2.Tags = tag.NewS()
357 *paramDeletionEvent2.Tags = append(
358 *paramDeletionEvent2.Tags,
359 tag.NewFromAny("e", []byte(hex.Enc(paramEvent.ID))),
360 )
361 paramDeletionEvent2.Sign(sign)
362
363 if _, err := db.SaveEvent(ctx, paramDeletionEvent2); err != nil {
364 t.Fatalf("Failed to save deletion event with e-tag: %v", err)
365 }
366
367 // Query for all events of this kind and pubkey
368 paramKindFilter := kind.NewS(kind.New(paramEvent.Kind))
369 paramAuthorFilter := tag.NewFromBytesSlice(paramEvent.Pubkey)
370
371 evs, err := db.QueryEvents(
372 ctx, &filter.F{
373 Kinds: paramKindFilter,
374 Authors: paramAuthorFilter,
375 },
376 )
377 if err != nil {
378 t.Fatalf("Failed to query for parameterized events: %v", err)
379 }
380
381 if len(evs) != 0 {
382 t.Fatalf("Expected 0 events after deletion, got %d", len(evs))
383 }
384
385 // Query by ID
386 evs, err = db.QueryEvents(
387 ctx, &filter.F{
388 Ids: tag.NewFromBytesSlice(paramEvent.ID),
389 },
390 )
391 if err != nil {
392 t.Fatalf("Failed to query for deleted event by ID: %v", err)
393 }
394
395 if len(evs) != 0 {
396 t.Fatalf("Expected 0 events when querying deleted event by ID, got %d", len(evs))
397 }
398 }
399
400 func TestQueryEventsByTimeRange(t *testing.T) {
401 // Use shared database (read-only test)
402 db, ctx := GetSharedDB(t)
403 events := GetSharedEvents(t)
404
405 if len(events) < 10 {
406 t.Fatalf("Need at least 10 saved events, got %d", len(events))
407 }
408
409 middleIndex := len(events) / 2
410 middleEvent := events[middleIndex]
411
412 sinceTime := new(timestamp.T)
413 sinceTime.V = middleEvent.CreatedAt - 3600
414
415 untilTime := new(timestamp.T)
416 untilTime.V = middleEvent.CreatedAt + 3600
417
418 evs, err := db.QueryEvents(
419 ctx, &filter.F{
420 Since: sinceTime,
421 Until: untilTime,
422 },
423 )
424 if err != nil {
425 t.Fatalf("Failed to query events by time range: %v", err)
426 }
427
428 if len(evs) == 0 {
429 t.Fatal("Expected events in time range, but got none")
430 }
431
432 for i, ev := range evs {
433 if ev.CreatedAt < sinceTime.V || ev.CreatedAt > untilTime.V {
434 t.Fatalf(
435 "Event %d is outside the time range. Got %d, expected between %d and %d",
436 i, ev.CreatedAt, sinceTime.V, untilTime.V,
437 )
438 }
439 }
440 }
441
442 func TestQueryEventsByTag(t *testing.T) {
443 // Use shared database (read-only test)
444 db, ctx := GetSharedDB(t)
445 events := GetSharedEvents(t)
446
447 // Find an event with tags
448 var testTagEvent *event.E
449 for _, ev := range events {
450 if ev.Tags != nil && ev.Tags.Len() > 0 {
451 for _, tg := range *ev.Tags {
452 if tg.Len() >= 2 && len(tg.Key()) == 1 {
453 testTagEvent = ev
454 break
455 }
456 }
457 if testTagEvent != nil {
458 break
459 }
460 }
461 }
462
463 if testTagEvent == nil {
464 t.Skip("No suitable event with tags found for testing")
465 return
466 }
467
468 var testTag *tag.T
469 for _, tg := range *testTagEvent.Tags {
470 if tg.Len() >= 2 && len(tg.Key()) == 1 {
471 testTag = tg
472 break
473 }
474 }
475
476 tagsFilter := tag.NewS(testTag)
477
478 evs, err := db.QueryEvents(
479 ctx, &filter.F{
480 Tags: tagsFilter,
481 },
482 )
483 if err != nil {
484 t.Fatalf("Failed to query events by tag: %v", err)
485 }
486
487 if len(evs) == 0 {
488 t.Fatal("Expected events with tag, but got none")
489 }
490
491 for i, ev := range evs {
492 var hasTag bool
493 for _, tg := range *ev.Tags {
494 if tg.Len() >= 2 && len(tg.Key()) == 1 {
495 if utils.FastEqual(tg.Key(), testTag.Key()) &&
496 utils.FastEqual(tg.Value(), testTag.Value()) {
497 hasTag = true
498 break
499 }
500 }
501 }
502 if !hasTag {
503 t.Fatalf("Event %d does not have the expected tag", i)
504 }
505 }
506 }
507