pubkey-graph_test.go raw

   1  package database
   2  
   3  import (
   4  	"context"
   5  	"testing"
   6  
   7  	"github.com/dgraph-io/badger/v4"
   8  	"next.orly.dev/pkg/database/indexes"
   9  	"next.orly.dev/pkg/database/indexes/types"
  10  	"next.orly.dev/pkg/nostr/encoders/event"
  11  	"next.orly.dev/pkg/nostr/encoders/hex"
  12  	"next.orly.dev/pkg/nostr/encoders/tag"
  13  )
  14  
  15  func TestPubkeySerialAssignment(t *testing.T) {
  16  	ctx, cancel := context.WithCancel(context.Background())
  17  	defer cancel()
  18  
  19  	db, err := New(ctx, cancel, t.TempDir(), "info")
  20  	if err != nil {
  21  		t.Fatalf("Failed to create database: %v", err)
  22  	}
  23  	defer db.Close()
  24  
  25  	// Create a test pubkey
  26  	pubkey1 := make([]byte, 32)
  27  	for i := range pubkey1 {
  28  		pubkey1[i] = byte(i)
  29  	}
  30  
  31  	// Get or create serial for the first time
  32  	t.Logf("First call: GetOrCreatePubkeySerial for pubkey %s", hex.Enc(pubkey1))
  33  	ser1, err := db.GetOrCreatePubkeySerial(pubkey1)
  34  	if err != nil {
  35  		t.Fatalf("Failed to get or create pubkey serial: %v", err)
  36  	}
  37  
  38  	if ser1 == nil {
  39  		t.Fatal("Serial should not be nil")
  40  	}
  41  	t.Logf("First call returned serial: %d", ser1.Get())
  42  
  43  	// Debug: List all keys in database
  44  	var keyCount int
  45  	db.View(func(txn *badger.Txn) error {
  46  		it := txn.NewIterator(badger.DefaultIteratorOptions)
  47  		defer it.Close()
  48  		for it.Rewind(); it.Valid(); it.Next() {
  49  			key := it.Item().KeyCopy(nil)
  50  			t.Logf("Found key: %s (len=%d)", hex.Enc(key), len(key))
  51  			keyCount++
  52  			if keyCount > 20 {
  53  				break // Limit output
  54  			}
  55  		}
  56  		return nil
  57  	})
  58  	t.Logf("Total keys found (first 20): %d", keyCount)
  59  
  60  	// Debug: what prefix should we be looking for?
  61  	pubHash := new(types.PubHash)
  62  	pubHash.FromPubkey(pubkey1)
  63  	expectedPrefix := []byte(indexes.PubkeySerialPrefix)
  64  	t.Logf("Expected PubkeySerial prefix: %s = %s", string(expectedPrefix), hex.Enc(expectedPrefix))
  65  
  66  	// Try direct lookup
  67  	t.Logf("Direct lookup: GetPubkeySerial for same pubkey")
  68  	serDirect, err := db.GetPubkeySerial(pubkey1)
  69  	if err != nil {
  70  		t.Logf("Direct lookup failed: %v", err)
  71  	} else {
  72  		t.Logf("Direct lookup returned serial: %d", serDirect.Get())
  73  	}
  74  
  75  	// Get the same pubkey again - should return the same serial
  76  	t.Logf("Second call: GetOrCreatePubkeySerial for same pubkey")
  77  	ser2, err := db.GetOrCreatePubkeySerial(pubkey1)
  78  	if err != nil {
  79  		t.Fatalf("Failed to get existing pubkey serial: %v", err)
  80  	}
  81  	t.Logf("Second call returned serial: %d", ser2.Get())
  82  
  83  	if ser1.Get() != ser2.Get() {
  84  		t.Errorf("Expected same serial, got %d and %d", ser1.Get(), ser2.Get())
  85  	}
  86  
  87  	// Create a different pubkey
  88  	pubkey2 := make([]byte, 32)
  89  	for i := range pubkey2 {
  90  		pubkey2[i] = byte(i + 100)
  91  	}
  92  
  93  	ser3, err := db.GetOrCreatePubkeySerial(pubkey2)
  94  	if err != nil {
  95  		t.Fatalf("Failed to get or create second pubkey serial: %v", err)
  96  	}
  97  
  98  	if ser3.Get() == ser1.Get() {
  99  		t.Error("Different pubkeys should have different serials")
 100  	}
 101  
 102  	// Test reverse lookup: serial -> pubkey
 103  	retrievedPubkey1, err := db.GetPubkeyBySerial(ser1)
 104  	if err != nil {
 105  		t.Fatalf("Failed to get pubkey by serial: %v", err)
 106  	}
 107  
 108  	if hex.Enc(retrievedPubkey1) != hex.Enc(pubkey1) {
 109  		t.Errorf("Retrieved pubkey doesn't match. Expected %s, got %s",
 110  			hex.Enc(pubkey1), hex.Enc(retrievedPubkey1))
 111  	}
 112  }
 113  
 114  func TestEventPubkeyGraph(t *testing.T) {
 115  	ctx, cancel := context.WithCancel(context.Background())
 116  	defer cancel()
 117  
 118  	db, err := New(ctx, cancel, t.TempDir(), "info")
 119  	if err != nil {
 120  		t.Fatalf("Failed to create database: %v", err)
 121  	}
 122  	defer db.Close()
 123  
 124  	// Create test event with author and p-tags
 125  	authorPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
 126  	pTagPubkey1, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
 127  	pTagPubkey2, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003")
 128  
 129  	eventID := make([]byte, 32)
 130  	eventID[0] = 1
 131  	eventSig := make([]byte, 64)
 132  	eventSig[0] = 1
 133  
 134  	// Create a valid e-tag event ID (32 bytes = 64 hex chars)
 135  	eTagEventID := make([]byte, 32)
 136  	eTagEventID[0] = 0xAB
 137  
 138  	ev := &event.E{
 139  		ID:        eventID,
 140  		Pubkey:    authorPubkey,
 141  		CreatedAt: 1234567890,
 142  		Kind:      1, // text note
 143  		Content:   []byte("Test event with p-tags"),
 144  		Sig:       eventSig,
 145  		Tags: tag.NewS(
 146  			tag.NewFromAny("p", hex.Enc(pTagPubkey1)),
 147  			tag.NewFromAny("p", hex.Enc(pTagPubkey2)),
 148  			tag.NewFromAny("e", hex.Enc(eTagEventID)),
 149  		),
 150  	}
 151  
 152  	// Save the event - this should create pubkey serials and graph edges
 153  	_, err = db.SaveEvent(ctx, ev)
 154  	if err != nil {
 155  		t.Fatalf("Failed to save event: %v", err)
 156  	}
 157  
 158  	// Verify that pubkey serials were created
 159  	authorSerial, err := db.GetPubkeySerial(authorPubkey)
 160  	if err != nil {
 161  		t.Fatalf("Failed to get author pubkey serial: %v", err)
 162  	}
 163  	if authorSerial == nil {
 164  		t.Fatal("Author serial should not be nil")
 165  	}
 166  
 167  	pTag1Serial, err := db.GetPubkeySerial(pTagPubkey1)
 168  	if err != nil {
 169  		t.Fatalf("Failed to get p-tag1 pubkey serial: %v", err)
 170  	}
 171  	if pTag1Serial == nil {
 172  		t.Fatal("P-tag1 serial should not be nil")
 173  	}
 174  
 175  	pTag2Serial, err := db.GetPubkeySerial(pTagPubkey2)
 176  	if err != nil {
 177  		t.Fatalf("Failed to get p-tag2 pubkey serial: %v", err)
 178  	}
 179  	if pTag2Serial == nil {
 180  		t.Fatal("P-tag2 serial should not be nil")
 181  	}
 182  
 183  	// Verify all three pubkeys have different serials
 184  	if authorSerial.Get() == pTag1Serial.Get() || authorSerial.Get() == pTag2Serial.Get() || pTag1Serial.Get() == pTag2Serial.Get() {
 185  		t.Error("All pubkey serials should be unique")
 186  	}
 187  
 188  	t.Logf("Event saved successfully with graph edges:")
 189  	t.Logf("  Author serial: %d", authorSerial.Get())
 190  	t.Logf("  P-tag1 serial: %d", pTag1Serial.Get())
 191  	t.Logf("  P-tag2 serial: %d", pTag2Serial.Get())
 192  }
 193  
 194  func TestMultipleEventsWithSamePubkeys(t *testing.T) {
 195  	ctx, cancel := context.WithCancel(context.Background())
 196  	defer cancel()
 197  
 198  	db, err := New(ctx, cancel, t.TempDir(), "info")
 199  	if err != nil {
 200  		t.Fatalf("Failed to create database: %v", err)
 201  	}
 202  	defer db.Close()
 203  
 204  	// Create two events from the same author mentioning the same person
 205  	authorPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
 206  	pTagPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
 207  
 208  	eventID1 := make([]byte, 32)
 209  	eventID1[0] = 1
 210  	eventSig1 := make([]byte, 64)
 211  	eventSig1[0] = 1
 212  
 213  	ev1 := &event.E{
 214  		ID:        eventID1,
 215  		Pubkey:    authorPubkey,
 216  		CreatedAt: 1234567890,
 217  		Kind:      1,
 218  		Content:   []byte("First event"),
 219  		Sig:       eventSig1,
 220  		Tags: tag.NewS(
 221  			tag.NewFromAny("p", hex.Enc(pTagPubkey)),
 222  		),
 223  	}
 224  
 225  	eventID2 := make([]byte, 32)
 226  	eventID2[0] = 2
 227  	eventSig2 := make([]byte, 64)
 228  	eventSig2[0] = 2
 229  
 230  	ev2 := &event.E{
 231  		ID:        eventID2,
 232  		Pubkey:    authorPubkey,
 233  		CreatedAt: 1234567891,
 234  		Kind:      1,
 235  		Content:   []byte("Second event"),
 236  		Sig:       eventSig2,
 237  		Tags: tag.NewS(
 238  			tag.NewFromAny("p", hex.Enc(pTagPubkey)),
 239  		),
 240  	}
 241  
 242  	// Save both events
 243  	_, err = db.SaveEvent(ctx, ev1)
 244  	if err != nil {
 245  		t.Fatalf("Failed to save event 1: %v", err)
 246  	}
 247  
 248  	_, err = db.SaveEvent(ctx, ev2)
 249  	if err != nil {
 250  		t.Fatalf("Failed to save event 2: %v", err)
 251  	}
 252  
 253  	// Verify the same pubkeys got the same serials
 254  	authorSerial1, _ := db.GetPubkeySerial(authorPubkey)
 255  	pTagSerial1, _ := db.GetPubkeySerial(pTagPubkey)
 256  
 257  	if authorSerial1 == nil || pTagSerial1 == nil {
 258  		t.Fatal("Pubkey serials should exist after saving events")
 259  	}
 260  
 261  	t.Logf("Both events share the same pubkey serials:")
 262  	t.Logf("  Author serial: %d", authorSerial1.Get())
 263  	t.Logf("  P-tag serial: %d", pTagSerial1.Get())
 264  }
 265  
 266  func TestPubkeySerialEdgeCases(t *testing.T) {
 267  	ctx, cancel := context.WithCancel(context.Background())
 268  	defer cancel()
 269  
 270  	db, err := New(ctx, cancel, t.TempDir(), "info")
 271  	if err != nil {
 272  		t.Fatalf("Failed to create database: %v", err)
 273  	}
 274  	defer db.Close()
 275  
 276  	// Test with invalid pubkey length
 277  	invalidPubkey := make([]byte, 16) // Wrong length
 278  	_, err = db.GetOrCreatePubkeySerial(invalidPubkey)
 279  	if err == nil {
 280  		t.Error("Should reject pubkey with invalid length")
 281  	}
 282  
 283  	// Test GetPubkeySerial for non-existent pubkey
 284  	nonExistentPubkey := make([]byte, 32)
 285  	for i := range nonExistentPubkey {
 286  		nonExistentPubkey[i] = 0xFF
 287  	}
 288  
 289  	_, err = db.GetPubkeySerial(nonExistentPubkey)
 290  	if err == nil {
 291  		t.Error("Should return error for non-existent pubkey serial")
 292  	}
 293  }
 294  
 295  func TestGraphEdgeDirections(t *testing.T) {
 296  	ctx, cancel := context.WithCancel(context.Background())
 297  	defer cancel()
 298  
 299  	db, err := New(ctx, cancel, t.TempDir(), "info")
 300  	if err != nil {
 301  		t.Fatalf("Failed to create database: %v", err)
 302  	}
 303  	defer db.Close()
 304  
 305  	// Create test event with author and p-tags
 306  	authorPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
 307  	pTagPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
 308  
 309  	eventID := make([]byte, 32)
 310  	eventID[0] = 1
 311  	eventSig := make([]byte, 64)
 312  	eventSig[0] = 1
 313  
 314  	ev := &event.E{
 315  		ID:        eventID,
 316  		Pubkey:    authorPubkey,
 317  		CreatedAt: 1234567890,
 318  		Kind:      1, // text note
 319  		Content:   []byte("Test event"),
 320  		Sig:       eventSig,
 321  		Tags: tag.NewS(
 322  			tag.NewFromAny("p", hex.Enc(pTagPubkey)),
 323  		),
 324  	}
 325  
 326  	// Save the event
 327  	_, err = db.SaveEvent(ctx, ev)
 328  	if err != nil {
 329  		t.Fatalf("Failed to save event: %v", err)
 330  	}
 331  
 332  	// Verify graph edges with correct direction bytes
 333  	// Look for PubkeyEventGraph keys and check direction byte
 334  	var foundAuthorEdge, foundPTagEdge bool
 335  	db.View(func(txn *badger.Txn) error {
 336  		it := txn.NewIterator(badger.DefaultIteratorOptions)
 337  		defer it.Close()
 338  
 339  		prefix := []byte(indexes.PubkeyEventGraphPrefix)
 340  		for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
 341  			key := it.Item().KeyCopy(nil)
 342  			// Key format: peg(3)|pubkey_serial(5)|kind(2)|direction(1)|event_serial(5) = 16 bytes
 343  			if len(key) == 16 {
 344  				direction := key[10] // Byte at position 10 is the direction
 345  				t.Logf("Found PubkeyEventGraph edge: key=%s, direction=%d", hex.Enc(key), direction)
 346  
 347  				if direction == types.EdgeDirectionAuthor {
 348  					foundAuthorEdge = true
 349  					t.Logf("  ✓ Found author edge (direction=0)")
 350  				} else if direction == types.EdgeDirectionPTagIn {
 351  					foundPTagEdge = true
 352  					t.Logf("  ✓ Found p-tag inbound edge (direction=2)")
 353  				}
 354  			}
 355  		}
 356  		return nil
 357  	})
 358  
 359  	if !foundAuthorEdge {
 360  		t.Error("Did not find author edge with direction=0")
 361  	}
 362  	if !foundPTagEdge {
 363  		t.Error("Did not find p-tag inbound edge with direction=2")
 364  	}
 365  
 366  	t.Logf("Graph edges correctly stored with direction bytes:")
 367  	t.Logf("  Author edge: %v (direction=0)", foundAuthorEdge)
 368  	t.Logf("  P-tag inbound edge: %v (direction=2)", foundPTagEdge)
 369  }
 370