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