Files
next.orly.dev/pkg/database/graph-follows_test.go
mleku 6b98c23606
Some checks failed
Go / build-and-release (push) Has been cancelled
add first draft graph query implementation
2025-12-04 09:28:13 +00:00

319 lines
8.5 KiB
Go

//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)
}
}