graph-traversal_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 TestGetPTagsFromEventSerial(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 an author pubkey
25 authorPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
26
27 // Create p-tag target pubkeys
28 target1, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
29 target2, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003")
30
31 // Create event with p-tags
32 eventID := make([]byte, 32)
33 eventID[0] = 0x10
34 eventSig := make([]byte, 64)
35 eventSig[0] = 0x10
36
37 ev := &event.E{
38 ID: eventID,
39 Pubkey: authorPubkey,
40 CreatedAt: 1234567890,
41 Kind: 1,
42 Content: []byte("Test event with p-tags"),
43 Sig: eventSig,
44 Tags: tag.NewS(
45 tag.NewFromAny("p", hex.Enc(target1)),
46 tag.NewFromAny("p", hex.Enc(target2)),
47 ),
48 }
49
50 _, err = db.SaveEvent(ctx, ev)
51 if err != nil {
52 t.Fatalf("Failed to save event: %v", err)
53 }
54
55 // Get the event serial
56 eventSerial, err := db.GetSerialById(eventID)
57 if err != nil {
58 t.Fatalf("Failed to get event serial: %v", err)
59 }
60
61 // Get p-tags from event serial
62 ptagSerials, err := db.GetPTagsFromEventSerial(eventSerial)
63 if err != nil {
64 t.Fatalf("GetPTagsFromEventSerial failed: %v", err)
65 }
66
67 // Should have 2 p-tags
68 if len(ptagSerials) != 2 {
69 t.Errorf("Expected 2 p-tag serials, got %d", len(ptagSerials))
70 }
71
72 // Verify the pubkeys
73 for _, serial := range ptagSerials {
74 pubkey, err := db.GetPubkeyBySerial(serial)
75 if err != nil {
76 t.Errorf("Failed to get pubkey for serial: %v", err)
77 continue
78 }
79 pubkeyHex := hex.Enc(pubkey)
80 if pubkeyHex != hex.Enc(target1) && pubkeyHex != hex.Enc(target2) {
81 t.Errorf("Unexpected pubkey: %s", pubkeyHex)
82 }
83 }
84 }
85
86 func TestGetETagsFromEventSerial(t *testing.T) {
87 ctx, cancel := context.WithCancel(context.Background())
88 defer cancel()
89
90 db, err := New(ctx, cancel, t.TempDir(), "info")
91 if err != nil {
92 t.Fatalf("Failed to create database: %v", err)
93 }
94 defer db.Close()
95
96 // Create a parent event
97 parentPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
98 parentID := make([]byte, 32)
99 parentID[0] = 0x10
100 parentSig := make([]byte, 64)
101 parentSig[0] = 0x10
102
103 parentEvent := &event.E{
104 ID: parentID,
105 Pubkey: parentPubkey,
106 CreatedAt: 1234567890,
107 Kind: 1,
108 Content: []byte("Parent post"),
109 Sig: parentSig,
110 Tags: &tag.S{},
111 }
112 _, err = db.SaveEvent(ctx, parentEvent)
113 if err != nil {
114 t.Fatalf("Failed to save parent event: %v", err)
115 }
116
117 // Create a reply event with e-tag
118 replyPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
119 replyID := make([]byte, 32)
120 replyID[0] = 0x20
121 replySig := make([]byte, 64)
122 replySig[0] = 0x20
123
124 replyEvent := &event.E{
125 ID: replyID,
126 Pubkey: replyPubkey,
127 CreatedAt: 1234567891,
128 Kind: 1,
129 Content: []byte("Reply"),
130 Sig: replySig,
131 Tags: tag.NewS(
132 tag.NewFromAny("e", hex.Enc(parentID)),
133 ),
134 }
135 _, err = db.SaveEvent(ctx, replyEvent)
136 if err != nil {
137 t.Fatalf("Failed to save reply event: %v", err)
138 }
139
140 // Get e-tags from reply
141 replySerial, _ := db.GetSerialById(replyID)
142 etagSerials, err := db.GetETagsFromEventSerial(replySerial)
143 if err != nil {
144 t.Fatalf("GetETagsFromEventSerial failed: %v", err)
145 }
146
147 if len(etagSerials) != 1 {
148 t.Errorf("Expected 1 e-tag serial, got %d", len(etagSerials))
149 }
150
151 // Verify the target event
152 if len(etagSerials) > 0 {
153 targetEventID, err := db.GetEventIdBySerial(etagSerials[0])
154 if err != nil {
155 t.Fatalf("Failed to get event ID from serial: %v", err)
156 }
157 if hex.Enc(targetEventID) != hex.Enc(parentID) {
158 t.Errorf("Expected parent ID, got %s", hex.Enc(targetEventID))
159 }
160 }
161 }
162
163 func TestGetReferencingEvents(t *testing.T) {
164 ctx, cancel := context.WithCancel(context.Background())
165 defer cancel()
166
167 db, err := New(ctx, cancel, t.TempDir(), "info")
168 if err != nil {
169 t.Fatalf("Failed to create database: %v", err)
170 }
171 defer db.Close()
172
173 // Create a parent event
174 parentPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
175 parentID := make([]byte, 32)
176 parentID[0] = 0x10
177 parentSig := make([]byte, 64)
178 parentSig[0] = 0x10
179
180 parentEvent := &event.E{
181 ID: parentID,
182 Pubkey: parentPubkey,
183 CreatedAt: 1234567890,
184 Kind: 1,
185 Content: []byte("Parent post"),
186 Sig: parentSig,
187 Tags: &tag.S{},
188 }
189 _, err = db.SaveEvent(ctx, parentEvent)
190 if err != nil {
191 t.Fatalf("Failed to save parent event: %v", err)
192 }
193
194 // Create multiple replies and reactions
195 for i := 0; i < 3; i++ {
196 replyPubkey := make([]byte, 32)
197 replyPubkey[0] = byte(0x20 + i)
198 replyID := make([]byte, 32)
199 replyID[0] = byte(0x30 + i)
200 replySig := make([]byte, 64)
201 replySig[0] = byte(0x30 + i)
202
203 var evKind uint16 = 1 // Reply
204 if i == 2 {
205 evKind = 7 // Reaction
206 }
207
208 replyEvent := &event.E{
209 ID: replyID,
210 Pubkey: replyPubkey,
211 CreatedAt: int64(1234567891 + i),
212 Kind: evKind,
213 Content: []byte("Response"),
214 Sig: replySig,
215 Tags: tag.NewS(
216 tag.NewFromAny("e", hex.Enc(parentID)),
217 ),
218 }
219 _, err = db.SaveEvent(ctx, replyEvent)
220 if err != nil {
221 t.Fatalf("Failed to save reply %d: %v", i, err)
222 }
223 }
224
225 // Get parent serial
226 parentSerial, _ := db.GetSerialById(parentID)
227
228 // Test without kind filter
229 refs, err := db.GetReferencingEvents(parentSerial, nil)
230 if err != nil {
231 t.Fatalf("GetReferencingEvents failed: %v", err)
232 }
233 if len(refs) != 3 {
234 t.Errorf("Expected 3 referencing events, got %d", len(refs))
235 }
236
237 // Test with kind filter (only replies)
238 refs, err = db.GetReferencingEvents(parentSerial, []uint16{1})
239 if err != nil {
240 t.Fatalf("GetReferencingEvents with kind filter failed: %v", err)
241 }
242 if len(refs) != 2 {
243 t.Errorf("Expected 2 kind-1 referencing events, got %d", len(refs))
244 }
245
246 // Test with kind filter (only reactions)
247 refs, err = db.GetReferencingEvents(parentSerial, []uint16{7})
248 if err != nil {
249 t.Fatalf("GetReferencingEvents with kind 7 filter failed: %v", err)
250 }
251 if len(refs) != 1 {
252 t.Errorf("Expected 1 kind-7 referencing event, got %d", len(refs))
253 }
254 }
255
256 func TestGetFollowsFromPubkeySerial(t *testing.T) {
257 ctx, cancel := context.WithCancel(context.Background())
258 defer cancel()
259
260 db, err := New(ctx, cancel, t.TempDir(), "info")
261 if err != nil {
262 t.Fatalf("Failed to create database: %v", err)
263 }
264 defer db.Close()
265
266 // Create author and their follows
267 authorPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
268 follow1, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
269 follow2, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003")
270 follow3, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000004")
271
272 // Create kind-3 contact list
273 eventID := make([]byte, 32)
274 eventID[0] = 0x10
275 eventSig := make([]byte, 64)
276 eventSig[0] = 0x10
277
278 contactList := &event.E{
279 ID: eventID,
280 Pubkey: authorPubkey,
281 CreatedAt: 1234567890,
282 Kind: 3,
283 Content: []byte(""),
284 Sig: eventSig,
285 Tags: tag.NewS(
286 tag.NewFromAny("p", hex.Enc(follow1)),
287 tag.NewFromAny("p", hex.Enc(follow2)),
288 tag.NewFromAny("p", hex.Enc(follow3)),
289 ),
290 }
291 _, err = db.SaveEvent(ctx, contactList)
292 if err != nil {
293 t.Fatalf("Failed to save contact list: %v", err)
294 }
295
296 // Get author serial
297 authorSerial, err := db.GetPubkeySerial(authorPubkey)
298 if err != nil {
299 t.Fatalf("Failed to get author serial: %v", err)
300 }
301
302 // Get follows
303 follows, err := db.GetFollowsFromPubkeySerial(authorSerial)
304 if err != nil {
305 t.Fatalf("GetFollowsFromPubkeySerial failed: %v", err)
306 }
307
308 if len(follows) != 3 {
309 t.Errorf("Expected 3 follows, got %d", len(follows))
310 }
311
312 // Verify the follows are correct
313 expectedFollows := map[string]bool{
314 hex.Enc(follow1): false,
315 hex.Enc(follow2): false,
316 hex.Enc(follow3): false,
317 }
318 for _, serial := range follows {
319 pubkey, err := db.GetPubkeyBySerial(serial)
320 if err != nil {
321 t.Errorf("Failed to get pubkey from serial: %v", err)
322 continue
323 }
324 pkHex := hex.Enc(pubkey)
325 if _, exists := expectedFollows[pkHex]; exists {
326 expectedFollows[pkHex] = true
327 } else {
328 t.Errorf("Unexpected follow: %s", pkHex)
329 }
330 }
331 for pk, found := range expectedFollows {
332 if !found {
333 t.Errorf("Expected follow not found: %s", pk)
334 }
335 }
336 }
337
338 func TestGraphResult(t *testing.T) {
339 result := NewGraphResult()
340
341 // Add pubkeys at different depths
342 result.AddPubkeyAtDepth("pubkey1", 1)
343 result.AddPubkeyAtDepth("pubkey2", 1)
344 result.AddPubkeyAtDepth("pubkey3", 2)
345 result.AddPubkeyAtDepth("pubkey4", 2)
346 result.AddPubkeyAtDepth("pubkey5", 3)
347
348 // Try to add duplicate
349 added := result.AddPubkeyAtDepth("pubkey1", 2)
350 if added {
351 t.Error("Should not add duplicate pubkey")
352 }
353
354 // Verify counts
355 if result.TotalPubkeys != 5 {
356 t.Errorf("Expected 5 total pubkeys, got %d", result.TotalPubkeys)
357 }
358
359 // Verify depth tracking
360 if result.GetPubkeyDepth("pubkey1") != 1 {
361 t.Errorf("pubkey1 should be at depth 1")
362 }
363 if result.GetPubkeyDepth("pubkey3") != 2 {
364 t.Errorf("pubkey3 should be at depth 2")
365 }
366
367 // Verify HasPubkey
368 if !result.HasPubkey("pubkey1") {
369 t.Error("Should have pubkey1")
370 }
371 if result.HasPubkey("nonexistent") {
372 t.Error("Should not have nonexistent pubkey")
373 }
374
375 // Verify ToDepthArrays
376 arrays := result.ToDepthArrays()
377 if len(arrays) != 3 {
378 t.Errorf("Expected 3 depth arrays, got %d", len(arrays))
379 }
380 if len(arrays[0]) != 2 {
381 t.Errorf("Expected 2 pubkeys at depth 1, got %d", len(arrays[0]))
382 }
383 if len(arrays[1]) != 2 {
384 t.Errorf("Expected 2 pubkeys at depth 2, got %d", len(arrays[1]))
385 }
386 if len(arrays[2]) != 1 {
387 t.Errorf("Expected 1 pubkey at depth 3, got %d", len(arrays[2]))
388 }
389 }
390
391 func TestGraphResultRefs(t *testing.T) {
392 result := NewGraphResult()
393
394 // Add some pubkeys
395 result.AddPubkeyAtDepth("pubkey1", 1)
396 result.AddEventAtDepth("event1", 1)
397
398 // Add inbound refs (kind 7 reactions)
399 result.AddInboundRef(7, "event1", "reaction1")
400 result.AddInboundRef(7, "event1", "reaction2")
401 result.AddInboundRef(7, "event1", "reaction3")
402
403 // Get sorted refs
404 refs := result.GetInboundRefsSorted(7)
405 if len(refs) != 1 {
406 t.Fatalf("Expected 1 aggregation, got %d", len(refs))
407 }
408 if refs[0].RefCount != 3 {
409 t.Errorf("Expected 3 refs, got %d", refs[0].RefCount)
410 }
411 if refs[0].TargetEventID != "event1" {
412 t.Errorf("Expected event1, got %s", refs[0].TargetEventID)
413 }
414 }
415
416 func TestGetFollowersOfPubkeySerial(t *testing.T) {
417 ctx, cancel := context.WithCancel(context.Background())
418 defer cancel()
419
420 db, err := New(ctx, cancel, t.TempDir(), "info")
421 if err != nil {
422 t.Fatalf("Failed to create database: %v", err)
423 }
424 defer db.Close()
425
426 // Create target pubkey (the one being followed)
427 targetPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
428
429 // Create followers
430 follower1, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
431 follower2, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003")
432
433 // Create kind-3 contact lists for followers
434 for i, followerPubkey := range [][]byte{follower1, follower2} {
435 eventID := make([]byte, 32)
436 eventID[0] = byte(0x10 + i)
437 eventSig := make([]byte, 64)
438 eventSig[0] = byte(0x10 + i)
439
440 contactList := &event.E{
441 ID: eventID,
442 Pubkey: followerPubkey,
443 CreatedAt: int64(1234567890 + i),
444 Kind: 3,
445 Content: []byte(""),
446 Sig: eventSig,
447 Tags: tag.NewS(
448 tag.NewFromAny("p", hex.Enc(targetPubkey)),
449 ),
450 }
451 _, err = db.SaveEvent(ctx, contactList)
452 if err != nil {
453 t.Fatalf("Failed to save contact list %d: %v", i, err)
454 }
455 }
456
457 // Get target serial
458 targetSerial, err := db.GetPubkeySerial(targetPubkey)
459 if err != nil {
460 t.Fatalf("Failed to get target serial: %v", err)
461 }
462
463 // Get followers
464 followers, err := db.GetFollowersOfPubkeySerial(targetSerial)
465 if err != nil {
466 t.Fatalf("GetFollowersOfPubkeySerial failed: %v", err)
467 }
468
469 if len(followers) != 2 {
470 t.Errorf("Expected 2 followers, got %d", len(followers))
471 }
472
473 // Verify the followers
474 expectedFollowers := map[string]bool{
475 hex.Enc(follower1): false,
476 hex.Enc(follower2): false,
477 }
478 for _, serial := range followers {
479 pubkey, err := db.GetPubkeyBySerial(serial)
480 if err != nil {
481 t.Errorf("Failed to get pubkey from serial: %v", err)
482 continue
483 }
484 pkHex := hex.Enc(pubkey)
485 if _, exists := expectedFollowers[pkHex]; exists {
486 expectedFollowers[pkHex] = true
487 } else {
488 t.Errorf("Unexpected follower: %s", pkHex)
489 }
490 }
491 for pk, found := range expectedFollowers {
492 if !found {
493 t.Errorf("Expected follower not found: %s", pk)
494 }
495 }
496 }
497
498 func TestPubkeyHexToSerial(t *testing.T) {
499 ctx, cancel := context.WithCancel(context.Background())
500 defer cancel()
501
502 db, err := New(ctx, cancel, t.TempDir(), "info")
503 if err != nil {
504 t.Fatalf("Failed to create database: %v", err)
505 }
506 defer db.Close()
507
508 // Create a pubkey by saving an event
509 pubkeyBytes, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
510 eventID := make([]byte, 32)
511 eventID[0] = 0x10
512 eventSig := make([]byte, 64)
513 eventSig[0] = 0x10
514
515 ev := &event.E{
516 ID: eventID,
517 Pubkey: pubkeyBytes,
518 CreatedAt: 1234567890,
519 Kind: 1,
520 Content: []byte("Test"),
521 Sig: eventSig,
522 Tags: &tag.S{},
523 }
524 _, err = db.SaveEvent(ctx, ev)
525 if err != nil {
526 t.Fatalf("Failed to save event: %v", err)
527 }
528
529 // Convert hex to serial
530 pubkeyHex := hex.Enc(pubkeyBytes)
531 serial, err := db.PubkeyHexToSerial(pubkeyHex)
532 if err != nil {
533 t.Fatalf("PubkeyHexToSerial failed: %v", err)
534 }
535 if serial == nil {
536 t.Fatal("Expected non-nil serial")
537 }
538
539 // Convert back and verify
540 backToHex, err := db.GetPubkeyHexFromSerial(serial)
541 if err != nil {
542 t.Fatalf("GetPubkeyHexFromSerial failed: %v", err)
543 }
544 if backToHex != pubkeyHex {
545 t.Errorf("Round-trip failed: %s != %s", backToHex, pubkeyHex)
546 }
547 }
548