Files
next.orly.dev/pkg/neo4j/query_events_test.go
mleku 95271cbc81
Some checks failed
Go / build-and-release (push) Has been cancelled
Add Neo4j integration tests and query rate-limiting logic
Introduce comprehensive integration tests for Neo4j bug fixes covering batching, event relationships, and processing logic. Add rate-limiting to Neo4j queries using semaphores and retry policies to prevent authentication rate limiting and connection exhaustion, ensuring system stability under load.
2025-12-07 00:07:25 +00:00

321 lines
8.6 KiB
Go

//go:build integration
// +build integration
// NOTE: This file requires updates to match the current nostr library types.
// The filter/tag/kind types have changed since this test was written.
package neo4j
import (
"context"
"testing"
"git.mleku.dev/mleku/nostr/encoders/filter"
"git.mleku.dev/mleku/nostr/encoders/kind"
"git.mleku.dev/mleku/nostr/encoders/tag"
"git.mleku.dev/mleku/nostr/encoders/timestamp"
)
// Valid test pubkeys and event IDs (64-character lowercase hex)
const (
validPubkey1 = "0000000000000000000000000000000000000000000000000000000000000001"
validPubkey2 = "0000000000000000000000000000000000000000000000000000000000000002"
validPubkey3 = "0000000000000000000000000000000000000000000000000000000000000003"
validEventID1 = "1111111111111111111111111111111111111111111111111111111111111111"
validEventID2 = "2222222222222222222222222222222222222222222222222222222222222222"
validEventID3 = "3333333333333333333333333333333333333333333333333333333333333333"
)
// TestQueryEventsWithNilFilter tests that QueryEvents handles nil filter fields gracefully
// This test covers the nil pointer fix in query-events.go
func TestQueryEventsWithNilFilter(t *testing.T) {
if testDB == nil {
t.Skip("Neo4j not available")
}
// Clean up before test
cleanTestDatabase()
// Setup some test events
setupTestEvent(t, validEventID1, validPubkey1, 1, "[]")
setupTestEvent(t, validEventID2, validPubkey2, 1, "[]")
ctx := context.Background()
// Test 1: Completely empty filter (all nil fields)
t.Run("EmptyFilter", func(t *testing.T) {
f := &filter.F{}
events, err := testDB.QueryEvents(ctx, f)
if err != nil {
t.Fatalf("QueryEvents with empty filter should not panic: %v", err)
}
if len(events) == 0 {
t.Error("Expected to find events with empty filter")
}
})
// Test 2: Filter with nil Ids
t.Run("NilIds", func(t *testing.T) {
f := &filter.F{
Ids: nil, // Explicitly nil
}
_, err := testDB.QueryEvents(ctx, f)
if err != nil {
t.Fatalf("QueryEvents with nil Ids should not panic: %v", err)
}
})
// Test 3: Filter with nil Authors
t.Run("NilAuthors", func(t *testing.T) {
f := &filter.F{
Authors: nil, // Explicitly nil
}
_, err := testDB.QueryEvents(ctx, f)
if err != nil {
t.Fatalf("QueryEvents with nil Authors should not panic: %v", err)
}
})
// Test 4: Filter with nil Kinds
t.Run("NilKinds", func(t *testing.T) {
f := &filter.F{
Kinds: nil, // Explicitly nil
}
_, err := testDB.QueryEvents(ctx, f)
if err != nil {
t.Fatalf("QueryEvents with nil Kinds should not panic: %v", err)
}
})
// Test 5: Filter with empty Ids (using tag with empty slice)
t.Run("EmptyIds", func(t *testing.T) {
f := &filter.F{
Ids: &tag.T{T: [][]byte{}},
}
_, err := testDB.QueryEvents(ctx, f)
if err != nil {
t.Fatalf("QueryEvents with empty Ids should not panic: %v", err)
}
})
// Test 6: Filter with empty Authors (using tag with empty slice)
t.Run("EmptyAuthors", func(t *testing.T) {
f := &filter.F{
Authors: &tag.T{T: [][]byte{}},
}
_, err := testDB.QueryEvents(ctx, f)
if err != nil {
t.Fatalf("QueryEvents with empty Authors should not panic: %v", err)
}
})
// Test 7: Filter with empty Kinds slice
t.Run("EmptyKinds", func(t *testing.T) {
f := &filter.F{
Kinds: kind.NewS(),
}
_, err := testDB.QueryEvents(ctx, f)
if err != nil {
t.Fatalf("QueryEvents with empty Kinds should not panic: %v", err)
}
})
}
// TestQueryEventsWithValidFilters tests that QueryEvents works correctly with valid filters
func TestQueryEventsWithValidFilters(t *testing.T) {
if testDB == nil {
t.Skip("Neo4j not available")
}
// Clean up before test
cleanTestDatabase()
// Setup test events
setupTestEvent(t, validEventID1, validPubkey1, 1, "[]")
setupTestEvent(t, validEventID2, validPubkey2, 3, "[]")
setupTestEvent(t, validEventID3, validPubkey1, 1, "[]")
ctx := context.Background()
// Test 1: Filter by ID
t.Run("FilterByID", func(t *testing.T) {
f := &filter.F{
Ids: tag.NewFromBytesSlice([]byte(validEventID1)),
}
events, err := testDB.QueryEvents(ctx, f)
if err != nil {
t.Fatalf("QueryEvents failed: %v", err)
}
if len(events) != 1 {
t.Errorf("Expected 1 event, got %d", len(events))
}
})
// Test 2: Filter by Author
t.Run("FilterByAuthor", func(t *testing.T) {
f := &filter.F{
Authors: tag.NewFromBytesSlice([]byte(validPubkey1)),
}
events, err := testDB.QueryEvents(ctx, f)
if err != nil {
t.Fatalf("QueryEvents failed: %v", err)
}
if len(events) != 2 {
t.Errorf("Expected 2 events from pubkey1, got %d", len(events))
}
})
// Test 3: Filter by Kind
t.Run("FilterByKind", func(t *testing.T) {
f := &filter.F{
Kinds: kind.NewS(kind.New(1)),
}
events, err := testDB.QueryEvents(ctx, f)
if err != nil {
t.Fatalf("QueryEvents failed: %v", err)
}
if len(events) != 2 {
t.Errorf("Expected 2 kind-1 events, got %d", len(events))
}
})
// Test 4: Combined filters (kind + author)
t.Run("FilterByKindAndAuthor", func(t *testing.T) {
f := &filter.F{
Kinds: kind.NewS(kind.New(1)),
Authors: tag.NewFromBytesSlice([]byte(validPubkey1)),
}
events, err := testDB.QueryEvents(ctx, f)
if err != nil {
t.Fatalf("QueryEvents failed: %v", err)
}
if len(events) != 2 {
t.Errorf("Expected 2 kind-1 events from pubkey1, got %d", len(events))
}
})
// Test 5: Filter with limit
t.Run("FilterWithLimit", func(t *testing.T) {
limit := uint(1)
f := &filter.F{
Kinds: kind.NewS(kind.New(1)),
Limit: &limit,
}
events, err := testDB.QueryEvents(ctx, f)
if err != nil {
t.Fatalf("QueryEvents failed: %v", err)
}
if len(events) != 1 {
t.Errorf("Expected 1 event due to limit, got %d", len(events))
}
})
}
// TestBuildCypherQueryWithNilFields tests the buildCypherQuery function with nil fields
func TestBuildCypherQueryWithNilFields(t *testing.T) {
if testDB == nil {
t.Skip("Neo4j not available")
}
// Test that buildCypherQuery doesn't panic with nil fields
t.Run("AllNilFields", func(t *testing.T) {
f := &filter.F{
Ids: nil,
Authors: nil,
Kinds: nil,
Since: nil,
Until: nil,
Tags: nil,
Limit: nil,
}
cypher, params := testDB.buildCypherQuery(f, false)
if cypher == "" {
t.Error("Expected non-empty Cypher query")
}
if params == nil {
t.Error("Expected non-nil params map")
}
})
// Test with empty slices
t.Run("EmptySlices", func(t *testing.T) {
f := &filter.F{
Ids: &tag.T{T: [][]byte{}},
Authors: &tag.T{T: [][]byte{}},
Kinds: kind.NewS(),
}
cypher, params := testDB.buildCypherQuery(f, false)
if cypher == "" {
t.Error("Expected non-empty Cypher query")
}
if params == nil {
t.Error("Expected non-nil params map")
}
})
// Test with time filters
t.Run("TimeFilters", func(t *testing.T) {
since := timestamp.Now()
until := timestamp.Now()
f := &filter.F{
Since: since,
Until: until,
}
cypher, params := testDB.buildCypherQuery(f, false)
if _, ok := params["since"]; !ok {
t.Error("Expected 'since' param")
}
if _, ok := params["until"]; !ok {
t.Error("Expected 'until' param")
}
_ = cypher
})
}
// TestQueryEventsUppercaseHexNormalization tests that uppercase hex in filters is normalized
func TestQueryEventsUppercaseHexNormalization(t *testing.T) {
if testDB == nil {
t.Skip("Neo4j not available")
}
// Clean up before test
cleanTestDatabase()
// Setup test event with lowercase pubkey (as Neo4j stores)
lowercasePubkey := "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
lowercaseEventID := "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
setupTestEvent(t, lowercaseEventID, lowercasePubkey, 1, "[]")
ctx := context.Background()
// Test query with uppercase pubkey - should be normalized and still match
t.Run("UppercaseAuthor", func(t *testing.T) {
uppercasePubkey := "ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789"
f := &filter.F{
Authors: tag.NewFromBytesSlice([]byte(uppercasePubkey)),
}
events, err := testDB.QueryEvents(ctx, f)
if err != nil {
t.Fatalf("QueryEvents failed: %v", err)
}
if len(events) != 1 {
t.Errorf("Expected to find 1 event with uppercase pubkey filter, got %d", len(events))
}
})
// Test query with uppercase event ID - should be normalized and still match
t.Run("UppercaseEventID", func(t *testing.T) {
uppercaseEventID := "FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210"
f := &filter.F{
Ids: tag.NewFromBytesSlice([]byte(uppercaseEventID)),
}
events, err := testDB.QueryEvents(ctx, f)
if err != nil {
t.Fatalf("QueryEvents failed: %v", err)
}
if len(events) != 1 {
t.Errorf("Expected to find 1 event with uppercase ID filter, got %d", len(events))
}
})
}