graph-traversal_test.go raw

   1  //go:build !(js && wasm)
   2  
   3  package database
   4  
   5  import (
   6  	"context"
   7  	"testing"
   8  
   9  	"next.orly.dev/pkg/nostr/encoders/event"
  10  	"next.orly.dev/pkg/nostr/encoders/hex"
  11  	"next.orly.dev/pkg/nostr/encoders/tag"
  12  )
  13  
  14  func TestGetPTagsFromEventSerial(t *testing.T) {
  15  	ctx, cancel := context.WithCancel(context.Background())
  16  	defer cancel()
  17  
  18  	db, err := New(ctx, cancel, t.TempDir(), "info")
  19  	if err != nil {
  20  		t.Fatalf("Failed to create database: %v", err)
  21  	}
  22  	defer db.Close()
  23  
  24  	// Create an author pubkey
  25  	authorPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
  26  
  27  	// Create p-tag target pubkeys
  28  	target1, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
  29  	target2, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003")
  30  
  31  	// Create event with p-tags
  32  	eventID := make([]byte, 32)
  33  	eventID[0] = 0x10
  34  	eventSig := make([]byte, 64)
  35  	eventSig[0] = 0x10
  36  
  37  	ev := &event.E{
  38  		ID:        eventID,
  39  		Pubkey:    authorPubkey,
  40  		CreatedAt: 1234567890,
  41  		Kind:      1,
  42  		Content:   []byte("Test event with p-tags"),
  43  		Sig:       eventSig,
  44  		Tags: tag.NewS(
  45  			tag.NewFromAny("p", hex.Enc(target1)),
  46  			tag.NewFromAny("p", hex.Enc(target2)),
  47  		),
  48  	}
  49  
  50  	_, err = db.SaveEvent(ctx, ev)
  51  	if err != nil {
  52  		t.Fatalf("Failed to save event: %v", err)
  53  	}
  54  
  55  	// Get the event serial
  56  	eventSerial, err := db.GetSerialById(eventID)
  57  	if err != nil {
  58  		t.Fatalf("Failed to get event serial: %v", err)
  59  	}
  60  
  61  	// Get p-tags from event serial
  62  	ptagSerials, err := db.GetPTagsFromEventSerial(eventSerial)
  63  	if err != nil {
  64  		t.Fatalf("GetPTagsFromEventSerial failed: %v", err)
  65  	}
  66  
  67  	// Should have 2 p-tags
  68  	if len(ptagSerials) != 2 {
  69  		t.Errorf("Expected 2 p-tag serials, got %d", len(ptagSerials))
  70  	}
  71  
  72  	// Verify the pubkeys
  73  	for _, serial := range ptagSerials {
  74  		pubkey, err := db.GetPubkeyBySerial(serial)
  75  		if err != nil {
  76  			t.Errorf("Failed to get pubkey for serial: %v", err)
  77  			continue
  78  		}
  79  		pubkeyHex := hex.Enc(pubkey)
  80  		if pubkeyHex != hex.Enc(target1) && pubkeyHex != hex.Enc(target2) {
  81  			t.Errorf("Unexpected pubkey: %s", pubkeyHex)
  82  		}
  83  	}
  84  }
  85  
  86  func TestGetETagsFromEventSerial(t *testing.T) {
  87  	ctx, cancel := context.WithCancel(context.Background())
  88  	defer cancel()
  89  
  90  	db, err := New(ctx, cancel, t.TempDir(), "info")
  91  	if err != nil {
  92  		t.Fatalf("Failed to create database: %v", err)
  93  	}
  94  	defer db.Close()
  95  
  96  	// Create a parent event
  97  	parentPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
  98  	parentID := make([]byte, 32)
  99  	parentID[0] = 0x10
 100  	parentSig := make([]byte, 64)
 101  	parentSig[0] = 0x10
 102  
 103  	parentEvent := &event.E{
 104  		ID:        parentID,
 105  		Pubkey:    parentPubkey,
 106  		CreatedAt: 1234567890,
 107  		Kind:      1,
 108  		Content:   []byte("Parent post"),
 109  		Sig:       parentSig,
 110  		Tags:      &tag.S{},
 111  	}
 112  	_, err = db.SaveEvent(ctx, parentEvent)
 113  	if err != nil {
 114  		t.Fatalf("Failed to save parent event: %v", err)
 115  	}
 116  
 117  	// Create a reply event with e-tag
 118  	replyPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
 119  	replyID := make([]byte, 32)
 120  	replyID[0] = 0x20
 121  	replySig := make([]byte, 64)
 122  	replySig[0] = 0x20
 123  
 124  	replyEvent := &event.E{
 125  		ID:        replyID,
 126  		Pubkey:    replyPubkey,
 127  		CreatedAt: 1234567891,
 128  		Kind:      1,
 129  		Content:   []byte("Reply"),
 130  		Sig:       replySig,
 131  		Tags: tag.NewS(
 132  			tag.NewFromAny("e", hex.Enc(parentID)),
 133  		),
 134  	}
 135  	_, err = db.SaveEvent(ctx, replyEvent)
 136  	if err != nil {
 137  		t.Fatalf("Failed to save reply event: %v", err)
 138  	}
 139  
 140  	// Get e-tags from reply
 141  	replySerial, _ := db.GetSerialById(replyID)
 142  	etagSerials, err := db.GetETagsFromEventSerial(replySerial)
 143  	if err != nil {
 144  		t.Fatalf("GetETagsFromEventSerial failed: %v", err)
 145  	}
 146  
 147  	if len(etagSerials) != 1 {
 148  		t.Errorf("Expected 1 e-tag serial, got %d", len(etagSerials))
 149  	}
 150  
 151  	// Verify the target event
 152  	if len(etagSerials) > 0 {
 153  		targetEventID, err := db.GetEventIdBySerial(etagSerials[0])
 154  		if err != nil {
 155  			t.Fatalf("Failed to get event ID from serial: %v", err)
 156  		}
 157  		if hex.Enc(targetEventID) != hex.Enc(parentID) {
 158  			t.Errorf("Expected parent ID, got %s", hex.Enc(targetEventID))
 159  		}
 160  	}
 161  }
 162  
 163  func TestGetReferencingEvents(t *testing.T) {
 164  	ctx, cancel := context.WithCancel(context.Background())
 165  	defer cancel()
 166  
 167  	db, err := New(ctx, cancel, t.TempDir(), "info")
 168  	if err != nil {
 169  		t.Fatalf("Failed to create database: %v", err)
 170  	}
 171  	defer db.Close()
 172  
 173  	// Create a parent event
 174  	parentPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
 175  	parentID := make([]byte, 32)
 176  	parentID[0] = 0x10
 177  	parentSig := make([]byte, 64)
 178  	parentSig[0] = 0x10
 179  
 180  	parentEvent := &event.E{
 181  		ID:        parentID,
 182  		Pubkey:    parentPubkey,
 183  		CreatedAt: 1234567890,
 184  		Kind:      1,
 185  		Content:   []byte("Parent post"),
 186  		Sig:       parentSig,
 187  		Tags:      &tag.S{},
 188  	}
 189  	_, err = db.SaveEvent(ctx, parentEvent)
 190  	if err != nil {
 191  		t.Fatalf("Failed to save parent event: %v", err)
 192  	}
 193  
 194  	// Create multiple replies and reactions
 195  	for i := 0; i < 3; i++ {
 196  		replyPubkey := make([]byte, 32)
 197  		replyPubkey[0] = byte(0x20 + i)
 198  		replyID := make([]byte, 32)
 199  		replyID[0] = byte(0x30 + i)
 200  		replySig := make([]byte, 64)
 201  		replySig[0] = byte(0x30 + i)
 202  
 203  		var evKind uint16 = 1 // Reply
 204  		if i == 2 {
 205  			evKind = 7 // Reaction
 206  		}
 207  
 208  		replyEvent := &event.E{
 209  			ID:        replyID,
 210  			Pubkey:    replyPubkey,
 211  			CreatedAt: int64(1234567891 + i),
 212  			Kind:      evKind,
 213  			Content:   []byte("Response"),
 214  			Sig:       replySig,
 215  			Tags: tag.NewS(
 216  				tag.NewFromAny("e", hex.Enc(parentID)),
 217  			),
 218  		}
 219  		_, err = db.SaveEvent(ctx, replyEvent)
 220  		if err != nil {
 221  			t.Fatalf("Failed to save reply %d: %v", i, err)
 222  		}
 223  	}
 224  
 225  	// Get parent serial
 226  	parentSerial, _ := db.GetSerialById(parentID)
 227  
 228  	// Test without kind filter
 229  	refs, err := db.GetReferencingEvents(parentSerial, nil)
 230  	if err != nil {
 231  		t.Fatalf("GetReferencingEvents failed: %v", err)
 232  	}
 233  	if len(refs) != 3 {
 234  		t.Errorf("Expected 3 referencing events, got %d", len(refs))
 235  	}
 236  
 237  	// Test with kind filter (only replies)
 238  	refs, err = db.GetReferencingEvents(parentSerial, []uint16{1})
 239  	if err != nil {
 240  		t.Fatalf("GetReferencingEvents with kind filter failed: %v", err)
 241  	}
 242  	if len(refs) != 2 {
 243  		t.Errorf("Expected 2 kind-1 referencing events, got %d", len(refs))
 244  	}
 245  
 246  	// Test with kind filter (only reactions)
 247  	refs, err = db.GetReferencingEvents(parentSerial, []uint16{7})
 248  	if err != nil {
 249  		t.Fatalf("GetReferencingEvents with kind 7 filter failed: %v", err)
 250  	}
 251  	if len(refs) != 1 {
 252  		t.Errorf("Expected 1 kind-7 referencing event, got %d", len(refs))
 253  	}
 254  }
 255  
 256  func TestGetFollowsFromPubkeySerial(t *testing.T) {
 257  	ctx, cancel := context.WithCancel(context.Background())
 258  	defer cancel()
 259  
 260  	db, err := New(ctx, cancel, t.TempDir(), "info")
 261  	if err != nil {
 262  		t.Fatalf("Failed to create database: %v", err)
 263  	}
 264  	defer db.Close()
 265  
 266  	// Create author and their follows
 267  	authorPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
 268  	follow1, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
 269  	follow2, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003")
 270  	follow3, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000004")
 271  
 272  	// Create kind-3 contact list
 273  	eventID := make([]byte, 32)
 274  	eventID[0] = 0x10
 275  	eventSig := make([]byte, 64)
 276  	eventSig[0] = 0x10
 277  
 278  	contactList := &event.E{
 279  		ID:        eventID,
 280  		Pubkey:    authorPubkey,
 281  		CreatedAt: 1234567890,
 282  		Kind:      3,
 283  		Content:   []byte(""),
 284  		Sig:       eventSig,
 285  		Tags: tag.NewS(
 286  			tag.NewFromAny("p", hex.Enc(follow1)),
 287  			tag.NewFromAny("p", hex.Enc(follow2)),
 288  			tag.NewFromAny("p", hex.Enc(follow3)),
 289  		),
 290  	}
 291  	_, err = db.SaveEvent(ctx, contactList)
 292  	if err != nil {
 293  		t.Fatalf("Failed to save contact list: %v", err)
 294  	}
 295  
 296  	// Get author serial
 297  	authorSerial, err := db.GetPubkeySerial(authorPubkey)
 298  	if err != nil {
 299  		t.Fatalf("Failed to get author serial: %v", err)
 300  	}
 301  
 302  	// Get follows
 303  	follows, err := db.GetFollowsFromPubkeySerial(authorSerial)
 304  	if err != nil {
 305  		t.Fatalf("GetFollowsFromPubkeySerial failed: %v", err)
 306  	}
 307  
 308  	if len(follows) != 3 {
 309  		t.Errorf("Expected 3 follows, got %d", len(follows))
 310  	}
 311  
 312  	// Verify the follows are correct
 313  	expectedFollows := map[string]bool{
 314  		hex.Enc(follow1): false,
 315  		hex.Enc(follow2): false,
 316  		hex.Enc(follow3): false,
 317  	}
 318  	for _, serial := range follows {
 319  		pubkey, err := db.GetPubkeyBySerial(serial)
 320  		if err != nil {
 321  			t.Errorf("Failed to get pubkey from serial: %v", err)
 322  			continue
 323  		}
 324  		pkHex := hex.Enc(pubkey)
 325  		if _, exists := expectedFollows[pkHex]; exists {
 326  			expectedFollows[pkHex] = true
 327  		} else {
 328  			t.Errorf("Unexpected follow: %s", pkHex)
 329  		}
 330  	}
 331  	for pk, found := range expectedFollows {
 332  		if !found {
 333  			t.Errorf("Expected follow not found: %s", pk)
 334  		}
 335  	}
 336  }
 337  
 338  func TestGraphResult(t *testing.T) {
 339  	result := NewGraphResult()
 340  
 341  	// Add pubkeys at different depths
 342  	result.AddPubkeyAtDepth("pubkey1", 1)
 343  	result.AddPubkeyAtDepth("pubkey2", 1)
 344  	result.AddPubkeyAtDepth("pubkey3", 2)
 345  	result.AddPubkeyAtDepth("pubkey4", 2)
 346  	result.AddPubkeyAtDepth("pubkey5", 3)
 347  
 348  	// Try to add duplicate
 349  	added := result.AddPubkeyAtDepth("pubkey1", 2)
 350  	if added {
 351  		t.Error("Should not add duplicate pubkey")
 352  	}
 353  
 354  	// Verify counts
 355  	if result.TotalPubkeys != 5 {
 356  		t.Errorf("Expected 5 total pubkeys, got %d", result.TotalPubkeys)
 357  	}
 358  
 359  	// Verify depth tracking
 360  	if result.GetPubkeyDepth("pubkey1") != 1 {
 361  		t.Errorf("pubkey1 should be at depth 1")
 362  	}
 363  	if result.GetPubkeyDepth("pubkey3") != 2 {
 364  		t.Errorf("pubkey3 should be at depth 2")
 365  	}
 366  
 367  	// Verify HasPubkey
 368  	if !result.HasPubkey("pubkey1") {
 369  		t.Error("Should have pubkey1")
 370  	}
 371  	if result.HasPubkey("nonexistent") {
 372  		t.Error("Should not have nonexistent pubkey")
 373  	}
 374  
 375  	// Verify ToDepthArrays
 376  	arrays := result.ToDepthArrays()
 377  	if len(arrays) != 3 {
 378  		t.Errorf("Expected 3 depth arrays, got %d", len(arrays))
 379  	}
 380  	if len(arrays[0]) != 2 {
 381  		t.Errorf("Expected 2 pubkeys at depth 1, got %d", len(arrays[0]))
 382  	}
 383  	if len(arrays[1]) != 2 {
 384  		t.Errorf("Expected 2 pubkeys at depth 2, got %d", len(arrays[1]))
 385  	}
 386  	if len(arrays[2]) != 1 {
 387  		t.Errorf("Expected 1 pubkey at depth 3, got %d", len(arrays[2]))
 388  	}
 389  }
 390  
 391  func TestGraphResultRefs(t *testing.T) {
 392  	result := NewGraphResult()
 393  
 394  	// Add some pubkeys
 395  	result.AddPubkeyAtDepth("pubkey1", 1)
 396  	result.AddEventAtDepth("event1", 1)
 397  
 398  	// Add inbound refs (kind 7 reactions)
 399  	result.AddInboundRef(7, "event1", "reaction1")
 400  	result.AddInboundRef(7, "event1", "reaction2")
 401  	result.AddInboundRef(7, "event1", "reaction3")
 402  
 403  	// Get sorted refs
 404  	refs := result.GetInboundRefsSorted(7)
 405  	if len(refs) != 1 {
 406  		t.Fatalf("Expected 1 aggregation, got %d", len(refs))
 407  	}
 408  	if refs[0].RefCount != 3 {
 409  		t.Errorf("Expected 3 refs, got %d", refs[0].RefCount)
 410  	}
 411  	if refs[0].TargetEventID != "event1" {
 412  		t.Errorf("Expected event1, got %s", refs[0].TargetEventID)
 413  	}
 414  }
 415  
 416  func TestGetFollowersOfPubkeySerial(t *testing.T) {
 417  	ctx, cancel := context.WithCancel(context.Background())
 418  	defer cancel()
 419  
 420  	db, err := New(ctx, cancel, t.TempDir(), "info")
 421  	if err != nil {
 422  		t.Fatalf("Failed to create database: %v", err)
 423  	}
 424  	defer db.Close()
 425  
 426  	// Create target pubkey (the one being followed)
 427  	targetPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
 428  
 429  	// Create followers
 430  	follower1, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
 431  	follower2, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003")
 432  
 433  	// Create kind-3 contact lists for followers
 434  	for i, followerPubkey := range [][]byte{follower1, follower2} {
 435  		eventID := make([]byte, 32)
 436  		eventID[0] = byte(0x10 + i)
 437  		eventSig := make([]byte, 64)
 438  		eventSig[0] = byte(0x10 + i)
 439  
 440  		contactList := &event.E{
 441  			ID:        eventID,
 442  			Pubkey:    followerPubkey,
 443  			CreatedAt: int64(1234567890 + i),
 444  			Kind:      3,
 445  			Content:   []byte(""),
 446  			Sig:       eventSig,
 447  			Tags: tag.NewS(
 448  				tag.NewFromAny("p", hex.Enc(targetPubkey)),
 449  			),
 450  		}
 451  		_, err = db.SaveEvent(ctx, contactList)
 452  		if err != nil {
 453  			t.Fatalf("Failed to save contact list %d: %v", i, err)
 454  		}
 455  	}
 456  
 457  	// Get target serial
 458  	targetSerial, err := db.GetPubkeySerial(targetPubkey)
 459  	if err != nil {
 460  		t.Fatalf("Failed to get target serial: %v", err)
 461  	}
 462  
 463  	// Get followers
 464  	followers, err := db.GetFollowersOfPubkeySerial(targetSerial)
 465  	if err != nil {
 466  		t.Fatalf("GetFollowersOfPubkeySerial failed: %v", err)
 467  	}
 468  
 469  	if len(followers) != 2 {
 470  		t.Errorf("Expected 2 followers, got %d", len(followers))
 471  	}
 472  
 473  	// Verify the followers
 474  	expectedFollowers := map[string]bool{
 475  		hex.Enc(follower1): false,
 476  		hex.Enc(follower2): false,
 477  	}
 478  	for _, serial := range followers {
 479  		pubkey, err := db.GetPubkeyBySerial(serial)
 480  		if err != nil {
 481  			t.Errorf("Failed to get pubkey from serial: %v", err)
 482  			continue
 483  		}
 484  		pkHex := hex.Enc(pubkey)
 485  		if _, exists := expectedFollowers[pkHex]; exists {
 486  			expectedFollowers[pkHex] = true
 487  		} else {
 488  			t.Errorf("Unexpected follower: %s", pkHex)
 489  		}
 490  	}
 491  	for pk, found := range expectedFollowers {
 492  		if !found {
 493  			t.Errorf("Expected follower not found: %s", pk)
 494  		}
 495  	}
 496  }
 497  
 498  func TestPubkeyHexToSerial(t *testing.T) {
 499  	ctx, cancel := context.WithCancel(context.Background())
 500  	defer cancel()
 501  
 502  	db, err := New(ctx, cancel, t.TempDir(), "info")
 503  	if err != nil {
 504  		t.Fatalf("Failed to create database: %v", err)
 505  	}
 506  	defer db.Close()
 507  
 508  	// Create a pubkey by saving an event
 509  	pubkeyBytes, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
 510  	eventID := make([]byte, 32)
 511  	eventID[0] = 0x10
 512  	eventSig := make([]byte, 64)
 513  	eventSig[0] = 0x10
 514  
 515  	ev := &event.E{
 516  		ID:        eventID,
 517  		Pubkey:    pubkeyBytes,
 518  		CreatedAt: 1234567890,
 519  		Kind:      1,
 520  		Content:   []byte("Test"),
 521  		Sig:       eventSig,
 522  		Tags:      &tag.S{},
 523  	}
 524  	_, err = db.SaveEvent(ctx, ev)
 525  	if err != nil {
 526  		t.Fatalf("Failed to save event: %v", err)
 527  	}
 528  
 529  	// Convert hex to serial
 530  	pubkeyHex := hex.Enc(pubkeyBytes)
 531  	serial, err := db.PubkeyHexToSerial(pubkeyHex)
 532  	if err != nil {
 533  		t.Fatalf("PubkeyHexToSerial failed: %v", err)
 534  	}
 535  	if serial == nil {
 536  		t.Fatal("Expected non-nil serial")
 537  	}
 538  
 539  	// Convert back and verify
 540  	backToHex, err := db.GetPubkeyHexFromSerial(serial)
 541  	if err != nil {
 542  		t.Fatalf("GetPubkeyHexFromSerial failed: %v", err)
 543  	}
 544  	if backToHex != pubkeyHex {
 545  		t.Errorf("Round-trip failed: %s != %s", backToHex, pubkeyHex)
 546  	}
 547  }
 548