//go:build !(js && wasm) package database import ( "context" "testing" "git.mleku.dev/mleku/nostr/encoders/event" "git.mleku.dev/mleku/nostr/encoders/hex" "git.mleku.dev/mleku/nostr/encoders/tag" ) func TestTraverseFollows(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() db, err := New(ctx, cancel, t.TempDir(), "info") if err != nil { t.Fatalf("Failed to create database: %v", err) } defer db.Close() // Create a simple follow graph: // Alice -> Bob, Carol // Bob -> David, Eve // Carol -> Eve, Frank // // Expected depth 1 from Alice: Bob, Carol // Expected depth 2 from Alice: David, Eve, Frank (Eve deduplicated) alice, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001") bob, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002") carol, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003") david, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000004") eve, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000005") frank, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000006") // Create Alice's follow list (kind 3) aliceContactID := make([]byte, 32) aliceContactID[0] = 0x10 aliceContactSig := make([]byte, 64) aliceContactSig[0] = 0x10 aliceContact := &event.E{ ID: aliceContactID, Pubkey: alice, CreatedAt: 1234567890, Kind: 3, Content: []byte(""), Sig: aliceContactSig, Tags: tag.NewS( tag.NewFromAny("p", hex.Enc(bob)), tag.NewFromAny("p", hex.Enc(carol)), ), } _, err = db.SaveEvent(ctx, aliceContact) if err != nil { t.Fatalf("Failed to save Alice's contact list: %v", err) } // Create Bob's follow list bobContactID := make([]byte, 32) bobContactID[0] = 0x20 bobContactSig := make([]byte, 64) bobContactSig[0] = 0x20 bobContact := &event.E{ ID: bobContactID, Pubkey: bob, CreatedAt: 1234567891, Kind: 3, Content: []byte(""), Sig: bobContactSig, Tags: tag.NewS( tag.NewFromAny("p", hex.Enc(david)), tag.NewFromAny("p", hex.Enc(eve)), ), } _, err = db.SaveEvent(ctx, bobContact) if err != nil { t.Fatalf("Failed to save Bob's contact list: %v", err) } // Create Carol's follow list carolContactID := make([]byte, 32) carolContactID[0] = 0x30 carolContactSig := make([]byte, 64) carolContactSig[0] = 0x30 carolContact := &event.E{ ID: carolContactID, Pubkey: carol, CreatedAt: 1234567892, Kind: 3, Content: []byte(""), Sig: carolContactSig, Tags: tag.NewS( tag.NewFromAny("p", hex.Enc(eve)), tag.NewFromAny("p", hex.Enc(frank)), ), } _, err = db.SaveEvent(ctx, carolContact) if err != nil { t.Fatalf("Failed to save Carol's contact list: %v", err) } // Traverse follows from Alice with depth 2 result, err := db.TraverseFollows(alice, 2) if err != nil { t.Fatalf("TraverseFollows failed: %v", err) } // Check depth 1: should have Bob and Carol depth1 := result.GetPubkeysAtDepth(1) if len(depth1) != 2 { t.Errorf("Expected 2 pubkeys at depth 1, got %d", len(depth1)) } depth1Set := make(map[string]bool) for _, pk := range depth1 { depth1Set[pk] = true } if !depth1Set[hex.Enc(bob)] { t.Error("Bob should be at depth 1") } if !depth1Set[hex.Enc(carol)] { t.Error("Carol should be at depth 1") } // Check depth 2: should have David, Eve, Frank (Eve deduplicated) depth2 := result.GetPubkeysAtDepth(2) if len(depth2) != 3 { t.Errorf("Expected 3 pubkeys at depth 2, got %d: %v", len(depth2), depth2) } depth2Set := make(map[string]bool) for _, pk := range depth2 { depth2Set[pk] = true } if !depth2Set[hex.Enc(david)] { t.Error("David should be at depth 2") } if !depth2Set[hex.Enc(eve)] { t.Error("Eve should be at depth 2") } if !depth2Set[hex.Enc(frank)] { t.Error("Frank should be at depth 2") } // Verify total count if result.TotalPubkeys != 5 { t.Errorf("Expected 5 total pubkeys, got %d", result.TotalPubkeys) } // Verify ToDepthArrays output arrays := result.ToDepthArrays() if len(arrays) != 2 { t.Errorf("Expected 2 depth arrays, got %d", len(arrays)) } if len(arrays[0]) != 2 { t.Errorf("Expected 2 pubkeys in depth 1 array, got %d", len(arrays[0])) } if len(arrays[1]) != 3 { t.Errorf("Expected 3 pubkeys in depth 2 array, got %d", len(arrays[1])) } } func TestTraverseFollowsDepth1(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() db, err := New(ctx, cancel, t.TempDir(), "info") if err != nil { t.Fatalf("Failed to create database: %v", err) } defer db.Close() alice, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001") bob, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002") carol, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003") // Create Alice's follow list aliceContactID := make([]byte, 32) aliceContactID[0] = 0x10 aliceContactSig := make([]byte, 64) aliceContactSig[0] = 0x10 aliceContact := &event.E{ ID: aliceContactID, Pubkey: alice, CreatedAt: 1234567890, Kind: 3, Content: []byte(""), Sig: aliceContactSig, Tags: tag.NewS( tag.NewFromAny("p", hex.Enc(bob)), tag.NewFromAny("p", hex.Enc(carol)), ), } _, err = db.SaveEvent(ctx, aliceContact) if err != nil { t.Fatalf("Failed to save contact list: %v", err) } // Traverse with depth 1 only result, err := db.TraverseFollows(alice, 1) if err != nil { t.Fatalf("TraverseFollows failed: %v", err) } if result.TotalPubkeys != 2 { t.Errorf("Expected 2 pubkeys, got %d", result.TotalPubkeys) } arrays := result.ToDepthArrays() if len(arrays) != 1 { t.Errorf("Expected 1 depth array for depth 1 query, got %d", len(arrays)) } } func TestTraverseFollowersBasic(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() db, err := New(ctx, cancel, t.TempDir(), "info") if err != nil { t.Fatalf("Failed to create database: %v", err) } defer db.Close() // Create scenario: Bob and Carol follow Alice alice, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001") bob, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002") carol, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003") // Bob's contact list includes Alice bobContactID := make([]byte, 32) bobContactID[0] = 0x10 bobContactSig := make([]byte, 64) bobContactSig[0] = 0x10 bobContact := &event.E{ ID: bobContactID, Pubkey: bob, CreatedAt: 1234567890, Kind: 3, Content: []byte(""), Sig: bobContactSig, Tags: tag.NewS( tag.NewFromAny("p", hex.Enc(alice)), ), } _, err = db.SaveEvent(ctx, bobContact) if err != nil { t.Fatalf("Failed to save Bob's contact list: %v", err) } // Carol's contact list includes Alice carolContactID := make([]byte, 32) carolContactID[0] = 0x20 carolContactSig := make([]byte, 64) carolContactSig[0] = 0x20 carolContact := &event.E{ ID: carolContactID, Pubkey: carol, CreatedAt: 1234567891, Kind: 3, Content: []byte(""), Sig: carolContactSig, Tags: tag.NewS( tag.NewFromAny("p", hex.Enc(alice)), ), } _, err = db.SaveEvent(ctx, carolContact) if err != nil { t.Fatalf("Failed to save Carol's contact list: %v", err) } // Find Alice's followers result, err := db.TraverseFollowers(alice, 1) if err != nil { t.Fatalf("TraverseFollowers failed: %v", err) } if result.TotalPubkeys != 2 { t.Errorf("Expected 2 followers, got %d", result.TotalPubkeys) } followers := result.GetPubkeysAtDepth(1) followerSet := make(map[string]bool) for _, pk := range followers { followerSet[pk] = true } if !followerSet[hex.Enc(bob)] { t.Error("Bob should be a follower") } if !followerSet[hex.Enc(carol)] { t.Error("Carol should be a follower") } } func TestTraverseFollowsNonExistent(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() db, err := New(ctx, cancel, t.TempDir(), "info") if err != nil { t.Fatalf("Failed to create database: %v", err) } defer db.Close() // Try to traverse from a pubkey that doesn't exist nonExistent, _ := hex.Dec("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") result, err := db.TraverseFollows(nonExistent, 2) if err != nil { t.Fatalf("TraverseFollows should not error for non-existent pubkey: %v", err) } if result.TotalPubkeys != 0 { t.Errorf("Expected 0 pubkeys for non-existent seed, got %d", result.TotalPubkeys) } }