query_events_test.go raw

   1  //go:build integration
   2  // +build integration
   3  
   4  // NOTE: This file requires updates to match the current nostr library types.
   5  // The filter/tag/kind types have changed since this test was written.
   6  
   7  package neo4j
   8  
   9  import (
  10  	"context"
  11  	"testing"
  12  
  13  	"next.orly.dev/pkg/nostr/encoders/filter"
  14  	"next.orly.dev/pkg/nostr/encoders/kind"
  15  	"next.orly.dev/pkg/nostr/encoders/tag"
  16  	"next.orly.dev/pkg/nostr/encoders/timestamp"
  17  )
  18  
  19  // Valid test pubkeys and event IDs (64-character lowercase hex)
  20  const (
  21  	validPubkey1 = "0000000000000000000000000000000000000000000000000000000000000001"
  22  	validPubkey2 = "0000000000000000000000000000000000000000000000000000000000000002"
  23  	validPubkey3 = "0000000000000000000000000000000000000000000000000000000000000003"
  24  	validEventID1 = "1111111111111111111111111111111111111111111111111111111111111111"
  25  	validEventID2 = "2222222222222222222222222222222222222222222222222222222222222222"
  26  	validEventID3 = "3333333333333333333333333333333333333333333333333333333333333333"
  27  )
  28  
  29  // TestQueryEventsWithNilFilter tests that QueryEvents handles nil filter fields gracefully
  30  // This test covers the nil pointer fix in query-events.go
  31  func TestQueryEventsWithNilFilter(t *testing.T) {
  32  	if testDB == nil {
  33  		t.Skip("Neo4j not available")
  34  	}
  35  
  36  	// Clean up before test
  37  	cleanTestDatabase()
  38  
  39  	// Setup some test events
  40  	setupTestEvent(t, validEventID1, validPubkey1, 1, "[]")
  41  	setupTestEvent(t, validEventID2, validPubkey2, 1, "[]")
  42  
  43  	ctx := context.Background()
  44  
  45  	// Test 1: Completely empty filter (all nil fields)
  46  	t.Run("EmptyFilter", func(t *testing.T) {
  47  		f := &filter.F{}
  48  		events, err := testDB.QueryEvents(ctx, f)
  49  		if err != nil {
  50  			t.Fatalf("QueryEvents with empty filter should not panic: %v", err)
  51  		}
  52  		if len(events) == 0 {
  53  			t.Error("Expected to find events with empty filter")
  54  		}
  55  	})
  56  
  57  	// Test 2: Filter with nil Ids
  58  	t.Run("NilIds", func(t *testing.T) {
  59  		f := &filter.F{
  60  			Ids: nil, // Explicitly nil
  61  		}
  62  		_, err := testDB.QueryEvents(ctx, f)
  63  		if err != nil {
  64  			t.Fatalf("QueryEvents with nil Ids should not panic: %v", err)
  65  		}
  66  	})
  67  
  68  	// Test 3: Filter with nil Authors
  69  	t.Run("NilAuthors", func(t *testing.T) {
  70  		f := &filter.F{
  71  			Authors: nil, // Explicitly nil
  72  		}
  73  		_, err := testDB.QueryEvents(ctx, f)
  74  		if err != nil {
  75  			t.Fatalf("QueryEvents with nil Authors should not panic: %v", err)
  76  		}
  77  	})
  78  
  79  	// Test 4: Filter with nil Kinds
  80  	t.Run("NilKinds", func(t *testing.T) {
  81  		f := &filter.F{
  82  			Kinds: nil, // Explicitly nil
  83  		}
  84  		_, err := testDB.QueryEvents(ctx, f)
  85  		if err != nil {
  86  			t.Fatalf("QueryEvents with nil Kinds should not panic: %v", err)
  87  		}
  88  	})
  89  
  90  	// Test 5: Filter with empty Ids (using tag with empty slice)
  91  	t.Run("EmptyIds", func(t *testing.T) {
  92  		f := &filter.F{
  93  			Ids: &tag.T{T: [][]byte{}},
  94  		}
  95  		_, err := testDB.QueryEvents(ctx, f)
  96  		if err != nil {
  97  			t.Fatalf("QueryEvents with empty Ids should not panic: %v", err)
  98  		}
  99  	})
 100  
 101  	// Test 6: Filter with empty Authors (using tag with empty slice)
 102  	t.Run("EmptyAuthors", func(t *testing.T) {
 103  		f := &filter.F{
 104  			Authors: &tag.T{T: [][]byte{}},
 105  		}
 106  		_, err := testDB.QueryEvents(ctx, f)
 107  		if err != nil {
 108  			t.Fatalf("QueryEvents with empty Authors should not panic: %v", err)
 109  		}
 110  	})
 111  
 112  	// Test 7: Filter with empty Kinds slice
 113  	t.Run("EmptyKinds", func(t *testing.T) {
 114  		f := &filter.F{
 115  			Kinds: kind.NewS(),
 116  		}
 117  		_, err := testDB.QueryEvents(ctx, f)
 118  		if err != nil {
 119  			t.Fatalf("QueryEvents with empty Kinds should not panic: %v", err)
 120  		}
 121  	})
 122  }
 123  
 124  // TestQueryEventsWithValidFilters tests that QueryEvents works correctly with valid filters
 125  func TestQueryEventsWithValidFilters(t *testing.T) {
 126  	if testDB == nil {
 127  		t.Skip("Neo4j not available")
 128  	}
 129  
 130  	// Clean up before test
 131  	cleanTestDatabase()
 132  
 133  	// Setup test events
 134  	setupTestEvent(t, validEventID1, validPubkey1, 1, "[]")
 135  	setupTestEvent(t, validEventID2, validPubkey2, 3, "[]")
 136  	setupTestEvent(t, validEventID3, validPubkey1, 1, "[]")
 137  
 138  	ctx := context.Background()
 139  
 140  	// Test 1: Filter by ID
 141  	t.Run("FilterByID", func(t *testing.T) {
 142  		f := &filter.F{
 143  			Ids: tag.NewFromBytesSlice([]byte(validEventID1)),
 144  		}
 145  		events, err := testDB.QueryEvents(ctx, f)
 146  		if err != nil {
 147  			t.Fatalf("QueryEvents failed: %v", err)
 148  		}
 149  		if len(events) != 1 {
 150  			t.Errorf("Expected 1 event, got %d", len(events))
 151  		}
 152  	})
 153  
 154  	// Test 2: Filter by Author
 155  	t.Run("FilterByAuthor", func(t *testing.T) {
 156  		f := &filter.F{
 157  			Authors: tag.NewFromBytesSlice([]byte(validPubkey1)),
 158  		}
 159  		events, err := testDB.QueryEvents(ctx, f)
 160  		if err != nil {
 161  			t.Fatalf("QueryEvents failed: %v", err)
 162  		}
 163  		if len(events) != 2 {
 164  			t.Errorf("Expected 2 events from pubkey1, got %d", len(events))
 165  		}
 166  	})
 167  
 168  	// Test 3: Filter by Kind
 169  	t.Run("FilterByKind", func(t *testing.T) {
 170  		f := &filter.F{
 171  			Kinds: kind.NewS(kind.New(1)),
 172  		}
 173  		events, err := testDB.QueryEvents(ctx, f)
 174  		if err != nil {
 175  			t.Fatalf("QueryEvents failed: %v", err)
 176  		}
 177  		if len(events) != 2 {
 178  			t.Errorf("Expected 2 kind-1 events, got %d", len(events))
 179  		}
 180  	})
 181  
 182  	// Test 4: Combined filters (kind + author)
 183  	t.Run("FilterByKindAndAuthor", func(t *testing.T) {
 184  		f := &filter.F{
 185  			Kinds:   kind.NewS(kind.New(1)),
 186  			Authors: tag.NewFromBytesSlice([]byte(validPubkey1)),
 187  		}
 188  		events, err := testDB.QueryEvents(ctx, f)
 189  		if err != nil {
 190  			t.Fatalf("QueryEvents failed: %v", err)
 191  		}
 192  		if len(events) != 2 {
 193  			t.Errorf("Expected 2 kind-1 events from pubkey1, got %d", len(events))
 194  		}
 195  	})
 196  
 197  	// Test 5: Filter with limit
 198  	t.Run("FilterWithLimit", func(t *testing.T) {
 199  		limit := uint(1)
 200  		f := &filter.F{
 201  			Kinds: kind.NewS(kind.New(1)),
 202  			Limit: &limit,
 203  		}
 204  		events, err := testDB.QueryEvents(ctx, f)
 205  		if err != nil {
 206  			t.Fatalf("QueryEvents failed: %v", err)
 207  		}
 208  		if len(events) != 1 {
 209  			t.Errorf("Expected 1 event due to limit, got %d", len(events))
 210  		}
 211  	})
 212  }
 213  
 214  // TestBuildCypherQueryWithNilFields tests the buildCypherQuery function with nil fields
 215  func TestBuildCypherQueryWithNilFields(t *testing.T) {
 216  	if testDB == nil {
 217  		t.Skip("Neo4j not available")
 218  	}
 219  
 220  	// Test that buildCypherQuery doesn't panic with nil fields
 221  	t.Run("AllNilFields", func(t *testing.T) {
 222  		f := &filter.F{
 223  			Ids:     nil,
 224  			Authors: nil,
 225  			Kinds:   nil,
 226  			Since:   nil,
 227  			Until:   nil,
 228  			Tags:    nil,
 229  			Limit:   nil,
 230  		}
 231  		cypher, params := testDB.buildCypherQuery(f, false)
 232  		if cypher == "" {
 233  			t.Error("Expected non-empty Cypher query")
 234  		}
 235  		if params == nil {
 236  			t.Error("Expected non-nil params map")
 237  		}
 238  	})
 239  
 240  	// Test with empty slices
 241  	t.Run("EmptySlices", func(t *testing.T) {
 242  		f := &filter.F{
 243  			Ids:     &tag.T{T: [][]byte{}},
 244  			Authors: &tag.T{T: [][]byte{}},
 245  			Kinds:   kind.NewS(),
 246  		}
 247  		cypher, params := testDB.buildCypherQuery(f, false)
 248  		if cypher == "" {
 249  			t.Error("Expected non-empty Cypher query")
 250  		}
 251  		if params == nil {
 252  			t.Error("Expected non-nil params map")
 253  		}
 254  	})
 255  
 256  	// Test with time filters
 257  	t.Run("TimeFilters", func(t *testing.T) {
 258  		since := timestamp.Now()
 259  		until := timestamp.Now()
 260  		f := &filter.F{
 261  			Since: since,
 262  			Until: until,
 263  		}
 264  		cypher, params := testDB.buildCypherQuery(f, false)
 265  		if _, ok := params["since"]; !ok {
 266  			t.Error("Expected 'since' param")
 267  		}
 268  		if _, ok := params["until"]; !ok {
 269  			t.Error("Expected 'until' param")
 270  		}
 271  		_ = cypher
 272  	})
 273  }
 274  
 275  // TestQueryEventsUppercaseHexNormalization tests that uppercase hex in filters is normalized
 276  func TestQueryEventsUppercaseHexNormalization(t *testing.T) {
 277  	if testDB == nil {
 278  		t.Skip("Neo4j not available")
 279  	}
 280  
 281  	// Clean up before test
 282  	cleanTestDatabase()
 283  
 284  	// Setup test event with lowercase pubkey (as Neo4j stores)
 285  	lowercasePubkey := "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
 286  	lowercaseEventID := "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
 287  	setupTestEvent(t, lowercaseEventID, lowercasePubkey, 1, "[]")
 288  
 289  	ctx := context.Background()
 290  
 291  	// Test query with uppercase pubkey - should be normalized and still match
 292  	t.Run("UppercaseAuthor", func(t *testing.T) {
 293  		uppercasePubkey := "ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789"
 294  		f := &filter.F{
 295  			Authors: tag.NewFromBytesSlice([]byte(uppercasePubkey)),
 296  		}
 297  		events, err := testDB.QueryEvents(ctx, f)
 298  		if err != nil {
 299  			t.Fatalf("QueryEvents failed: %v", err)
 300  		}
 301  		if len(events) != 1 {
 302  			t.Errorf("Expected to find 1 event with uppercase pubkey filter, got %d", len(events))
 303  		}
 304  	})
 305  
 306  	// Test query with uppercase event ID - should be normalized and still match
 307  	t.Run("UppercaseEventID", func(t *testing.T) {
 308  		uppercaseEventID := "FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210"
 309  		f := &filter.F{
 310  			Ids: tag.NewFromBytesSlice([]byte(uppercaseEventID)),
 311  		}
 312  		events, err := testDB.QueryEvents(ctx, f)
 313  		if err != nil {
 314  			t.Fatalf("QueryEvents failed: %v", err)
 315  		}
 316  		if len(events) != 1 {
 317  			t.Errorf("Expected to find 1 event with uppercase ID filter, got %d", len(events))
 318  		}
 319  	})
 320  }
 321