You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
547 lines
14 KiB
547 lines
14 KiB
//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) |
|
} |
|
}
|
|
|