query-for-ptag-graph_test.go raw

   1  package database
   2  
   3  import (
   4  	"context"
   5  	"testing"
   6  
   7  	"next.orly.dev/pkg/nostr/encoders/event"
   8  	"next.orly.dev/pkg/nostr/encoders/filter"
   9  	"next.orly.dev/pkg/nostr/encoders/hex"
  10  	"next.orly.dev/pkg/nostr/encoders/kind"
  11  	"next.orly.dev/pkg/nostr/encoders/tag"
  12  )
  13  
  14  func TestCanUsePTagGraph(t *testing.T) {
  15  	tests := []struct {
  16  		name     string
  17  		filter   *filter.F
  18  		expected bool
  19  	}{
  20  		{
  21  			name: "filter with p-tags only",
  22  			filter: &filter.F{
  23  				Tags: tag.NewS(
  24  					tag.NewFromAny("p", "0000000000000000000000000000000000000000000000000000000000000001"),
  25  				),
  26  			},
  27  			expected: true,
  28  		},
  29  		{
  30  			name: "filter with p-tags and kinds",
  31  			filter: &filter.F{
  32  				Kinds: kind.NewS(kind.New(1)),
  33  				Tags: tag.NewS(
  34  					tag.NewFromAny("p", "0000000000000000000000000000000000000000000000000000000000000001"),
  35  				),
  36  			},
  37  			expected: true,
  38  		},
  39  		{
  40  			name: "filter with p-tags and authors (should use traditional index)",
  41  			filter: &filter.F{
  42  				Authors: tag.NewFromBytesSlice([]byte("author")),
  43  				Tags: tag.NewS(
  44  					tag.NewFromAny("p", "0000000000000000000000000000000000000000000000000000000000000001"),
  45  				),
  46  			},
  47  			expected: false,
  48  		},
  49  		{
  50  			name: "filter with e-tags only (no p-tags)",
  51  			filter: &filter.F{
  52  				Tags: tag.NewS(
  53  					tag.NewFromAny("e", "someeventid"),
  54  				),
  55  			},
  56  			expected: false,
  57  		},
  58  		{
  59  			name:     "filter with no tags",
  60  			filter:   &filter.F{},
  61  			expected: false,
  62  		},
  63  	}
  64  
  65  	for _, tt := range tests {
  66  		t.Run(tt.name, func(t *testing.T) {
  67  			result := CanUsePTagGraph(tt.filter)
  68  			if result != tt.expected {
  69  				t.Errorf("CanUsePTagGraph() = %v, want %v", result, tt.expected)
  70  			}
  71  		})
  72  	}
  73  }
  74  
  75  func TestQueryPTagGraph(t *testing.T) {
  76  	ctx, cancel := context.WithCancel(context.Background())
  77  	defer cancel()
  78  
  79  	db, err := New(ctx, cancel, t.TempDir(), "info")
  80  	if err != nil {
  81  		t.Fatalf("Failed to create database: %v", err)
  82  	}
  83  	defer db.Close()
  84  
  85  	// Create test events with p-tags
  86  	authorPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
  87  	alicePubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
  88  	bobPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003")
  89  
  90  	// Event 1: kind-1 (text note) mentioning Alice
  91  	eventID1 := make([]byte, 32)
  92  	eventID1[0] = 1
  93  	eventSig1 := make([]byte, 64)
  94  	eventSig1[0] = 1
  95  
  96  	ev1 := &event.E{
  97  		ID:        eventID1,
  98  		Pubkey:    authorPubkey,
  99  		CreatedAt: 1234567890,
 100  		Kind:      1,
 101  		Content:   []byte("Mentioning Alice"),
 102  		Sig:       eventSig1,
 103  		Tags: tag.NewS(
 104  			tag.NewFromAny("p", hex.Enc(alicePubkey)),
 105  		),
 106  	}
 107  
 108  	// Event 2: kind-6 (repost) mentioning Alice
 109  	eventID2 := make([]byte, 32)
 110  	eventID2[0] = 2
 111  	eventSig2 := make([]byte, 64)
 112  	eventSig2[0] = 2
 113  
 114  	ev2 := &event.E{
 115  		ID:        eventID2,
 116  		Pubkey:    authorPubkey,
 117  		CreatedAt: 1234567891,
 118  		Kind:      6,
 119  		Content:   []byte("Reposting Alice"),
 120  		Sig:       eventSig2,
 121  		Tags: tag.NewS(
 122  			tag.NewFromAny("p", hex.Enc(alicePubkey)),
 123  		),
 124  	}
 125  
 126  	// Event 3: kind-1 mentioning Bob
 127  	eventID3 := make([]byte, 32)
 128  	eventID3[0] = 3
 129  	eventSig3 := make([]byte, 64)
 130  	eventSig3[0] = 3
 131  
 132  	ev3 := &event.E{
 133  		ID:        eventID3,
 134  		Pubkey:    authorPubkey,
 135  		CreatedAt: 1234567892,
 136  		Kind:      1,
 137  		Content:   []byte("Mentioning Bob"),
 138  		Sig:       eventSig3,
 139  		Tags: tag.NewS(
 140  			tag.NewFromAny("p", hex.Enc(bobPubkey)),
 141  		),
 142  	}
 143  
 144  	// Save all events
 145  	if _, err := db.SaveEvent(ctx, ev1); err != nil {
 146  		t.Fatalf("Failed to save event 1: %v", err)
 147  	}
 148  	if _, err := db.SaveEvent(ctx, ev2); err != nil {
 149  		t.Fatalf("Failed to save event 2: %v", err)
 150  	}
 151  	if _, err := db.SaveEvent(ctx, ev3); err != nil {
 152  		t.Fatalf("Failed to save event 3: %v", err)
 153  	}
 154  
 155  	// Test 1: Query for all events mentioning Alice
 156  	t.Run("query for Alice mentions", func(t *testing.T) {
 157  		f := &filter.F{
 158  			Tags: tag.NewS(
 159  				tag.NewFromAny("p", hex.Enc(alicePubkey)),
 160  			),
 161  		}
 162  
 163  		sers, err := db.QueryPTagGraph(f)
 164  		if err != nil {
 165  			t.Fatalf("QueryPTagGraph failed: %v", err)
 166  		}
 167  
 168  		if len(sers) != 2 {
 169  			t.Errorf("Expected 2 events mentioning Alice, got %d", len(sers))
 170  		}
 171  		t.Logf("Found %d events mentioning Alice", len(sers))
 172  	})
 173  
 174  	// Test 2: Query for kind-1 events mentioning Alice
 175  	t.Run("query for kind-1 Alice mentions", func(t *testing.T) {
 176  		f := &filter.F{
 177  			Kinds: kind.NewS(kind.New(1)),
 178  			Tags: tag.NewS(
 179  				tag.NewFromAny("p", hex.Enc(alicePubkey)),
 180  			),
 181  		}
 182  
 183  		sers, err := db.QueryPTagGraph(f)
 184  		if err != nil {
 185  			t.Fatalf("QueryPTagGraph failed: %v", err)
 186  		}
 187  
 188  		if len(sers) != 1 {
 189  			t.Errorf("Expected 1 kind-1 event mentioning Alice, got %d", len(sers))
 190  		}
 191  		t.Logf("Found %d kind-1 events mentioning Alice", len(sers))
 192  	})
 193  
 194  	// Test 3: Query for events mentioning Bob
 195  	t.Run("query for Bob mentions", func(t *testing.T) {
 196  		f := &filter.F{
 197  			Tags: tag.NewS(
 198  				tag.NewFromAny("p", hex.Enc(bobPubkey)),
 199  			),
 200  		}
 201  
 202  		sers, err := db.QueryPTagGraph(f)
 203  		if err != nil {
 204  			t.Fatalf("QueryPTagGraph failed: %v", err)
 205  		}
 206  
 207  		if len(sers) != 1 {
 208  			t.Errorf("Expected 1 event mentioning Bob, got %d", len(sers))
 209  		}
 210  		t.Logf("Found %d events mentioning Bob", len(sers))
 211  	})
 212  
 213  	// Test 4: Query for non-existent pubkey
 214  	t.Run("query for non-existent pubkey", func(t *testing.T) {
 215  		nonExistentPubkey := make([]byte, 32)
 216  		for i := range nonExistentPubkey {
 217  			nonExistentPubkey[i] = 0xFF
 218  		}
 219  
 220  		f := &filter.F{
 221  			Tags: tag.NewS(
 222  				tag.NewFromAny("p", hex.Enc(nonExistentPubkey)),
 223  			),
 224  		}
 225  
 226  		sers, err := db.QueryPTagGraph(f)
 227  		if err != nil {
 228  			t.Fatalf("QueryPTagGraph failed: %v", err)
 229  		}
 230  
 231  		if len(sers) != 0 {
 232  			t.Errorf("Expected 0 events for non-existent pubkey, got %d", len(sers))
 233  		}
 234  		t.Logf("Correctly found 0 events for non-existent pubkey")
 235  	})
 236  
 237  	// Test 5: Query for multiple kinds
 238  	t.Run("query for multiple kinds mentioning Alice", func(t *testing.T) {
 239  		f := &filter.F{
 240  			Kinds: kind.NewS(kind.New(1), kind.New(6)),
 241  			Tags: tag.NewS(
 242  				tag.NewFromAny("p", hex.Enc(alicePubkey)),
 243  			),
 244  		}
 245  
 246  		sers, err := db.QueryPTagGraph(f)
 247  		if err != nil {
 248  			t.Fatalf("QueryPTagGraph failed: %v", err)
 249  		}
 250  
 251  		if len(sers) != 2 {
 252  			t.Errorf("Expected 2 events (kind 1 and 6) mentioning Alice, got %d", len(sers))
 253  		}
 254  		t.Logf("Found %d events (kind 1 and 6) mentioning Alice", len(sers))
 255  	})
 256  }
 257  
 258  func TestGetSerialsFromFilterWithPTagOptimization(t *testing.T) {
 259  	ctx, cancel := context.WithCancel(context.Background())
 260  	defer cancel()
 261  
 262  	db, err := New(ctx, cancel, t.TempDir(), "info")
 263  	if err != nil {
 264  		t.Fatalf("Failed to create database: %v", err)
 265  	}
 266  	defer db.Close()
 267  
 268  	// Create test event with p-tag
 269  	authorPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
 270  	alicePubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
 271  
 272  	eventID := make([]byte, 32)
 273  	eventID[0] = 1
 274  	eventSig := make([]byte, 64)
 275  	eventSig[0] = 1
 276  
 277  	ev := &event.E{
 278  		ID:        eventID,
 279  		Pubkey:    authorPubkey,
 280  		CreatedAt: 1234567890,
 281  		Kind:      1,
 282  		Content:   []byte("Mentioning Alice"),
 283  		Sig:       eventSig,
 284  		Tags: tag.NewS(
 285  			tag.NewFromAny("p", hex.Enc(alicePubkey)),
 286  		),
 287  	}
 288  
 289  	if _, err := db.SaveEvent(ctx, ev); err != nil {
 290  		t.Fatalf("Failed to save event: %v", err)
 291  	}
 292  
 293  	// Test that GetSerialsFromFilter uses the p-tag graph optimization
 294  	f := &filter.F{
 295  		Kinds: kind.NewS(kind.New(1)),
 296  		Tags: tag.NewS(
 297  			tag.NewFromAny("p", hex.Enc(alicePubkey)),
 298  		),
 299  	}
 300  
 301  	sers, err := db.GetSerialsFromFilter(f)
 302  	if err != nil {
 303  		t.Fatalf("GetSerialsFromFilter failed: %v", err)
 304  	}
 305  
 306  	if len(sers) != 1 {
 307  		t.Errorf("Expected 1 event, got %d", len(sers))
 308  	}
 309  
 310  	t.Logf("GetSerialsFromFilter successfully used p-tag graph optimization, found %d events", len(sers))
 311  }
 312