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 := &timestamp.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