query-events_test.go raw
1 //go:build integration
2 // +build integration
3
4 package neo4j
5
6 import (
7 "context"
8 "testing"
9
10 "next.orly.dev/pkg/nostr/encoders/event"
11 "next.orly.dev/pkg/nostr/encoders/filter"
12 "next.orly.dev/pkg/nostr/encoders/hex"
13 "next.orly.dev/pkg/nostr/encoders/kind"
14 "next.orly.dev/pkg/nostr/encoders/tag"
15 "next.orly.dev/pkg/nostr/encoders/timestamp"
16 "next.orly.dev/pkg/nostr/interfaces/signer/p8k"
17 )
18
19 // All tests in this file use the shared testDB instance from testmain_test.go
20 // to avoid Neo4j authentication rate limiting from too many connections.
21
22 // createTestSignerLocal creates a new signer for test events
23 func createTestSignerLocal(t *testing.T) *p8k.Signer {
24 t.Helper()
25
26 signer, err := p8k.New()
27 if err != nil {
28 t.Fatalf("Failed to create signer: %v", err)
29 }
30 if err := signer.Generate(); err != nil {
31 t.Fatalf("Failed to generate keypair: %v", err)
32 }
33 return signer
34 }
35
36 // createAndSaveEventLocal creates a signed event and saves it to the database
37 func createAndSaveEventLocal(t *testing.T, ctx context.Context, signer *p8k.Signer, k uint16, content string, tags *tag.S, ts int64) *event.E {
38 t.Helper()
39
40 ev := event.New()
41 ev.Pubkey = signer.Pub()
42 ev.CreatedAt = ts
43 ev.Kind = k
44 ev.Content = []byte(content)
45 ev.Tags = tags
46
47 if err := ev.Sign(signer); err != nil {
48 t.Fatalf("Failed to sign event: %v", err)
49 }
50
51 if _, err := testDB.SaveEvent(ctx, ev); err != nil {
52 t.Fatalf("Failed to save event: %v", err)
53 }
54
55 return ev
56 }
57
58 func TestQueryEventsByID(t *testing.T) {
59 if testDB == nil {
60 t.Skip("Neo4j not available")
61 }
62
63 cleanTestDatabase()
64
65 ctx := context.Background()
66 signer := createTestSignerLocal(t)
67
68 // Create and save a test event
69 ev := createAndSaveEventLocal(t, ctx, signer, 1, "Test event for ID query", nil, timestamp.Now().V)
70
71 // Query by ID
72 evs, err := testDB.QueryEvents(ctx, &filter.F{
73 Ids: tag.NewFromBytesSlice(ev.ID),
74 })
75 if err != nil {
76 t.Fatalf("Failed to query events by ID: %v", err)
77 }
78
79 if len(evs) != 1 {
80 t.Fatalf("Expected 1 event, got %d", len(evs))
81 }
82
83 if hex.Enc(evs[0].ID[:]) != hex.Enc(ev.ID[:]) {
84 t.Fatalf("Event ID mismatch: got %s, expected %s",
85 hex.Enc(evs[0].ID[:]), hex.Enc(ev.ID[:]))
86 }
87
88 t.Logf("✓ Query by ID returned correct event")
89 }
90
91 func TestQueryEventsByKind(t *testing.T) {
92 if testDB == nil {
93 t.Skip("Neo4j not available")
94 }
95
96 cleanTestDatabase()
97
98 ctx := context.Background()
99 signer := createTestSignerLocal(t)
100 baseTs := timestamp.Now().V
101
102 // Create events of different kinds
103 createAndSaveEventLocal(t, ctx, signer, 1, "Kind 1 event A", nil, baseTs)
104 createAndSaveEventLocal(t, ctx, signer, 1, "Kind 1 event B", nil, baseTs+1)
105 createAndSaveEventLocal(t, ctx, signer, 7, "Kind 7 reaction", nil, baseTs+2)
106 createAndSaveEventLocal(t, ctx, signer, 30023, "Kind 30023 article", nil, baseTs+3)
107
108 // Query for kind 1
109 evs, err := testDB.QueryEvents(ctx, &filter.F{
110 Kinds: kind.NewS(kind.New(1)),
111 })
112 if err != nil {
113 t.Fatalf("Failed to query events by kind: %v", err)
114 }
115
116 if len(evs) != 2 {
117 t.Fatalf("Expected 2 kind 1 events, got %d", len(evs))
118 }
119
120 for _, ev := range evs {
121 if ev.Kind != 1 {
122 t.Fatalf("Expected kind 1, got %d", ev.Kind)
123 }
124 }
125
126 t.Logf("✓ Query by kind returned %d correct events", len(evs))
127 }
128
129 func TestQueryEventsByAuthor(t *testing.T) {
130 if testDB == nil {
131 t.Skip("Neo4j not available")
132 }
133
134 cleanTestDatabase()
135
136 ctx := context.Background()
137 alice := createTestSignerLocal(t)
138 bob := createTestSignerLocal(t)
139 baseTs := timestamp.Now().V
140
141 // Create events from different authors
142 createAndSaveEventLocal(t, ctx, alice, 1, "Alice's event 1", nil, baseTs)
143 createAndSaveEventLocal(t, ctx, alice, 1, "Alice's event 2", nil, baseTs+1)
144 createAndSaveEventLocal(t, ctx, bob, 1, "Bob's event", nil, baseTs+2)
145
146 // Query for Alice's events
147 evs, err := testDB.QueryEvents(ctx, &filter.F{
148 Authors: tag.NewFromBytesSlice(alice.Pub()),
149 })
150 if err != nil {
151 t.Fatalf("Failed to query events by author: %v", err)
152 }
153
154 if len(evs) != 2 {
155 t.Fatalf("Expected 2 events from Alice, got %d", len(evs))
156 }
157
158 alicePubkey := hex.Enc(alice.Pub())
159 for _, ev := range evs {
160 if hex.Enc(ev.Pubkey[:]) != alicePubkey {
161 t.Fatalf("Expected author %s, got %s", alicePubkey, hex.Enc(ev.Pubkey[:]))
162 }
163 }
164
165 t.Logf("✓ Query by author returned %d correct events", len(evs))
166 }
167
168 func TestQueryEventsByTimeRange(t *testing.T) {
169 if testDB == nil {
170 t.Skip("Neo4j not available")
171 }
172
173 cleanTestDatabase()
174
175 ctx := context.Background()
176 signer := createTestSignerLocal(t)
177 baseTs := timestamp.Now().V
178
179 // Create events at different times
180 createAndSaveEventLocal(t, ctx, signer, 1, "Old event", nil, baseTs-7200) // 2 hours ago
181 createAndSaveEventLocal(t, ctx, signer, 1, "Recent event", nil, baseTs-1800) // 30 min ago
182 createAndSaveEventLocal(t, ctx, signer, 1, "Current event", nil, baseTs)
183
184 // Query for events in the last hour
185 since := ×tamp.T{V: baseTs - 3600}
186 evs, err := testDB.QueryEvents(ctx, &filter.F{
187 Since: since,
188 })
189 if err != nil {
190 t.Fatalf("Failed to query events by time range: %v", err)
191 }
192
193 if len(evs) != 2 {
194 t.Fatalf("Expected 2 events in last hour, got %d", len(evs))
195 }
196
197 for _, ev := range evs {
198 if ev.CreatedAt < since.V {
199 t.Fatalf("Event created_at %d is before since %d", ev.CreatedAt, since.V)
200 }
201 }
202
203 t.Logf("✓ Query by time range returned %d correct events", len(evs))
204 }
205
206 func TestQueryEventsByTag(t *testing.T) {
207 if testDB == nil {
208 t.Skip("Neo4j not available")
209 }
210
211 cleanTestDatabase()
212
213 ctx := context.Background()
214 signer := createTestSignerLocal(t)
215 baseTs := timestamp.Now().V
216
217 // Create events with tags
218 createAndSaveEventLocal(t, ctx, signer, 1, "Bitcoin post",
219 tag.NewS(tag.NewFromAny("t", "bitcoin")), baseTs)
220 createAndSaveEventLocal(t, ctx, signer, 1, "Nostr post",
221 tag.NewS(tag.NewFromAny("t", "nostr")), baseTs+1)
222 createAndSaveEventLocal(t, ctx, signer, 1, "Bitcoin and Nostr post",
223 tag.NewS(tag.NewFromAny("t", "bitcoin"), tag.NewFromAny("t", "nostr")), baseTs+2)
224
225 // Query for bitcoin tagged events
226 evs, err := testDB.QueryEvents(ctx, &filter.F{
227 Tags: tag.NewS(tag.NewFromAny("t", "bitcoin")),
228 })
229 if err != nil {
230 t.Fatalf("Failed to query events by tag: %v", err)
231 }
232
233 if len(evs) != 2 {
234 t.Fatalf("Expected 2 bitcoin-tagged events, got %d", len(evs))
235 }
236
237 t.Logf("✓ Query by tag returned %d correct events", len(evs))
238 }
239
240 func TestQueryEventsByKindAndAuthor(t *testing.T) {
241 if testDB == nil {
242 t.Skip("Neo4j not available")
243 }
244
245 cleanTestDatabase()
246
247 ctx := context.Background()
248 alice := createTestSignerLocal(t)
249 bob := createTestSignerLocal(t)
250 baseTs := timestamp.Now().V
251
252 // Create events
253 createAndSaveEventLocal(t, ctx, alice, 1, "Alice note", nil, baseTs)
254 createAndSaveEventLocal(t, ctx, alice, 7, "Alice reaction", nil, baseTs+1)
255 createAndSaveEventLocal(t, ctx, bob, 1, "Bob note", nil, baseTs+2)
256
257 // Query for Alice's kind 1 events
258 evs, err := testDB.QueryEvents(ctx, &filter.F{
259 Kinds: kind.NewS(kind.New(1)),
260 Authors: tag.NewFromBytesSlice(alice.Pub()),
261 })
262 if err != nil {
263 t.Fatalf("Failed to query events by kind and author: %v", err)
264 }
265
266 if len(evs) != 1 {
267 t.Fatalf("Expected 1 kind 1 event from Alice, got %d", len(evs))
268 }
269
270 t.Logf("✓ Query by kind and author returned correct events")
271 }
272
273 func TestQueryEventsWithLimit(t *testing.T) {
274 if testDB == nil {
275 t.Skip("Neo4j not available")
276 }
277
278 cleanTestDatabase()
279
280 ctx := context.Background()
281 signer := createTestSignerLocal(t)
282 baseTs := timestamp.Now().V
283
284 // Create many events
285 for i := 0; i < 20; i++ {
286 createAndSaveEventLocal(t, ctx, signer, 1, "Event", nil, baseTs+int64(i))
287 }
288
289 // Query with limit
290 limit := uint(5)
291 evs, err := testDB.QueryEvents(ctx, &filter.F{
292 Kinds: kind.NewS(kind.New(1)),
293 Limit: &limit,
294 })
295 if err != nil {
296 t.Fatalf("Failed to query events with limit: %v", err)
297 }
298
299 if len(evs) != int(limit) {
300 t.Fatalf("Expected %d events with limit, got %d", limit, len(evs))
301 }
302
303 t.Logf("✓ Query with limit returned %d events", len(evs))
304 }
305
306 func TestQueryEventsOrderByCreatedAt(t *testing.T) {
307 if testDB == nil {
308 t.Skip("Neo4j not available")
309 }
310
311 cleanTestDatabase()
312
313 ctx := context.Background()
314 signer := createTestSignerLocal(t)
315 baseTs := timestamp.Now().V
316
317 // Create events at different times
318 createAndSaveEventLocal(t, ctx, signer, 1, "First", nil, baseTs)
319 createAndSaveEventLocal(t, ctx, signer, 1, "Second", nil, baseTs+100)
320 createAndSaveEventLocal(t, ctx, signer, 1, "Third", nil, baseTs+200)
321
322 // Query and verify order (should be descending by created_at)
323 evs, err := testDB.QueryEvents(ctx, &filter.F{
324 Kinds: kind.NewS(kind.New(1)),
325 })
326 if err != nil {
327 t.Fatalf("Failed to query events: %v", err)
328 }
329
330 if len(evs) < 2 {
331 t.Fatalf("Expected at least 2 events, got %d", len(evs))
332 }
333
334 // Verify descending order
335 for i := 1; i < len(evs); i++ {
336 if evs[i-1].CreatedAt < evs[i].CreatedAt {
337 t.Fatalf("Events not in descending order: %d < %d at index %d",
338 evs[i-1].CreatedAt, evs[i].CreatedAt, i)
339 }
340 }
341
342 t.Logf("✓ Query returned events in correct descending order")
343 }
344
345 func TestQueryEventsEmpty(t *testing.T) {
346 if testDB == nil {
347 t.Skip("Neo4j not available")
348 }
349
350 cleanTestDatabase()
351
352 ctx := context.Background()
353
354 // Query for non-existent kind
355 evs, err := testDB.QueryEvents(ctx, &filter.F{
356 Kinds: kind.NewS(kind.New(99999)),
357 })
358 if err != nil {
359 t.Fatalf("Failed to query events: %v", err)
360 }
361
362 if len(evs) != 0 {
363 t.Fatalf("Expected 0 events, got %d", len(evs))
364 }
365
366 t.Logf("✓ Query for non-existent kind returned empty result")
367 }
368
369 func TestQueryEventsMultipleKinds(t *testing.T) {
370 if testDB == nil {
371 t.Skip("Neo4j not available")
372 }
373
374 cleanTestDatabase()
375
376 ctx := context.Background()
377 signer := createTestSignerLocal(t)
378 baseTs := timestamp.Now().V
379
380 // Create events of different kinds
381 createAndSaveEventLocal(t, ctx, signer, 1, "Note", nil, baseTs)
382 createAndSaveEventLocal(t, ctx, signer, 7, "Reaction", nil, baseTs+1)
383 createAndSaveEventLocal(t, ctx, signer, 30023, "Article", nil, baseTs+2)
384
385 // Query for multiple kinds
386 evs, err := testDB.QueryEvents(ctx, &filter.F{
387 Kinds: kind.NewS(kind.New(1), kind.New(7)),
388 })
389 if err != nil {
390 t.Fatalf("Failed to query events: %v", err)
391 }
392
393 if len(evs) != 2 {
394 t.Fatalf("Expected 2 events (kind 1 and 7), got %d", len(evs))
395 }
396
397 t.Logf("✓ Query for multiple kinds returned correct events")
398 }
399
400 func TestQueryEventsMultipleAuthors(t *testing.T) {
401 if testDB == nil {
402 t.Skip("Neo4j not available")
403 }
404
405 cleanTestDatabase()
406
407 ctx := context.Background()
408 alice := createTestSignerLocal(t)
409 bob := createTestSignerLocal(t)
410 charlie := createTestSignerLocal(t)
411 baseTs := timestamp.Now().V
412
413 // Create events from different authors
414 createAndSaveEventLocal(t, ctx, alice, 1, "Alice", nil, baseTs)
415 createAndSaveEventLocal(t, ctx, bob, 1, "Bob", nil, baseTs+1)
416 createAndSaveEventLocal(t, ctx, charlie, 1, "Charlie", nil, baseTs+2)
417
418 // Query for Alice and Bob's events
419 authors := tag.NewFromBytesSlice(alice.Pub(), bob.Pub())
420
421 evs, err := testDB.QueryEvents(ctx, &filter.F{
422 Authors: authors,
423 })
424 if err != nil {
425 t.Fatalf("Failed to query events: %v", err)
426 }
427
428 if len(evs) != 2 {
429 t.Fatalf("Expected 2 events from Alice and Bob, got %d", len(evs))
430 }
431
432 t.Logf("✓ Query for multiple authors returned correct events")
433 }
434
435 func TestCountEvents(t *testing.T) {
436 if testDB == nil {
437 t.Skip("Neo4j not available")
438 }
439
440 cleanTestDatabase()
441
442 ctx := context.Background()
443 signer := createTestSignerLocal(t)
444 baseTs := timestamp.Now().V
445
446 // Create events
447 for i := 0; i < 5; i++ {
448 createAndSaveEventLocal(t, ctx, signer, 1, "Event", nil, baseTs+int64(i))
449 }
450
451 // Count events
452 count, _, err := testDB.CountEvents(ctx, &filter.F{
453 Kinds: kind.NewS(kind.New(1)),
454 })
455 if err != nil {
456 t.Fatalf("Failed to count events: %v", err)
457 }
458
459 if count != 5 {
460 t.Fatalf("Expected count 5, got %d", count)
461 }
462
463 t.Logf("✓ Count events returned correct count: %d", count)
464 }
465
466 // TestQueryEventsByTagWithHashPrefix tests that tag filters with "#" prefix work correctly.
467 // This is a regression test for a bug where filter tags like "#d" were not being matched
468 // because the "#" prefix wasn't being stripped before comparison with stored tags.
469 func TestQueryEventsByTagWithHashPrefix(t *testing.T) {
470 if testDB == nil {
471 t.Skip("Neo4j not available")
472 }
473
474 cleanTestDatabase()
475
476 ctx := context.Background()
477 signer := createTestSignerLocal(t)
478 baseTs := timestamp.Now().V
479
480 // Create events with d-tags (parameterized replaceable kind)
481 createAndSaveEventLocal(t, ctx, signer, 30382, "Event with d=id1",
482 tag.NewS(tag.NewFromAny("d", "id1")), baseTs)
483 createAndSaveEventLocal(t, ctx, signer, 30382, "Event with d=id2",
484 tag.NewS(tag.NewFromAny("d", "id2")), baseTs+1)
485 createAndSaveEventLocal(t, ctx, signer, 30382, "Event with d=id3",
486 tag.NewS(tag.NewFromAny("d", "id3")), baseTs+2)
487 createAndSaveEventLocal(t, ctx, signer, 30382, "Event with d=other",
488 tag.NewS(tag.NewFromAny("d", "other")), baseTs+3)
489
490 // Query with "#d" prefix (as clients send it) - should match events with d=id1
491 evs, err := testDB.QueryEvents(ctx, &filter.F{
492 Kinds: kind.NewS(kind.New(30382)),
493 Tags: tag.NewS(tag.NewFromAny("#d", "id1")),
494 })
495 if err != nil {
496 t.Fatalf("Failed to query events with #d tag: %v", err)
497 }
498
499 if len(evs) != 1 {
500 t.Fatalf("Expected 1 event with d=id1, got %d", len(evs))
501 }
502
503 // Verify the returned event has the correct d-tag
504 dTag := evs[0].Tags.GetFirst([]byte("d"))
505 if dTag == nil || string(dTag.Value()) != "id1" {
506 t.Fatalf("Expected d=id1, got d=%s", dTag.Value())
507 }
508
509 t.Logf("✓ Query with #d prefix returned correct event")
510 }
511
512 // TestQueryEventsByTagMultipleValues tests that tag filters with multiple values
513 // use OR logic (match events with ANY of the values).
514 func TestQueryEventsByTagMultipleValues(t *testing.T) {
515 if testDB == nil {
516 t.Skip("Neo4j not available")
517 }
518
519 cleanTestDatabase()
520
521 ctx := context.Background()
522 signer := createTestSignerLocal(t)
523 baseTs := timestamp.Now().V
524
525 // Create events with different d-tags
526 createAndSaveEventLocal(t, ctx, signer, 30382, "Event A",
527 tag.NewS(tag.NewFromAny("d", "target-1")), baseTs)
528 createAndSaveEventLocal(t, ctx, signer, 30382, "Event B",
529 tag.NewS(tag.NewFromAny("d", "target-2")), baseTs+1)
530 createAndSaveEventLocal(t, ctx, signer, 30382, "Event C",
531 tag.NewS(tag.NewFromAny("d", "target-3")), baseTs+2)
532 createAndSaveEventLocal(t, ctx, signer, 30382, "Event D (not target)",
533 tag.NewS(tag.NewFromAny("d", "other-value")), baseTs+3)
534 createAndSaveEventLocal(t, ctx, signer, 30382, "Event E (no match)",
535 tag.NewS(tag.NewFromAny("d", "different")), baseTs+4)
536
537 // Query with multiple d-tag values using "#d" prefix
538 // Should match events with d=target-1 OR d=target-2 OR d=target-3
539 evs, err := testDB.QueryEvents(ctx, &filter.F{
540 Kinds: kind.NewS(kind.New(30382)),
541 Tags: tag.NewS(tag.NewFromAny("#d", "target-1", "target-2", "target-3")),
542 })
543 if err != nil {
544 t.Fatalf("Failed to query events with multiple #d values: %v", err)
545 }
546
547 if len(evs) != 3 {
548 t.Fatalf("Expected 3 events matching the d-tag values, got %d", len(evs))
549 }
550
551 // Verify returned events have correct d-tags
552 validDTags := map[string]bool{"target-1": false, "target-2": false, "target-3": false}
553 for _, ev := range evs {
554 dTag := ev.Tags.GetFirst([]byte("d"))
555 if dTag == nil {
556 t.Fatalf("Event missing d-tag")
557 }
558 dValue := string(dTag.Value())
559 if _, ok := validDTags[dValue]; !ok {
560 t.Fatalf("Unexpected d-tag value: %s", dValue)
561 }
562 validDTags[dValue] = true
563 }
564
565 // Verify all expected d-tags were found
566 for dValue, found := range validDTags {
567 if !found {
568 t.Fatalf("Expected to find event with d=%s", dValue)
569 }
570 }
571
572 t.Logf("✓ Query with multiple #d values returned correct events")
573 }
574
575 // TestQueryEventsByTagNoMatch tests that tag filters correctly return no results
576 // when no events match the filter.
577 func TestQueryEventsByTagNoMatch(t *testing.T) {
578 if testDB == nil {
579 t.Skip("Neo4j not available")
580 }
581
582 cleanTestDatabase()
583
584 ctx := context.Background()
585 signer := createTestSignerLocal(t)
586 baseTs := timestamp.Now().V
587
588 // Create events with d-tags
589 createAndSaveEventLocal(t, ctx, signer, 30382, "Event",
590 tag.NewS(tag.NewFromAny("d", "existing-value")), baseTs)
591
592 // Query for d-tag value that doesn't exist
593 evs, err := testDB.QueryEvents(ctx, &filter.F{
594 Kinds: kind.NewS(kind.New(30382)),
595 Tags: tag.NewS(tag.NewFromAny("#d", "non-existent-value")),
596 })
597 if err != nil {
598 t.Fatalf("Failed to query events: %v", err)
599 }
600
601 if len(evs) != 0 {
602 t.Fatalf("Expected 0 events for non-matching d-tag, got %d", len(evs))
603 }
604
605 t.Logf("✓ Query with non-matching #d value returned no events")
606 }
607
608 // TestQueryEventsByTagWithKindAndAuthor tests the combination of kind, author, and tag filters.
609 // This is the specific case reported by the user with kind 30382.
610 func TestQueryEventsByTagWithKindAndAuthor(t *testing.T) {
611 if testDB == nil {
612 t.Skip("Neo4j not available")
613 }
614
615 cleanTestDatabase()
616
617 ctx := context.Background()
618 alice := createTestSignerLocal(t)
619 bob := createTestSignerLocal(t)
620 baseTs := timestamp.Now().V
621
622 // Create events from different authors with d-tags
623 createAndSaveEventLocal(t, ctx, alice, 30382, "Alice target 1",
624 tag.NewS(tag.NewFromAny("d", "card-1")), baseTs)
625 createAndSaveEventLocal(t, ctx, alice, 30382, "Alice target 2",
626 tag.NewS(tag.NewFromAny("d", "card-2")), baseTs+1)
627 createAndSaveEventLocal(t, ctx, alice, 30382, "Alice other",
628 tag.NewS(tag.NewFromAny("d", "other-card")), baseTs+2)
629 createAndSaveEventLocal(t, ctx, bob, 30382, "Bob target 1",
630 tag.NewS(tag.NewFromAny("d", "card-1")), baseTs+3) // Same d-tag as Alice but different author
631
632 // Query for Alice's events with specific d-tags
633 evs, err := testDB.QueryEvents(ctx, &filter.F{
634 Kinds: kind.NewS(kind.New(30382)),
635 Authors: tag.NewFromBytesSlice(alice.Pub()),
636 Tags: tag.NewS(tag.NewFromAny("#d", "card-1", "card-2")),
637 })
638 if err != nil {
639 t.Fatalf("Failed to query events: %v", err)
640 }
641
642 // Should only return Alice's 2 events, not Bob's even though he has card-1
643 if len(evs) != 2 {
644 t.Fatalf("Expected 2 events from Alice with matching d-tags, got %d", len(evs))
645 }
646
647 alicePubkey := hex.Enc(alice.Pub())
648 for _, ev := range evs {
649 if hex.Enc(ev.Pubkey[:]) != alicePubkey {
650 t.Fatalf("Expected author %s, got %s", alicePubkey, hex.Enc(ev.Pubkey[:]))
651 }
652 dTag := ev.Tags.GetFirst([]byte("d"))
653 dValue := string(dTag.Value())
654 if dValue != "card-1" && dValue != "card-2" {
655 t.Fatalf("Expected d=card-1 or card-2, got d=%s", dValue)
656 }
657 }
658
659 t.Logf("✓ Query with kind, author, and #d filter returned correct events")
660 }
661
662 // TestBinaryTagFilterRegression tests that queries with #e and #p tags work correctly
663 // even when tags are stored with binary-encoded values but filters come as hex strings.
664 // This mirrors the Badger database test for binary tag handling.
665 func TestBinaryTagFilterRegression(t *testing.T) {
666 if testDB == nil {
667 t.Skip("Neo4j not available")
668 }
669
670 cleanTestDatabase()
671
672 ctx := context.Background()
673 author := createTestSignerLocal(t)
674 referenced := createTestSignerLocal(t)
675 baseTs := timestamp.Now().V
676
677 // Create a referenced event to get a valid event ID for e-tag
678 refEvent := createAndSaveEventLocal(t, ctx, referenced, 1, "Referenced event", nil, baseTs)
679
680 // Get hex representations
681 refEventIdHex := hex.Enc(refEvent.ID)
682 refPubkeyHex := hex.Enc(referenced.Pub())
683
684 // Create test event with e, p, d, and other tags
685 testEvent := createAndSaveEventLocal(t, ctx, author, 30520, "Event with binary tags",
686 tag.NewS(
687 tag.NewFromAny("d", "test-d-value"),
688 tag.NewFromAny("p", string(refPubkeyHex)),
689 tag.NewFromAny("e", string(refEventIdHex)),
690 tag.NewFromAny("t", "test-topic"),
691 ), baseTs+1)
692
693 testEventIdHex := hex.Enc(testEvent.ID)
694
695 // Test case 1: Query WITHOUT #e/#p tags (baseline - should work)
696 t.Run("QueryWithoutEPTags", func(t *testing.T) {
697 evs, err := testDB.QueryEvents(ctx, &filter.F{
698 Kinds: kind.NewS(kind.New(30520)),
699 Authors: tag.NewFromBytesSlice(author.Pub()),
700 Tags: tag.NewS(tag.NewFromAny("#d", "test-d-value")),
701 })
702 if err != nil {
703 t.Fatalf("Query without e/p tags failed: %v", err)
704 }
705
706 if len(evs) == 0 {
707 t.Fatal("Expected to find event with d tag filter, got 0 results")
708 }
709
710 found := false
711 for _, ev := range evs {
712 if hex.Enc(ev.ID) == testEventIdHex {
713 found = true
714 break
715 }
716 }
717 if !found {
718 t.Errorf("Expected event ID %s not found", testEventIdHex)
719 }
720 })
721
722 // Test case 2: Query WITH #p tag
723 t.Run("QueryWithPTag", func(t *testing.T) {
724 evs, err := testDB.QueryEvents(ctx, &filter.F{
725 Kinds: kind.NewS(kind.New(30520)),
726 Authors: tag.NewFromBytesSlice(author.Pub()),
727 Tags: tag.NewS(
728 tag.NewFromAny("#d", "test-d-value"),
729 tag.NewFromAny("#p", string(refPubkeyHex)),
730 ),
731 })
732 if err != nil {
733 t.Fatalf("Query with #p tag failed: %v", err)
734 }
735
736 if len(evs) == 0 {
737 t.Fatalf("REGRESSION: Expected to find event with #p tag filter, got 0 results")
738 }
739 })
740
741 // Test case 3: Query WITH #e tag
742 t.Run("QueryWithETag", func(t *testing.T) {
743 evs, err := testDB.QueryEvents(ctx, &filter.F{
744 Kinds: kind.NewS(kind.New(30520)),
745 Authors: tag.NewFromBytesSlice(author.Pub()),
746 Tags: tag.NewS(
747 tag.NewFromAny("#d", "test-d-value"),
748 tag.NewFromAny("#e", string(refEventIdHex)),
749 ),
750 })
751 if err != nil {
752 t.Fatalf("Query with #e tag failed: %v", err)
753 }
754
755 if len(evs) == 0 {
756 t.Fatalf("REGRESSION: Expected to find event with #e tag filter, got 0 results")
757 }
758 })
759
760 // Test case 4: Query WITH BOTH #e AND #p tags
761 t.Run("QueryWithBothEAndPTags", func(t *testing.T) {
762 evs, err := testDB.QueryEvents(ctx, &filter.F{
763 Kinds: kind.NewS(kind.New(30520)),
764 Authors: tag.NewFromBytesSlice(author.Pub()),
765 Tags: tag.NewS(
766 tag.NewFromAny("#d", "test-d-value"),
767 tag.NewFromAny("#e", string(refEventIdHex)),
768 tag.NewFromAny("#p", string(refPubkeyHex)),
769 ),
770 })
771 if err != nil {
772 t.Fatalf("Query with both #e and #p tags failed: %v", err)
773 }
774
775 if len(evs) == 0 {
776 t.Fatalf("REGRESSION: Expected to find event with #e and #p tag filters, got 0 results")
777 }
778 })
779
780 t.Logf("✓ Binary tag filter regression tests passed")
781 }
782
783 // TestParameterizedReplaceableEvents tests that parameterized replaceable events (kind 30000+)
784 // are handled correctly - only the newest version should be returned in queries by kind/author/d-tag.
785 func TestParameterizedReplaceableEvents(t *testing.T) {
786 if testDB == nil {
787 t.Skip("Neo4j not available")
788 }
789
790 cleanTestDatabase()
791
792 ctx := context.Background()
793 signer := createTestSignerLocal(t)
794 baseTs := timestamp.Now().V
795
796 // Create older parameterized replaceable event
797 createAndSaveEventLocal(t, ctx, signer, 30000, "Original event",
798 tag.NewS(tag.NewFromAny("d", "test-param")), baseTs-7200) // 2 hours ago
799
800 // Create newer event with same kind/author/d-tag
801 createAndSaveEventLocal(t, ctx, signer, 30000, "Newer event",
802 tag.NewS(tag.NewFromAny("d", "test-param")), baseTs-3600) // 1 hour ago
803
804 // Create newest event with same kind/author/d-tag
805 newestEvent := createAndSaveEventLocal(t, ctx, signer, 30000, "Newest event",
806 tag.NewS(tag.NewFromAny("d", "test-param")), baseTs) // Now
807
808 // Query for events - should only return the newest one
809 evs, err := testDB.QueryEvents(ctx, &filter.F{
810 Kinds: kind.NewS(kind.New(30000)),
811 Authors: tag.NewFromBytesSlice(signer.Pub()),
812 Tags: tag.NewS(tag.NewFromAny("#d", "test-param")),
813 })
814 if err != nil {
815 t.Fatalf("Failed to query parameterized replaceable events: %v", err)
816 }
817
818 // Note: Neo4j backend may or may not automatically deduplicate replaceable events
819 // depending on implementation. The important thing is that the newest is returned first.
820 if len(evs) == 0 {
821 t.Fatal("Expected at least 1 event")
822 }
823
824 // Verify the first (most recent) event is the newest one
825 if hex.Enc(evs[0].ID) != hex.Enc(newestEvent.ID) {
826 t.Logf("Note: Expected newest event first, got different order")
827 }
828
829 t.Logf("✓ Parameterized replaceable events test returned %d events", len(evs))
830 }
831
832 // TestQueryForIds tests the QueryForIds method
833 func TestQueryForIds(t *testing.T) {
834 if testDB == nil {
835 t.Skip("Neo4j not available")
836 }
837
838 cleanTestDatabase()
839
840 ctx := context.Background()
841 signer := createTestSignerLocal(t)
842 baseTs := timestamp.Now().V
843
844 // Create test events
845 ev1 := createAndSaveEventLocal(t, ctx, signer, 1, "Event 1", nil, baseTs)
846 ev2 := createAndSaveEventLocal(t, ctx, signer, 1, "Event 2", nil, baseTs+1)
847 createAndSaveEventLocal(t, ctx, signer, 7, "Reaction", nil, baseTs+2)
848
849 // Query for IDs of kind 1 events
850 idPkTs, err := testDB.QueryForIds(ctx, &filter.F{
851 Kinds: kind.NewS(kind.New(1)),
852 })
853 if err != nil {
854 t.Fatalf("Failed to query for IDs: %v", err)
855 }
856
857 if len(idPkTs) != 2 {
858 t.Fatalf("Expected 2 IDs for kind 1 events, got %d", len(idPkTs))
859 }
860
861 // Verify IDs match our events
862 foundIds := make(map[string]bool)
863 for _, r := range idPkTs {
864 foundIds[hex.Enc(r.Id)] = true
865 }
866
867 if !foundIds[hex.Enc(ev1.ID)] {
868 t.Error("Event 1 ID not found in results")
869 }
870 if !foundIds[hex.Enc(ev2.ID)] {
871 t.Error("Event 2 ID not found in results")
872 }
873
874 t.Logf("✓ QueryForIds returned correct IDs")
875 }
876
877 // TestQueryForSerials tests the QueryForSerials method
878 func TestQueryForSerials(t *testing.T) {
879 if testDB == nil {
880 t.Skip("Neo4j not available")
881 }
882
883 cleanTestDatabase()
884
885 ctx := context.Background()
886 signer := createTestSignerLocal(t)
887 baseTs := timestamp.Now().V
888
889 // Create test events
890 createAndSaveEventLocal(t, ctx, signer, 1, "Event 1", nil, baseTs)
891 createAndSaveEventLocal(t, ctx, signer, 1, "Event 2", nil, baseTs+1)
892 createAndSaveEventLocal(t, ctx, signer, 1, "Event 3", nil, baseTs+2)
893
894 // Query for serials
895 serials, err := testDB.QueryForSerials(ctx, &filter.F{
896 Kinds: kind.NewS(kind.New(1)),
897 })
898 if err != nil {
899 t.Fatalf("Failed to query for serials: %v", err)
900 }
901
902 if len(serials) != 3 {
903 t.Fatalf("Expected 3 serials, got %d", len(serials))
904 }
905
906 t.Logf("✓ QueryForSerials returned %d serials", len(serials))
907 }
908
909 // TestQueryEventsComplex tests complex filter combinations
910 func TestQueryEventsComplex(t *testing.T) {
911 if testDB == nil {
912 t.Skip("Neo4j not available")
913 }
914
915 cleanTestDatabase()
916
917 ctx := context.Background()
918 alice := createTestSignerLocal(t)
919 bob := createTestSignerLocal(t)
920 baseTs := timestamp.Now().V
921
922 // Create diverse set of events
923 createAndSaveEventLocal(t, ctx, alice, 1, "Alice note with bitcoin tag",
924 tag.NewS(tag.NewFromAny("t", "bitcoin")), baseTs)
925 createAndSaveEventLocal(t, ctx, alice, 1, "Alice note with nostr tag",
926 tag.NewS(tag.NewFromAny("t", "nostr")), baseTs+1)
927 createAndSaveEventLocal(t, ctx, alice, 7, "Alice reaction",
928 nil, baseTs+2)
929 createAndSaveEventLocal(t, ctx, bob, 1, "Bob note with bitcoin tag",
930 tag.NewS(tag.NewFromAny("t", "bitcoin")), baseTs+3)
931
932 // Test: kinds + tags (no authors)
933 t.Run("KindsAndTags", func(t *testing.T) {
934 evs, err := testDB.QueryEvents(ctx, &filter.F{
935 Kinds: kind.NewS(kind.New(1)),
936 Tags: tag.NewS(tag.NewFromAny("#t", "bitcoin")),
937 })
938 if err != nil {
939 t.Fatalf("Query failed: %v", err)
940 }
941 if len(evs) != 2 {
942 t.Fatalf("Expected 2 events with kind=1 and #t=bitcoin, got %d", len(evs))
943 }
944 })
945
946 // Test: authors + tags (no kinds)
947 t.Run("AuthorsAndTags", func(t *testing.T) {
948 evs, err := testDB.QueryEvents(ctx, &filter.F{
949 Authors: tag.NewFromBytesSlice(alice.Pub()),
950 Tags: tag.NewS(tag.NewFromAny("#t", "bitcoin")),
951 })
952 if err != nil {
953 t.Fatalf("Query failed: %v", err)
954 }
955 if len(evs) != 1 {
956 t.Fatalf("Expected 1 event from Alice with #t=bitcoin, got %d", len(evs))
957 }
958 })
959
960 // Test: kinds + authors (no tags)
961 t.Run("KindsAndAuthors", func(t *testing.T) {
962 evs, err := testDB.QueryEvents(ctx, &filter.F{
963 Kinds: kind.NewS(kind.New(1)),
964 Authors: tag.NewFromBytesSlice(alice.Pub()),
965 })
966 if err != nil {
967 t.Fatalf("Query failed: %v", err)
968 }
969 if len(evs) != 2 {
970 t.Fatalf("Expected 2 kind=1 events from Alice, got %d", len(evs))
971 }
972 })
973
974 // Test: all three filters
975 t.Run("AllFilters", func(t *testing.T) {
976 evs, err := testDB.QueryEvents(ctx, &filter.F{
977 Kinds: kind.NewS(kind.New(1)),
978 Authors: tag.NewFromBytesSlice(alice.Pub()),
979 Tags: tag.NewS(tag.NewFromAny("#t", "nostr")),
980 })
981 if err != nil {
982 t.Fatalf("Query failed: %v", err)
983 }
984 if len(evs) != 1 {
985 t.Fatalf("Expected 1 event (Alice kind=1 #t=nostr), got %d", len(evs))
986 }
987 })
988
989 t.Logf("✓ Complex filter combination tests passed")
990 }
991
992 // TestQueryEventsMultipleTagTypes tests filtering with multiple different tag types
993 func TestQueryEventsMultipleTagTypes(t *testing.T) {
994 if testDB == nil {
995 t.Skip("Neo4j not available")
996 }
997
998 cleanTestDatabase()
999
1000 ctx := context.Background()
1001 signer := createTestSignerLocal(t)
1002 baseTs := timestamp.Now().V
1003
1004 // Create events with multiple tag types
1005 createAndSaveEventLocal(t, ctx, signer, 30382, "Event with d and client tags",
1006 tag.NewS(
1007 tag.NewFromAny("d", "user-1"),
1008 tag.NewFromAny("client", "app-a"),
1009 ), baseTs)
1010
1011 createAndSaveEventLocal(t, ctx, signer, 30382, "Event with d and different client",
1012 tag.NewS(
1013 tag.NewFromAny("d", "user-2"),
1014 tag.NewFromAny("client", "app-b"),
1015 ), baseTs+1)
1016
1017 createAndSaveEventLocal(t, ctx, signer, 30382, "Event with only d tag",
1018 tag.NewS(
1019 tag.NewFromAny("d", "user-3"),
1020 ), baseTs+2)
1021
1022 // Query with multiple tag types (should AND them together)
1023 evs, err := testDB.QueryEvents(ctx, &filter.F{
1024 Kinds: kind.NewS(kind.New(30382)),
1025 Tags: tag.NewS(
1026 tag.NewFromAny("#d", "user-1", "user-2"),
1027 tag.NewFromAny("#client", "app-a"),
1028 ),
1029 })
1030 if err != nil {
1031 t.Fatalf("Query with multiple tag types failed: %v", err)
1032 }
1033
1034 // Should match only the first event (user-1 with app-a)
1035 if len(evs) != 1 {
1036 t.Fatalf("Expected 1 event matching both #d and #client, got %d", len(evs))
1037 }
1038
1039 dTag := evs[0].Tags.GetFirst([]byte("d"))
1040 if string(dTag.Value()) != "user-1" {
1041 t.Fatalf("Expected d=user-1, got d=%s", dTag.Value())
1042 }
1043
1044 t.Logf("✓ Multiple tag types filter test passed")
1045 }
1046