Refactor Neo4j tests and improve tag handling in Cypher
Replaces outdated Neo4j test setup with a robust TestMain, shared test database, and utility functions for test data and migrations. Improves Cypher generation for processing e-tags, p-tags, and other tags to ensure compliance with Neo4j syntax. Added integration test script and updated benchmark reports for Badger backend.
This commit is contained in:
314
pkg/neo4j/query_events_test.go
Normal file
314
pkg/neo4j/query_events_test.go
Normal file
@@ -0,0 +1,314 @@
|
||||
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 slice
|
||||
t.Run("EmptyIds", func(t *testing.T) {
|
||||
f := &filter.F{
|
||||
Ids: &tag.S{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 slice
|
||||
t.Run("EmptyAuthors", func(t *testing.T) {
|
||||
f := &filter.F{
|
||||
Authors: &tag.S{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.S{K: []*kind.T{}},
|
||||
}
|
||||
_, 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 := 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.S{T: [][]byte{}},
|
||||
Authors: &tag.S{T: [][]byte{}},
|
||||
Kinds: &kind.S{K: []*kind.T{}},
|
||||
}
|
||||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user