Files
next.orly.dev/pkg/database/graph-traversal_test.go
mleku 6b98c23606
Some checks failed
Go / build-and-release (push) Has been cancelled
add first draft graph query implementation
2025-12-04 09:28:13 +00:00

548 lines
14 KiB
Go

//go:build !(js && wasm)
package database
import (
"context"
"testing"
"git.mleku.dev/mleku/nostr/encoders/event"
"git.mleku.dev/mleku/nostr/encoders/hex"
"git.mleku.dev/mleku/nostr/encoders/tag"
)
func TestGetPTagsFromEventSerial(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, err := New(ctx, cancel, t.TempDir(), "info")
if err != nil {
t.Fatalf("Failed to create database: %v", err)
}
defer db.Close()
// Create an author pubkey
authorPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
// Create p-tag target pubkeys
target1, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
target2, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003")
// Create event with p-tags
eventID := make([]byte, 32)
eventID[0] = 0x10
eventSig := make([]byte, 64)
eventSig[0] = 0x10
ev := &event.E{
ID: eventID,
Pubkey: authorPubkey,
CreatedAt: 1234567890,
Kind: 1,
Content: []byte("Test event with p-tags"),
Sig: eventSig,
Tags: tag.NewS(
tag.NewFromAny("p", hex.Enc(target1)),
tag.NewFromAny("p", hex.Enc(target2)),
),
}
_, err = db.SaveEvent(ctx, ev)
if err != nil {
t.Fatalf("Failed to save event: %v", err)
}
// Get the event serial
eventSerial, err := db.GetSerialById(eventID)
if err != nil {
t.Fatalf("Failed to get event serial: %v", err)
}
// Get p-tags from event serial
ptagSerials, err := db.GetPTagsFromEventSerial(eventSerial)
if err != nil {
t.Fatalf("GetPTagsFromEventSerial failed: %v", err)
}
// Should have 2 p-tags
if len(ptagSerials) != 2 {
t.Errorf("Expected 2 p-tag serials, got %d", len(ptagSerials))
}
// Verify the pubkeys
for _, serial := range ptagSerials {
pubkey, err := db.GetPubkeyBySerial(serial)
if err != nil {
t.Errorf("Failed to get pubkey for serial: %v", err)
continue
}
pubkeyHex := hex.Enc(pubkey)
if pubkeyHex != hex.Enc(target1) && pubkeyHex != hex.Enc(target2) {
t.Errorf("Unexpected pubkey: %s", pubkeyHex)
}
}
}
func TestGetETagsFromEventSerial(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, err := New(ctx, cancel, t.TempDir(), "info")
if err != nil {
t.Fatalf("Failed to create database: %v", err)
}
defer db.Close()
// Create a parent event
parentPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
parentID := make([]byte, 32)
parentID[0] = 0x10
parentSig := make([]byte, 64)
parentSig[0] = 0x10
parentEvent := &event.E{
ID: parentID,
Pubkey: parentPubkey,
CreatedAt: 1234567890,
Kind: 1,
Content: []byte("Parent post"),
Sig: parentSig,
Tags: &tag.S{},
}
_, err = db.SaveEvent(ctx, parentEvent)
if err != nil {
t.Fatalf("Failed to save parent event: %v", err)
}
// Create a reply event with e-tag
replyPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
replyID := make([]byte, 32)
replyID[0] = 0x20
replySig := make([]byte, 64)
replySig[0] = 0x20
replyEvent := &event.E{
ID: replyID,
Pubkey: replyPubkey,
CreatedAt: 1234567891,
Kind: 1,
Content: []byte("Reply"),
Sig: replySig,
Tags: tag.NewS(
tag.NewFromAny("e", hex.Enc(parentID)),
),
}
_, err = db.SaveEvent(ctx, replyEvent)
if err != nil {
t.Fatalf("Failed to save reply event: %v", err)
}
// Get e-tags from reply
replySerial, _ := db.GetSerialById(replyID)
etagSerials, err := db.GetETagsFromEventSerial(replySerial)
if err != nil {
t.Fatalf("GetETagsFromEventSerial failed: %v", err)
}
if len(etagSerials) != 1 {
t.Errorf("Expected 1 e-tag serial, got %d", len(etagSerials))
}
// Verify the target event
if len(etagSerials) > 0 {
targetEventID, err := db.GetEventIdBySerial(etagSerials[0])
if err != nil {
t.Fatalf("Failed to get event ID from serial: %v", err)
}
if hex.Enc(targetEventID) != hex.Enc(parentID) {
t.Errorf("Expected parent ID, got %s", hex.Enc(targetEventID))
}
}
}
func TestGetReferencingEvents(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, err := New(ctx, cancel, t.TempDir(), "info")
if err != nil {
t.Fatalf("Failed to create database: %v", err)
}
defer db.Close()
// Create a parent event
parentPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
parentID := make([]byte, 32)
parentID[0] = 0x10
parentSig := make([]byte, 64)
parentSig[0] = 0x10
parentEvent := &event.E{
ID: parentID,
Pubkey: parentPubkey,
CreatedAt: 1234567890,
Kind: 1,
Content: []byte("Parent post"),
Sig: parentSig,
Tags: &tag.S{},
}
_, err = db.SaveEvent(ctx, parentEvent)
if err != nil {
t.Fatalf("Failed to save parent event: %v", err)
}
// Create multiple replies and reactions
for i := 0; i < 3; i++ {
replyPubkey := make([]byte, 32)
replyPubkey[0] = byte(0x20 + i)
replyID := make([]byte, 32)
replyID[0] = byte(0x30 + i)
replySig := make([]byte, 64)
replySig[0] = byte(0x30 + i)
var evKind uint16 = 1 // Reply
if i == 2 {
evKind = 7 // Reaction
}
replyEvent := &event.E{
ID: replyID,
Pubkey: replyPubkey,
CreatedAt: int64(1234567891 + i),
Kind: evKind,
Content: []byte("Response"),
Sig: replySig,
Tags: tag.NewS(
tag.NewFromAny("e", hex.Enc(parentID)),
),
}
_, err = db.SaveEvent(ctx, replyEvent)
if err != nil {
t.Fatalf("Failed to save reply %d: %v", i, err)
}
}
// Get parent serial
parentSerial, _ := db.GetSerialById(parentID)
// Test without kind filter
refs, err := db.GetReferencingEvents(parentSerial, nil)
if err != nil {
t.Fatalf("GetReferencingEvents failed: %v", err)
}
if len(refs) != 3 {
t.Errorf("Expected 3 referencing events, got %d", len(refs))
}
// Test with kind filter (only replies)
refs, err = db.GetReferencingEvents(parentSerial, []uint16{1})
if err != nil {
t.Fatalf("GetReferencingEvents with kind filter failed: %v", err)
}
if len(refs) != 2 {
t.Errorf("Expected 2 kind-1 referencing events, got %d", len(refs))
}
// Test with kind filter (only reactions)
refs, err = db.GetReferencingEvents(parentSerial, []uint16{7})
if err != nil {
t.Fatalf("GetReferencingEvents with kind 7 filter failed: %v", err)
}
if len(refs) != 1 {
t.Errorf("Expected 1 kind-7 referencing event, got %d", len(refs))
}
}
func TestGetFollowsFromPubkeySerial(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, err := New(ctx, cancel, t.TempDir(), "info")
if err != nil {
t.Fatalf("Failed to create database: %v", err)
}
defer db.Close()
// Create author and their follows
authorPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
follow1, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
follow2, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003")
follow3, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000004")
// Create kind-3 contact list
eventID := make([]byte, 32)
eventID[0] = 0x10
eventSig := make([]byte, 64)
eventSig[0] = 0x10
contactList := &event.E{
ID: eventID,
Pubkey: authorPubkey,
CreatedAt: 1234567890,
Kind: 3,
Content: []byte(""),
Sig: eventSig,
Tags: tag.NewS(
tag.NewFromAny("p", hex.Enc(follow1)),
tag.NewFromAny("p", hex.Enc(follow2)),
tag.NewFromAny("p", hex.Enc(follow3)),
),
}
_, err = db.SaveEvent(ctx, contactList)
if err != nil {
t.Fatalf("Failed to save contact list: %v", err)
}
// Get author serial
authorSerial, err := db.GetPubkeySerial(authorPubkey)
if err != nil {
t.Fatalf("Failed to get author serial: %v", err)
}
// Get follows
follows, err := db.GetFollowsFromPubkeySerial(authorSerial)
if err != nil {
t.Fatalf("GetFollowsFromPubkeySerial failed: %v", err)
}
if len(follows) != 3 {
t.Errorf("Expected 3 follows, got %d", len(follows))
}
// Verify the follows are correct
expectedFollows := map[string]bool{
hex.Enc(follow1): false,
hex.Enc(follow2): false,
hex.Enc(follow3): false,
}
for _, serial := range follows {
pubkey, err := db.GetPubkeyBySerial(serial)
if err != nil {
t.Errorf("Failed to get pubkey from serial: %v", err)
continue
}
pkHex := hex.Enc(pubkey)
if _, exists := expectedFollows[pkHex]; exists {
expectedFollows[pkHex] = true
} else {
t.Errorf("Unexpected follow: %s", pkHex)
}
}
for pk, found := range expectedFollows {
if !found {
t.Errorf("Expected follow not found: %s", pk)
}
}
}
func TestGraphResult(t *testing.T) {
result := NewGraphResult()
// Add pubkeys at different depths
result.AddPubkeyAtDepth("pubkey1", 1)
result.AddPubkeyAtDepth("pubkey2", 1)
result.AddPubkeyAtDepth("pubkey3", 2)
result.AddPubkeyAtDepth("pubkey4", 2)
result.AddPubkeyAtDepth("pubkey5", 3)
// Try to add duplicate
added := result.AddPubkeyAtDepth("pubkey1", 2)
if added {
t.Error("Should not add duplicate pubkey")
}
// Verify counts
if result.TotalPubkeys != 5 {
t.Errorf("Expected 5 total pubkeys, got %d", result.TotalPubkeys)
}
// Verify depth tracking
if result.GetPubkeyDepth("pubkey1") != 1 {
t.Errorf("pubkey1 should be at depth 1")
}
if result.GetPubkeyDepth("pubkey3") != 2 {
t.Errorf("pubkey3 should be at depth 2")
}
// Verify HasPubkey
if !result.HasPubkey("pubkey1") {
t.Error("Should have pubkey1")
}
if result.HasPubkey("nonexistent") {
t.Error("Should not have nonexistent pubkey")
}
// Verify ToDepthArrays
arrays := result.ToDepthArrays()
if len(arrays) != 3 {
t.Errorf("Expected 3 depth arrays, got %d", len(arrays))
}
if len(arrays[0]) != 2 {
t.Errorf("Expected 2 pubkeys at depth 1, got %d", len(arrays[0]))
}
if len(arrays[1]) != 2 {
t.Errorf("Expected 2 pubkeys at depth 2, got %d", len(arrays[1]))
}
if len(arrays[2]) != 1 {
t.Errorf("Expected 1 pubkey at depth 3, got %d", len(arrays[2]))
}
}
func TestGraphResultRefs(t *testing.T) {
result := NewGraphResult()
// Add some pubkeys
result.AddPubkeyAtDepth("pubkey1", 1)
result.AddEventAtDepth("event1", 1)
// Add inbound refs (kind 7 reactions)
result.AddInboundRef(7, "event1", "reaction1")
result.AddInboundRef(7, "event1", "reaction2")
result.AddInboundRef(7, "event1", "reaction3")
// Get sorted refs
refs := result.GetInboundRefsSorted(7)
if len(refs) != 1 {
t.Fatalf("Expected 1 aggregation, got %d", len(refs))
}
if refs[0].RefCount != 3 {
t.Errorf("Expected 3 refs, got %d", refs[0].RefCount)
}
if refs[0].TargetEventID != "event1" {
t.Errorf("Expected event1, got %s", refs[0].TargetEventID)
}
}
func TestGetFollowersOfPubkeySerial(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, err := New(ctx, cancel, t.TempDir(), "info")
if err != nil {
t.Fatalf("Failed to create database: %v", err)
}
defer db.Close()
// Create target pubkey (the one being followed)
targetPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
// Create followers
follower1, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
follower2, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003")
// Create kind-3 contact lists for followers
for i, followerPubkey := range [][]byte{follower1, follower2} {
eventID := make([]byte, 32)
eventID[0] = byte(0x10 + i)
eventSig := make([]byte, 64)
eventSig[0] = byte(0x10 + i)
contactList := &event.E{
ID: eventID,
Pubkey: followerPubkey,
CreatedAt: int64(1234567890 + i),
Kind: 3,
Content: []byte(""),
Sig: eventSig,
Tags: tag.NewS(
tag.NewFromAny("p", hex.Enc(targetPubkey)),
),
}
_, err = db.SaveEvent(ctx, contactList)
if err != nil {
t.Fatalf("Failed to save contact list %d: %v", i, err)
}
}
// Get target serial
targetSerial, err := db.GetPubkeySerial(targetPubkey)
if err != nil {
t.Fatalf("Failed to get target serial: %v", err)
}
// Get followers
followers, err := db.GetFollowersOfPubkeySerial(targetSerial)
if err != nil {
t.Fatalf("GetFollowersOfPubkeySerial failed: %v", err)
}
if len(followers) != 2 {
t.Errorf("Expected 2 followers, got %d", len(followers))
}
// Verify the followers
expectedFollowers := map[string]bool{
hex.Enc(follower1): false,
hex.Enc(follower2): false,
}
for _, serial := range followers {
pubkey, err := db.GetPubkeyBySerial(serial)
if err != nil {
t.Errorf("Failed to get pubkey from serial: %v", err)
continue
}
pkHex := hex.Enc(pubkey)
if _, exists := expectedFollowers[pkHex]; exists {
expectedFollowers[pkHex] = true
} else {
t.Errorf("Unexpected follower: %s", pkHex)
}
}
for pk, found := range expectedFollowers {
if !found {
t.Errorf("Expected follower not found: %s", pk)
}
}
}
func TestPubkeyHexToSerial(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, err := New(ctx, cancel, t.TempDir(), "info")
if err != nil {
t.Fatalf("Failed to create database: %v", err)
}
defer db.Close()
// Create a pubkey by saving an event
pubkeyBytes, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
eventID := make([]byte, 32)
eventID[0] = 0x10
eventSig := make([]byte, 64)
eventSig[0] = 0x10
ev := &event.E{
ID: eventID,
Pubkey: pubkeyBytes,
CreatedAt: 1234567890,
Kind: 1,
Content: []byte("Test"),
Sig: eventSig,
Tags: &tag.S{},
}
_, err = db.SaveEvent(ctx, ev)
if err != nil {
t.Fatalf("Failed to save event: %v", err)
}
// Convert hex to serial
pubkeyHex := hex.Enc(pubkeyBytes)
serial, err := db.PubkeyHexToSerial(pubkeyHex)
if err != nil {
t.Fatalf("PubkeyHexToSerial failed: %v", err)
}
if serial == nil {
t.Fatal("Expected non-nil serial")
}
// Convert back and verify
backToHex, err := db.GetPubkeyHexFromSerial(serial)
if err != nil {
t.Fatalf("GetPubkeyHexFromSerial failed: %v", err)
}
if backToHex != pubkeyHex {
t.Errorf("Round-trip failed: %s != %s", backToHex, pubkeyHex)
}
}