pubkey-graph_test.go raw
1 package database
2
3 import (
4 "context"
5 "testing"
6
7 "github.com/dgraph-io/badger/v4"
8 "next.orly.dev/pkg/database/indexes"
9 "next.orly.dev/pkg/database/indexes/types"
10 "next.orly.dev/pkg/nostr/encoders/event"
11 "next.orly.dev/pkg/nostr/encoders/hex"
12 "next.orly.dev/pkg/nostr/encoders/tag"
13 )
14
15 func TestPubkeySerialAssignment(t *testing.T) {
16 ctx, cancel := context.WithCancel(context.Background())
17 defer cancel()
18
19 db, err := New(ctx, cancel, t.TempDir(), "info")
20 if err != nil {
21 t.Fatalf("Failed to create database: %v", err)
22 }
23 defer db.Close()
24
25 // Create a test pubkey
26 pubkey1 := make([]byte, 32)
27 for i := range pubkey1 {
28 pubkey1[i] = byte(i)
29 }
30
31 // Get or create serial for the first time
32 t.Logf("First call: GetOrCreatePubkeySerial for pubkey %s", hex.Enc(pubkey1))
33 ser1, err := db.GetOrCreatePubkeySerial(pubkey1)
34 if err != nil {
35 t.Fatalf("Failed to get or create pubkey serial: %v", err)
36 }
37
38 if ser1 == nil {
39 t.Fatal("Serial should not be nil")
40 }
41 t.Logf("First call returned serial: %d", ser1.Get())
42
43 // Debug: List all keys in database
44 var keyCount int
45 db.View(func(txn *badger.Txn) error {
46 it := txn.NewIterator(badger.DefaultIteratorOptions)
47 defer it.Close()
48 for it.Rewind(); it.Valid(); it.Next() {
49 key := it.Item().KeyCopy(nil)
50 t.Logf("Found key: %s (len=%d)", hex.Enc(key), len(key))
51 keyCount++
52 if keyCount > 20 {
53 break // Limit output
54 }
55 }
56 return nil
57 })
58 t.Logf("Total keys found (first 20): %d", keyCount)
59
60 // Debug: what prefix should we be looking for?
61 pubHash := new(types.PubHash)
62 pubHash.FromPubkey(pubkey1)
63 expectedPrefix := []byte(indexes.PubkeySerialPrefix)
64 t.Logf("Expected PubkeySerial prefix: %s = %s", string(expectedPrefix), hex.Enc(expectedPrefix))
65
66 // Try direct lookup
67 t.Logf("Direct lookup: GetPubkeySerial for same pubkey")
68 serDirect, err := db.GetPubkeySerial(pubkey1)
69 if err != nil {
70 t.Logf("Direct lookup failed: %v", err)
71 } else {
72 t.Logf("Direct lookup returned serial: %d", serDirect.Get())
73 }
74
75 // Get the same pubkey again - should return the same serial
76 t.Logf("Second call: GetOrCreatePubkeySerial for same pubkey")
77 ser2, err := db.GetOrCreatePubkeySerial(pubkey1)
78 if err != nil {
79 t.Fatalf("Failed to get existing pubkey serial: %v", err)
80 }
81 t.Logf("Second call returned serial: %d", ser2.Get())
82
83 if ser1.Get() != ser2.Get() {
84 t.Errorf("Expected same serial, got %d and %d", ser1.Get(), ser2.Get())
85 }
86
87 // Create a different pubkey
88 pubkey2 := make([]byte, 32)
89 for i := range pubkey2 {
90 pubkey2[i] = byte(i + 100)
91 }
92
93 ser3, err := db.GetOrCreatePubkeySerial(pubkey2)
94 if err != nil {
95 t.Fatalf("Failed to get or create second pubkey serial: %v", err)
96 }
97
98 if ser3.Get() == ser1.Get() {
99 t.Error("Different pubkeys should have different serials")
100 }
101
102 // Test reverse lookup: serial -> pubkey
103 retrievedPubkey1, err := db.GetPubkeyBySerial(ser1)
104 if err != nil {
105 t.Fatalf("Failed to get pubkey by serial: %v", err)
106 }
107
108 if hex.Enc(retrievedPubkey1) != hex.Enc(pubkey1) {
109 t.Errorf("Retrieved pubkey doesn't match. Expected %s, got %s",
110 hex.Enc(pubkey1), hex.Enc(retrievedPubkey1))
111 }
112 }
113
114 func TestEventPubkeyGraph(t *testing.T) {
115 ctx, cancel := context.WithCancel(context.Background())
116 defer cancel()
117
118 db, err := New(ctx, cancel, t.TempDir(), "info")
119 if err != nil {
120 t.Fatalf("Failed to create database: %v", err)
121 }
122 defer db.Close()
123
124 // Create test event with author and p-tags
125 authorPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
126 pTagPubkey1, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
127 pTagPubkey2, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003")
128
129 eventID := make([]byte, 32)
130 eventID[0] = 1
131 eventSig := make([]byte, 64)
132 eventSig[0] = 1
133
134 // Create a valid e-tag event ID (32 bytes = 64 hex chars)
135 eTagEventID := make([]byte, 32)
136 eTagEventID[0] = 0xAB
137
138 ev := &event.E{
139 ID: eventID,
140 Pubkey: authorPubkey,
141 CreatedAt: 1234567890,
142 Kind: 1, // text note
143 Content: []byte("Test event with p-tags"),
144 Sig: eventSig,
145 Tags: tag.NewS(
146 tag.NewFromAny("p", hex.Enc(pTagPubkey1)),
147 tag.NewFromAny("p", hex.Enc(pTagPubkey2)),
148 tag.NewFromAny("e", hex.Enc(eTagEventID)),
149 ),
150 }
151
152 // Save the event - this should create pubkey serials and graph edges
153 _, err = db.SaveEvent(ctx, ev)
154 if err != nil {
155 t.Fatalf("Failed to save event: %v", err)
156 }
157
158 // Verify that pubkey serials were created
159 authorSerial, err := db.GetPubkeySerial(authorPubkey)
160 if err != nil {
161 t.Fatalf("Failed to get author pubkey serial: %v", err)
162 }
163 if authorSerial == nil {
164 t.Fatal("Author serial should not be nil")
165 }
166
167 pTag1Serial, err := db.GetPubkeySerial(pTagPubkey1)
168 if err != nil {
169 t.Fatalf("Failed to get p-tag1 pubkey serial: %v", err)
170 }
171 if pTag1Serial == nil {
172 t.Fatal("P-tag1 serial should not be nil")
173 }
174
175 pTag2Serial, err := db.GetPubkeySerial(pTagPubkey2)
176 if err != nil {
177 t.Fatalf("Failed to get p-tag2 pubkey serial: %v", err)
178 }
179 if pTag2Serial == nil {
180 t.Fatal("P-tag2 serial should not be nil")
181 }
182
183 // Verify all three pubkeys have different serials
184 if authorSerial.Get() == pTag1Serial.Get() || authorSerial.Get() == pTag2Serial.Get() || pTag1Serial.Get() == pTag2Serial.Get() {
185 t.Error("All pubkey serials should be unique")
186 }
187
188 t.Logf("Event saved successfully with graph edges:")
189 t.Logf(" Author serial: %d", authorSerial.Get())
190 t.Logf(" P-tag1 serial: %d", pTag1Serial.Get())
191 t.Logf(" P-tag2 serial: %d", pTag2Serial.Get())
192 }
193
194 func TestMultipleEventsWithSamePubkeys(t *testing.T) {
195 ctx, cancel := context.WithCancel(context.Background())
196 defer cancel()
197
198 db, err := New(ctx, cancel, t.TempDir(), "info")
199 if err != nil {
200 t.Fatalf("Failed to create database: %v", err)
201 }
202 defer db.Close()
203
204 // Create two events from the same author mentioning the same person
205 authorPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
206 pTagPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
207
208 eventID1 := make([]byte, 32)
209 eventID1[0] = 1
210 eventSig1 := make([]byte, 64)
211 eventSig1[0] = 1
212
213 ev1 := &event.E{
214 ID: eventID1,
215 Pubkey: authorPubkey,
216 CreatedAt: 1234567890,
217 Kind: 1,
218 Content: []byte("First event"),
219 Sig: eventSig1,
220 Tags: tag.NewS(
221 tag.NewFromAny("p", hex.Enc(pTagPubkey)),
222 ),
223 }
224
225 eventID2 := make([]byte, 32)
226 eventID2[0] = 2
227 eventSig2 := make([]byte, 64)
228 eventSig2[0] = 2
229
230 ev2 := &event.E{
231 ID: eventID2,
232 Pubkey: authorPubkey,
233 CreatedAt: 1234567891,
234 Kind: 1,
235 Content: []byte("Second event"),
236 Sig: eventSig2,
237 Tags: tag.NewS(
238 tag.NewFromAny("p", hex.Enc(pTagPubkey)),
239 ),
240 }
241
242 // Save both events
243 _, err = db.SaveEvent(ctx, ev1)
244 if err != nil {
245 t.Fatalf("Failed to save event 1: %v", err)
246 }
247
248 _, err = db.SaveEvent(ctx, ev2)
249 if err != nil {
250 t.Fatalf("Failed to save event 2: %v", err)
251 }
252
253 // Verify the same pubkeys got the same serials
254 authorSerial1, _ := db.GetPubkeySerial(authorPubkey)
255 pTagSerial1, _ := db.GetPubkeySerial(pTagPubkey)
256
257 if authorSerial1 == nil || pTagSerial1 == nil {
258 t.Fatal("Pubkey serials should exist after saving events")
259 }
260
261 t.Logf("Both events share the same pubkey serials:")
262 t.Logf(" Author serial: %d", authorSerial1.Get())
263 t.Logf(" P-tag serial: %d", pTagSerial1.Get())
264 }
265
266 func TestPubkeySerialEdgeCases(t *testing.T) {
267 ctx, cancel := context.WithCancel(context.Background())
268 defer cancel()
269
270 db, err := New(ctx, cancel, t.TempDir(), "info")
271 if err != nil {
272 t.Fatalf("Failed to create database: %v", err)
273 }
274 defer db.Close()
275
276 // Test with invalid pubkey length
277 invalidPubkey := make([]byte, 16) // Wrong length
278 _, err = db.GetOrCreatePubkeySerial(invalidPubkey)
279 if err == nil {
280 t.Error("Should reject pubkey with invalid length")
281 }
282
283 // Test GetPubkeySerial for non-existent pubkey
284 nonExistentPubkey := make([]byte, 32)
285 for i := range nonExistentPubkey {
286 nonExistentPubkey[i] = 0xFF
287 }
288
289 _, err = db.GetPubkeySerial(nonExistentPubkey)
290 if err == nil {
291 t.Error("Should return error for non-existent pubkey serial")
292 }
293 }
294
295 func TestGraphEdgeDirections(t *testing.T) {
296 ctx, cancel := context.WithCancel(context.Background())
297 defer cancel()
298
299 db, err := New(ctx, cancel, t.TempDir(), "info")
300 if err != nil {
301 t.Fatalf("Failed to create database: %v", err)
302 }
303 defer db.Close()
304
305 // Create test event with author and p-tags
306 authorPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
307 pTagPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
308
309 eventID := make([]byte, 32)
310 eventID[0] = 1
311 eventSig := make([]byte, 64)
312 eventSig[0] = 1
313
314 ev := &event.E{
315 ID: eventID,
316 Pubkey: authorPubkey,
317 CreatedAt: 1234567890,
318 Kind: 1, // text note
319 Content: []byte("Test event"),
320 Sig: eventSig,
321 Tags: tag.NewS(
322 tag.NewFromAny("p", hex.Enc(pTagPubkey)),
323 ),
324 }
325
326 // Save the event
327 _, err = db.SaveEvent(ctx, ev)
328 if err != nil {
329 t.Fatalf("Failed to save event: %v", err)
330 }
331
332 // Verify graph edges with correct direction bytes
333 // Look for PubkeyEventGraph keys and check direction byte
334 var foundAuthorEdge, foundPTagEdge bool
335 db.View(func(txn *badger.Txn) error {
336 it := txn.NewIterator(badger.DefaultIteratorOptions)
337 defer it.Close()
338
339 prefix := []byte(indexes.PubkeyEventGraphPrefix)
340 for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
341 key := it.Item().KeyCopy(nil)
342 // Key format: peg(3)|pubkey_serial(5)|kind(2)|direction(1)|event_serial(5) = 16 bytes
343 if len(key) == 16 {
344 direction := key[10] // Byte at position 10 is the direction
345 t.Logf("Found PubkeyEventGraph edge: key=%s, direction=%d", hex.Enc(key), direction)
346
347 if direction == types.EdgeDirectionAuthor {
348 foundAuthorEdge = true
349 t.Logf(" ✓ Found author edge (direction=0)")
350 } else if direction == types.EdgeDirectionPTagIn {
351 foundPTagEdge = true
352 t.Logf(" ✓ Found p-tag inbound edge (direction=2)")
353 }
354 }
355 }
356 return nil
357 })
358
359 if !foundAuthorEdge {
360 t.Error("Did not find author edge with direction=0")
361 }
362 if !foundPTagEdge {
363 t.Error("Did not find p-tag inbound edge with direction=2")
364 }
365
366 t.Logf("Graph edges correctly stored with direction bytes:")
367 t.Logf(" Author edge: %v (direction=0)", foundAuthorEdge)
368 t.Logf(" P-tag inbound edge: %v (direction=2)", foundPTagEdge)
369 }
370