graph_test.go raw

   1  package nostr
   2  
   3  import (
   4  	"testing"
   5  )
   6  
   7  func makeEvent(id, pubkey string, kind int, tags [][]string) *Event {
   8  	return &Event{
   9  		ID:        id,
  10  		Pubkey:    pubkey,
  11  		CreatedAt: 1700000000,
  12  		Kind:      kind,
  13  		Tags:      tags,
  14  		Content:   "test",
  15  	}
  16  }
  17  
  18  func TestEventGraphClustering(t *testing.T) {
  19  	g := NewEventGraph()
  20  
  21  	// Two events from author A, one from author B.
  22  	g.Add(makeEvent("aaa1", "pubA", 1, nil))
  23  	g.Add(makeEvent("aaa2", "pubA", 1, nil))
  24  	g.Add(makeEvent("bbb1", "pubB", 7, nil))
  25  
  26  	g.Resolve()
  27  
  28  	s := g.Stats()
  29  	if s.TotalEvents != 3 {
  30  		t.Fatalf("expected 3 events, got %d", s.TotalEvents)
  31  	}
  32  	if s.AuthorClusters != 2 {
  33  		t.Fatalf("expected 2 author clusters, got %d", s.AuthorClusters)
  34  	}
  35  	if s.KindClusters != 2 {
  36  		t.Fatalf("expected 2 kind clusters (kind:1, kind:7), got %d", s.KindClusters)
  37  	}
  38  	if s.LargestAuthor != 2 {
  39  		t.Fatalf("expected largest author cluster = 2, got %d", s.LargestAuthor)
  40  	}
  41  }
  42  
  43  func TestEventGraphReferenceBonds(t *testing.T) {
  44  	g := NewEventGraph()
  45  
  46  	// Event bbb1 references event aaa1 via e-tag.
  47  	g.Add(makeEvent("aaa1", "pubA", 1, nil))
  48  	g.Add(makeEvent("bbb1", "pubB", 1, [][]string{{"e", "aaa1"}}))
  49  
  50  	g.Resolve()
  51  
  52  	s := g.Stats()
  53  	if s.RefBonds != 1 {
  54  		t.Fatalf("expected 1 resolved reference bond, got %d", s.RefBonds)
  55  	}
  56  	if s.Orphans != 0 {
  57  		t.Fatalf("expected 0 orphans, got %d", s.Orphans)
  58  	}
  59  
  60  	// Verify the InRef on aaa1.
  61  	node := g.Nodes["aaa1"]
  62  	if len(node.InRefs) != 1 || node.InRefs[0] != "bbb1" {
  63  		t.Fatalf("expected aaa1 to have InRef from bbb1, got %v", node.InRefs)
  64  	}
  65  }
  66  
  67  func TestEventGraphOrphans(t *testing.T) {
  68  	g := NewEventGraph()
  69  
  70  	// Event references a nonexistent event and a nonexistent pubkey.
  71  	g.Add(makeEvent("aaa1", "pubA", 1, [][]string{
  72  		{"e", "missing_event_id"},
  73  		{"p", "missing_pubkey"},
  74  	}))
  75  
  76  	g.Resolve()
  77  
  78  	s := g.Stats()
  79  	if s.Orphans != 2 {
  80  		t.Fatalf("expected 2 orphans, got %d", s.Orphans)
  81  	}
  82  
  83  	byType := g.OrphansByType()
  84  	if len(byType["event"]) != 1 {
  85  		t.Fatalf("expected 1 event orphan, got %d", len(byType["event"]))
  86  	}
  87  	if len(byType["pubkey"]) != 1 {
  88  		t.Fatalf("expected 1 pubkey orphan, got %d", len(byType["pubkey"]))
  89  	}
  90  
  91  	// Verify orphan details.
  92  	eo := byType["event"][0]
  93  	if eo.ID != "missing_event_id" || eo.RefBy != "aaa1" {
  94  		t.Fatalf("event orphan mismatch: %+v", eo)
  95  	}
  96  	po := byType["pubkey"][0]
  97  	if po.ID != "missing_pubkey" || po.RefBy != "aaa1" {
  98  		t.Fatalf("pubkey orphan mismatch: %+v", po)
  99  	}
 100  }
 101  
 102  func TestEventGraphDeduplicate(t *testing.T) {
 103  	g := NewEventGraph()
 104  
 105  	g.Add(makeEvent("aaa1", "pubA", 1, nil))
 106  	g.Add(makeEvent("aaa1", "pubA", 1, nil)) // duplicate
 107  
 108  	if len(g.Nodes) != 1 {
 109  		t.Fatalf("expected 1 node after dedup, got %d", len(g.Nodes))
 110  	}
 111  	// Author cluster should have 1 event, not 2.
 112  	if len(g.Authors["pubA"].Events) != 1 {
 113  		t.Fatalf("expected 1 event in author cluster, got %d", len(g.Authors["pubA"].Events))
 114  	}
 115  }
 116  
 117  func TestEventGraphPubkeyOrphanResolvedByPresence(t *testing.T) {
 118  	g := NewEventGraph()
 119  
 120  	// pubB mentions pubA via p-tag. pubA is present as an author.
 121  	g.Add(makeEvent("aaa1", "pubA", 1, nil))
 122  	g.Add(makeEvent("bbb1", "pubB", 1, [][]string{{"p", "pubA"}}))
 123  
 124  	g.Resolve()
 125  
 126  	s := g.Stats()
 127  	// pubA is present as an author, so the p-tag reference is NOT an orphan.
 128  	if s.Orphans != 0 {
 129  		t.Fatalf("expected 0 orphans (pubA is present), got %d", s.Orphans)
 130  	}
 131  }
 132  
 133  func TestEventGraphReplyChain(t *testing.T) {
 134  	g := NewEventGraph()
 135  
 136  	// A → B → C reply chain.
 137  	g.Add(makeEvent("ev_a", "pubA", 1, nil))
 138  	g.Add(makeEvent("ev_b", "pubB", 1, [][]string{{"e", "ev_a"}}))
 139  	g.Add(makeEvent("ev_c", "pubC", 1, [][]string{{"e", "ev_b"}}))
 140  
 141  	g.Resolve()
 142  
 143  	s := g.Stats()
 144  	if s.RefBonds != 2 {
 145  		t.Fatalf("expected 2 reference bonds in reply chain, got %d", s.RefBonds)
 146  	}
 147  	if s.Orphans != 0 {
 148  		t.Fatalf("expected 0 orphans in fully resolved chain, got %d", s.Orphans)
 149  	}
 150  }
 151