You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
318 lines
8.5 KiB
318 lines
8.5 KiB
//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) |
|
} |
|
}
|
|
|