grapevine_test.mx raw

   1  package grapevine
   2  
   3  import (
   4  	"testing"
   5  
   6  	"smesh.lol/pkg/nostr/event"
   7  	"smesh.lol/pkg/nostr/kind"
   8  	"smesh.lol/pkg/nostr/signer/p8k"
   9  	"smesh.lol/pkg/nostr/tag"
  10  	"smesh.lol/pkg/store"
  11  )
  12  
  13  func openTmp(t *testing.T) *store.Engine {
  14  	t.Helper()
  15  	eng, err := store.Open(t.TempDir())
  16  	if err != nil {
  17  		t.Fatal(err)
  18  	}
  19  	t.Cleanup(func() { eng.Close() })
  20  	return eng
  21  }
  22  
  23  func genSigner(t *testing.T) *p8k.Signer {
  24  	t.Helper()
  25  	s := p8k.MustNew()
  26  	if err := s.Generate(); err != nil {
  27  		t.Fatal(err)
  28  	}
  29  	return s
  30  }
  31  
  32  func binPubkey(s *p8k.Signer) []byte {
  33  	pk := []byte{:33}
  34  	copy(pk, s.Pub())
  35  	// pk[32] = 0 already
  36  	return pk
  37  }
  38  
  39  func makeFollowList(t *testing.T, signer *p8k.Signer, follows ...*p8k.Signer) *event.E {
  40  	t.Helper()
  41  	tags := tag.NewSWithCap(len(follows))
  42  	for _, f := range follows {
  43  		*tags = append(*tags, tag.NewFromBytesSlice([]byte("p"), binPubkey(f)))
  44  	}
  45  	ev := &event.E{
  46  		CreatedAt: 1700000000,
  47  		Kind:      kind.FollowList.K,
  48  		Tags:      tags,
  49  	}
  50  	if err := ev.Sign(signer); err != nil {
  51  		t.Fatal(err)
  52  	}
  53  	return ev
  54  }
  55  
  56  func TestComputeDepth1(t *testing.T) {
  57  	eng := openTmp(t)
  58  	seed := genSigner(t)
  59  	alice := genSigner(t)
  60  	bob := genSigner(t)
  61  
  62  	// seed follows alice and bob
  63  	if err := eng.SaveEvent(makeFollowList(t, seed, alice, bob)); err != nil {
  64  		t.Fatal(err)
  65  	}
  66  
  67  	w := New(eng)
  68  	scores := w.Compute(seed.Pub(), 1)
  69  	if len(scores) != 2 {
  70  		t.Fatalf("expected 2 scores, got %d", len(scores))
  71  	}
  72  	// depth 1 decay = 1/2^0 = 1.0
  73  	for _, sc := range scores {
  74  		if sc.Value != 1.0 {
  75  			t.Errorf("expected score 1.0, got %f", sc.Value)
  76  		}
  77  		if sc.Depth != 1 {
  78  			t.Errorf("expected depth 1, got %d", sc.Depth)
  79  		}
  80  	}
  81  }
  82  
  83  func TestComputeDepth2(t *testing.T) {
  84  	eng := openTmp(t)
  85  	seed := genSigner(t)
  86  	alice := genSigner(t)
  87  	bob := genSigner(t)
  88  
  89  	// seed -> alice -> bob
  90  	if err := eng.SaveEvent(makeFollowList(t, seed, alice)); err != nil {
  91  		t.Fatal(err)
  92  	}
  93  	if err := eng.SaveEvent(makeFollowList(t, alice, bob)); err != nil {
  94  		t.Fatal(err)
  95  	}
  96  
  97  	w := New(eng)
  98  	scores := w.Compute(seed.Pub(), 2)
  99  
 100  	found := false
 101  	for _, sc := range scores {
 102  		if string(sc.Pubkey) == string(bob.Pub()) {
 103  			found = true
 104  			// depth 2 decay = 1/2^1 = 0.5
 105  			if sc.Value != 0.5 {
 106  				t.Errorf("expected bob score 0.5, got %f", sc.Value)
 107  			}
 108  		}
 109  	}
 110  	if !found {
 111  		t.Fatal("bob not found in scores")
 112  	}
 113  }
 114  
 115  func TestIsTrusted(t *testing.T) {
 116  	scores := []Score{
 117  		{Pubkey: []byte("aaa"), Value: 0.8},
 118  		{Pubkey: []byte("bbb"), Value: 0.3},
 119  	}
 120  	if !IsTrusted(scores, []byte("aaa"), 0.5) {
 121  		t.Error("aaa should be trusted at 0.5 threshold")
 122  	}
 123  	if IsTrusted(scores, []byte("bbb"), 0.5) {
 124  		t.Error("bbb should not be trusted at 0.5 threshold")
 125  	}
 126  	if IsTrusted(scores, []byte("ccc"), 0.1) {
 127  		t.Error("unknown pubkey should not be trusted")
 128  	}
 129  }
 130