graph-follows_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 TestTraverseFollows(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 a simple follow graph:
  25  	// Alice -> Bob, Carol
  26  	// Bob -> David, Eve
  27  	// Carol -> Eve, Frank
  28  	//
  29  	// Expected depth 1 from Alice: Bob, Carol
  30  	// Expected depth 2 from Alice: David, Eve, Frank (Eve deduplicated)
  31  
  32  	alice, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
  33  	bob, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
  34  	carol, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003")
  35  	david, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000004")
  36  	eve, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000005")
  37  	frank, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000006")
  38  
  39  	// Create Alice's follow list (kind 3)
  40  	aliceContactID := make([]byte, 32)
  41  	aliceContactID[0] = 0x10
  42  	aliceContactSig := make([]byte, 64)
  43  	aliceContactSig[0] = 0x10
  44  	aliceContact := &event.E{
  45  		ID:        aliceContactID,
  46  		Pubkey:    alice,
  47  		CreatedAt: 1234567890,
  48  		Kind:      3,
  49  		Content:   []byte(""),
  50  		Sig:       aliceContactSig,
  51  		Tags: tag.NewS(
  52  			tag.NewFromAny("p", hex.Enc(bob)),
  53  			tag.NewFromAny("p", hex.Enc(carol)),
  54  		),
  55  	}
  56  	_, err = db.SaveEvent(ctx, aliceContact)
  57  	if err != nil {
  58  		t.Fatalf("Failed to save Alice's contact list: %v", err)
  59  	}
  60  
  61  	// Create Bob's follow list
  62  	bobContactID := make([]byte, 32)
  63  	bobContactID[0] = 0x20
  64  	bobContactSig := make([]byte, 64)
  65  	bobContactSig[0] = 0x20
  66  	bobContact := &event.E{
  67  		ID:        bobContactID,
  68  		Pubkey:    bob,
  69  		CreatedAt: 1234567891,
  70  		Kind:      3,
  71  		Content:   []byte(""),
  72  		Sig:       bobContactSig,
  73  		Tags: tag.NewS(
  74  			tag.NewFromAny("p", hex.Enc(david)),
  75  			tag.NewFromAny("p", hex.Enc(eve)),
  76  		),
  77  	}
  78  	_, err = db.SaveEvent(ctx, bobContact)
  79  	if err != nil {
  80  		t.Fatalf("Failed to save Bob's contact list: %v", err)
  81  	}
  82  
  83  	// Create Carol's follow list
  84  	carolContactID := make([]byte, 32)
  85  	carolContactID[0] = 0x30
  86  	carolContactSig := make([]byte, 64)
  87  	carolContactSig[0] = 0x30
  88  	carolContact := &event.E{
  89  		ID:        carolContactID,
  90  		Pubkey:    carol,
  91  		CreatedAt: 1234567892,
  92  		Kind:      3,
  93  		Content:   []byte(""),
  94  		Sig:       carolContactSig,
  95  		Tags: tag.NewS(
  96  			tag.NewFromAny("p", hex.Enc(eve)),
  97  			tag.NewFromAny("p", hex.Enc(frank)),
  98  		),
  99  	}
 100  	_, err = db.SaveEvent(ctx, carolContact)
 101  	if err != nil {
 102  		t.Fatalf("Failed to save Carol's contact list: %v", err)
 103  	}
 104  
 105  	// Traverse follows from Alice with depth 2
 106  	result, err := db.TraverseFollows(alice, 2)
 107  	if err != nil {
 108  		t.Fatalf("TraverseFollows failed: %v", err)
 109  	}
 110  
 111  	// Check depth 1: should have Bob and Carol
 112  	depth1 := result.GetPubkeysAtDepth(1)
 113  	if len(depth1) != 2 {
 114  		t.Errorf("Expected 2 pubkeys at depth 1, got %d", len(depth1))
 115  	}
 116  
 117  	depth1Set := make(map[string]bool)
 118  	for _, pk := range depth1 {
 119  		depth1Set[pk] = true
 120  	}
 121  	if !depth1Set[hex.Enc(bob)] {
 122  		t.Error("Bob should be at depth 1")
 123  	}
 124  	if !depth1Set[hex.Enc(carol)] {
 125  		t.Error("Carol should be at depth 1")
 126  	}
 127  
 128  	// Check depth 2: should have David, Eve, Frank (Eve deduplicated)
 129  	depth2 := result.GetPubkeysAtDepth(2)
 130  	if len(depth2) != 3 {
 131  		t.Errorf("Expected 3 pubkeys at depth 2, got %d: %v", len(depth2), depth2)
 132  	}
 133  
 134  	depth2Set := make(map[string]bool)
 135  	for _, pk := range depth2 {
 136  		depth2Set[pk] = true
 137  	}
 138  	if !depth2Set[hex.Enc(david)] {
 139  		t.Error("David should be at depth 2")
 140  	}
 141  	if !depth2Set[hex.Enc(eve)] {
 142  		t.Error("Eve should be at depth 2")
 143  	}
 144  	if !depth2Set[hex.Enc(frank)] {
 145  		t.Error("Frank should be at depth 2")
 146  	}
 147  
 148  	// Verify total count
 149  	if result.TotalPubkeys != 5 {
 150  		t.Errorf("Expected 5 total pubkeys, got %d", result.TotalPubkeys)
 151  	}
 152  
 153  	// Verify ToDepthArrays output
 154  	arrays := result.ToDepthArrays()
 155  	if len(arrays) != 2 {
 156  		t.Errorf("Expected 2 depth arrays, got %d", len(arrays))
 157  	}
 158  	if len(arrays[0]) != 2 {
 159  		t.Errorf("Expected 2 pubkeys in depth 1 array, got %d", len(arrays[0]))
 160  	}
 161  	if len(arrays[1]) != 3 {
 162  		t.Errorf("Expected 3 pubkeys in depth 2 array, got %d", len(arrays[1]))
 163  	}
 164  }
 165  
 166  func TestTraverseFollowsDepth1(t *testing.T) {
 167  	ctx, cancel := context.WithCancel(context.Background())
 168  	defer cancel()
 169  
 170  	db, err := New(ctx, cancel, t.TempDir(), "info")
 171  	if err != nil {
 172  		t.Fatalf("Failed to create database: %v", err)
 173  	}
 174  	defer db.Close()
 175  
 176  	alice, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
 177  	bob, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
 178  	carol, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003")
 179  
 180  	// Create Alice's follow list
 181  	aliceContactID := make([]byte, 32)
 182  	aliceContactID[0] = 0x10
 183  	aliceContactSig := make([]byte, 64)
 184  	aliceContactSig[0] = 0x10
 185  	aliceContact := &event.E{
 186  		ID:        aliceContactID,
 187  		Pubkey:    alice,
 188  		CreatedAt: 1234567890,
 189  		Kind:      3,
 190  		Content:   []byte(""),
 191  		Sig:       aliceContactSig,
 192  		Tags: tag.NewS(
 193  			tag.NewFromAny("p", hex.Enc(bob)),
 194  			tag.NewFromAny("p", hex.Enc(carol)),
 195  		),
 196  	}
 197  	_, err = db.SaveEvent(ctx, aliceContact)
 198  	if err != nil {
 199  		t.Fatalf("Failed to save contact list: %v", err)
 200  	}
 201  
 202  	// Traverse with depth 1 only
 203  	result, err := db.TraverseFollows(alice, 1)
 204  	if err != nil {
 205  		t.Fatalf("TraverseFollows failed: %v", err)
 206  	}
 207  
 208  	if result.TotalPubkeys != 2 {
 209  		t.Errorf("Expected 2 pubkeys, got %d", result.TotalPubkeys)
 210  	}
 211  
 212  	arrays := result.ToDepthArrays()
 213  	if len(arrays) != 1 {
 214  		t.Errorf("Expected 1 depth array for depth 1 query, got %d", len(arrays))
 215  	}
 216  }
 217  
 218  func TestTraverseFollowersBasic(t *testing.T) {
 219  	ctx, cancel := context.WithCancel(context.Background())
 220  	defer cancel()
 221  
 222  	db, err := New(ctx, cancel, t.TempDir(), "info")
 223  	if err != nil {
 224  		t.Fatalf("Failed to create database: %v", err)
 225  	}
 226  	defer db.Close()
 227  
 228  	// Create scenario: Bob and Carol follow Alice
 229  	alice, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
 230  	bob, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
 231  	carol, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003")
 232  
 233  	// Bob's contact list includes Alice
 234  	bobContactID := make([]byte, 32)
 235  	bobContactID[0] = 0x10
 236  	bobContactSig := make([]byte, 64)
 237  	bobContactSig[0] = 0x10
 238  	bobContact := &event.E{
 239  		ID:        bobContactID,
 240  		Pubkey:    bob,
 241  		CreatedAt: 1234567890,
 242  		Kind:      3,
 243  		Content:   []byte(""),
 244  		Sig:       bobContactSig,
 245  		Tags: tag.NewS(
 246  			tag.NewFromAny("p", hex.Enc(alice)),
 247  		),
 248  	}
 249  	_, err = db.SaveEvent(ctx, bobContact)
 250  	if err != nil {
 251  		t.Fatalf("Failed to save Bob's contact list: %v", err)
 252  	}
 253  
 254  	// Carol's contact list includes Alice
 255  	carolContactID := make([]byte, 32)
 256  	carolContactID[0] = 0x20
 257  	carolContactSig := make([]byte, 64)
 258  	carolContactSig[0] = 0x20
 259  	carolContact := &event.E{
 260  		ID:        carolContactID,
 261  		Pubkey:    carol,
 262  		CreatedAt: 1234567891,
 263  		Kind:      3,
 264  		Content:   []byte(""),
 265  		Sig:       carolContactSig,
 266  		Tags: tag.NewS(
 267  			tag.NewFromAny("p", hex.Enc(alice)),
 268  		),
 269  	}
 270  	_, err = db.SaveEvent(ctx, carolContact)
 271  	if err != nil {
 272  		t.Fatalf("Failed to save Carol's contact list: %v", err)
 273  	}
 274  
 275  	// Find Alice's followers
 276  	result, err := db.TraverseFollowers(alice, 1)
 277  	if err != nil {
 278  		t.Fatalf("TraverseFollowers failed: %v", err)
 279  	}
 280  
 281  	if result.TotalPubkeys != 2 {
 282  		t.Errorf("Expected 2 followers, got %d", result.TotalPubkeys)
 283  	}
 284  
 285  	followers := result.GetPubkeysAtDepth(1)
 286  	followerSet := make(map[string]bool)
 287  	for _, pk := range followers {
 288  		followerSet[pk] = true
 289  	}
 290  	if !followerSet[hex.Enc(bob)] {
 291  		t.Error("Bob should be a follower")
 292  	}
 293  	if !followerSet[hex.Enc(carol)] {
 294  		t.Error("Carol should be a follower")
 295  	}
 296  }
 297  
 298  func TestTraverseFollowsNonExistent(t *testing.T) {
 299  	ctx, cancel := context.WithCancel(context.Background())
 300  	defer cancel()
 301  
 302  	db, err := New(ctx, cancel, t.TempDir(), "info")
 303  	if err != nil {
 304  		t.Fatalf("Failed to create database: %v", err)
 305  	}
 306  	defer db.Close()
 307  
 308  	// Try to traverse from a pubkey that doesn't exist
 309  	nonExistent, _ := hex.Dec("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
 310  	result, err := db.TraverseFollows(nonExistent, 2)
 311  	if err != nil {
 312  		t.Fatalf("TraverseFollows should not error for non-existent pubkey: %v", err)
 313  	}
 314  
 315  	if result.TotalPubkeys != 0 {
 316  		t.Errorf("Expected 0 pubkeys for non-existent seed, got %d", result.TotalPubkeys)
 317  	}
 318  }
 319