Some checks failed
Go / build-and-release (push) Has been cancelled
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.
321 lines
8.6 KiB
Go
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))
|
|
}
|
|
})
|
|
}
|