Files
at-container-registry/pkg/hold/pds/server_test.go

950 lines
28 KiB
Go

package pds
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"atcr.io/pkg/atproto"
)
// TestNewHoldPDS_NewRepo tests creating a new hold PDS with fresh database
func TestNewHoldPDS_NewRepo(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
// Paths for database and key
dbPath := filepath.Join(tmpDir, "pds.db")
keyPath := filepath.Join(tmpDir, "signing-key")
// Create new hold PDS
did := "did:web:hold.example.com"
publicURL := "https://hold.example.com"
pds, err := NewHoldPDS(ctx, did, publicURL, "https://atcr.io", dbPath, keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
// Verify DID was set
if pds.DID() != did {
t.Errorf("Expected DID %s, got %s", did, pds.DID())
}
// Verify signing key was created
if pds.SigningKey() == nil {
t.Error("Expected signing key to be created")
}
// Verify key file exists
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
t.Error("Expected signing key file to be created")
}
// Verify database file exists (SQLite creates db.sqlite3 inside the directory)
dbFile := filepath.Join(dbPath, "db.sqlite3")
if _, err := os.Stat(dbFile); os.IsNotExist(err) {
t.Errorf("Expected database file to be created at %s", dbFile)
}
}
// TestNewHoldPDS_ExistingRepo tests opening an existing hold PDS database
func TestNewHoldPDS_ExistingRepo(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "pds.db")
keyPath := filepath.Join(tmpDir, "signing-key")
did := "did:web:hold.example.com"
publicURL := "https://hold.example.com"
// Create first PDS instance and bootstrap it
pds1, err := NewHoldPDS(ctx, did, publicURL, "https://atcr.io", dbPath, keyPath, false)
if err != nil {
t.Fatalf("First NewHoldPDS failed: %v", err)
}
// Bootstrap with a captain record
ownerDID := "did:plc:owner123"
if err := pds1.Bootstrap(ctx, nil, BootstrapConfig{OwnerDID: ownerDID, Public: true}); err != nil {
t.Fatalf("Bootstrap failed: %v", err)
}
// Verify captain record exists
_, captain1, err := pds1.GetCaptainRecord(ctx)
if err != nil {
t.Fatalf("GetCaptainRecord failed after bootstrap: %v", err)
}
if captain1.Owner != ownerDID {
t.Errorf("Expected owner %s, got %s", ownerDID, captain1.Owner)
}
// Close first instance
pds1.Close()
// Re-open the same database
pds2, err := NewHoldPDS(ctx, did, publicURL, "https://atcr.io", dbPath, keyPath, false)
if err != nil {
t.Fatalf("Second NewHoldPDS failed: %v", err)
}
defer pds2.Close()
// Verify captain record still exists
_, captain2, err := pds2.GetCaptainRecord(ctx)
if err != nil {
t.Fatalf("GetCaptainRecord failed after reopening: %v", err)
}
// Verify captain data persisted
if captain2.Owner != ownerDID {
t.Errorf("Expected owner %s after reopen, got %s", ownerDID, captain2.Owner)
}
if !captain2.Public {
t.Error("Expected captain.Public to be true")
}
if captain2.AllowAllCrew {
t.Error("Expected captain.AllowAllCrew to be false")
}
}
// TestBootstrap_NewRepo tests bootstrap on a new repository
func TestBootstrap_NewRepo(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "pds.db")
keyPath := filepath.Join(tmpDir, "signing-key")
pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", "https://atcr.io", dbPath, keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
// Bootstrap with owner
ownerDID := "did:plc:alice123"
publicAccess := true
allowAllCrew := false
err = pds.Bootstrap(ctx, nil, BootstrapConfig{OwnerDID: ownerDID, Public: publicAccess, AllowAllCrew: allowAllCrew})
if err != nil {
t.Fatalf("Bootstrap failed: %v", err)
}
// Verify captain record was created
_, captain, err := pds.GetCaptainRecord(ctx)
if err != nil {
t.Fatalf("GetCaptainRecord failed: %v", err)
}
// Verify captain fields
if captain.Owner != ownerDID {
t.Errorf("Expected owner %s, got %s", ownerDID, captain.Owner)
}
if captain.Public != publicAccess {
t.Errorf("Expected public=%v, got %v", publicAccess, captain.Public)
}
if captain.AllowAllCrew != allowAllCrew {
t.Errorf("Expected allowAllCrew=%v, got %v", allowAllCrew, captain.AllowAllCrew)
}
if captain.Type != atproto.CaptainCollection {
t.Errorf("Expected type %s, got %s", atproto.CaptainCollection, captain.Type)
}
if captain.DeployedAt == "" {
t.Error("Expected deployedAt to be set")
}
// Verify owner was added as crew member
crewMembers, err := pds.ListCrewMembers(ctx)
if err != nil {
t.Fatalf("ListCrewMembers failed: %v", err)
}
if len(crewMembers) != 1 {
t.Fatalf("Expected 1 crew member, got %d", len(crewMembers))
}
crew := crewMembers[0]
if crew.Record.Member != ownerDID {
t.Errorf("Expected crew member %s, got %s", ownerDID, crew.Record.Member)
}
if crew.Record.Role != "admin" {
t.Errorf("Expected role admin, got %s", crew.Record.Role)
}
// Verify permissions
expectedPerms := []string{"blob:read", "blob:write", "crew:admin"}
if len(crew.Record.Permissions) != len(expectedPerms) {
t.Fatalf("Expected %d permissions, got %d", len(expectedPerms), len(crew.Record.Permissions))
}
for i, perm := range expectedPerms {
if crew.Record.Permissions[i] != perm {
t.Errorf("Expected permission[%d]=%s, got %s", i, perm, crew.Record.Permissions[i])
}
}
}
// TestBootstrap_Idempotent tests that bootstrap is idempotent
func TestBootstrap_Idempotent(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "pds.db")
keyPath := filepath.Join(tmpDir, "signing-key")
pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", "https://atcr.io", dbPath, keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
ownerDID := "did:plc:alice123"
// First bootstrap
err = pds.Bootstrap(ctx, nil, BootstrapConfig{OwnerDID: ownerDID, Public: true})
if err != nil {
t.Fatalf("First bootstrap failed: %v", err)
}
// Get captain CID after first bootstrap
cid1, captain1, err := pds.GetCaptainRecord(ctx)
if err != nil {
t.Fatalf("GetCaptainRecord failed: %v", err)
}
// Get crew count after first bootstrap
crew1, err := pds.ListCrewMembers(ctx)
if err != nil {
t.Fatalf("ListCrewMembers failed: %v", err)
}
crewCount1 := len(crew1)
// Second bootstrap (should be idempotent - skip creation)
err = pds.Bootstrap(ctx, nil, BootstrapConfig{OwnerDID: ownerDID, Public: true})
if err != nil {
t.Fatalf("Second bootstrap failed: %v", err)
}
// Verify captain record unchanged
cid2, captain2, err := pds.GetCaptainRecord(ctx)
if err != nil {
t.Fatalf("GetCaptainRecord failed after second bootstrap: %v", err)
}
if !cid1.Equals(cid2) {
t.Error("Expected captain CID to remain unchanged after second bootstrap")
}
if captain1.Owner != captain2.Owner {
t.Error("Expected captain owner to remain unchanged")
}
// Verify crew count unchanged (owner not added twice)
crew2, err := pds.ListCrewMembers(ctx)
if err != nil {
t.Fatalf("ListCrewMembers failed after second bootstrap: %v", err)
}
crewCount2 := len(crew2)
if crewCount1 != crewCount2 {
t.Errorf("Expected crew count to remain %d, got %d (owner may have been added twice)", crewCount1, crewCount2)
}
}
// TestBootstrap_EmptyOwner tests that bootstrap with empty owner is a no-op
func TestBootstrap_EmptyOwner(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "pds.db")
keyPath := filepath.Join(tmpDir, "signing-key")
pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", "https://atcr.io", dbPath, keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
// Bootstrap with empty owner DID (should be no-op)
err = pds.Bootstrap(ctx, nil, BootstrapConfig{Public: true})
if err != nil {
t.Fatalf("Bootstrap with empty owner should not error: %v", err)
}
// Verify captain record was NOT created
_, _, err = pds.GetCaptainRecord(ctx)
if err == nil {
t.Error("Expected GetCaptainRecord to fail (no captain record), but it succeeded")
}
// Verify it's a "not found" type error
if err != nil && !strings.Contains(err.Error(), "not found") && !strings.Contains(err.Error(), "failed to get captain record") {
t.Errorf("Expected 'not found' error, got: %v", err)
}
}
// TestLexiconTypeRegistration tests that captain and crew types are registered
func TestLexiconTypeRegistration(t *testing.T) {
// The init() function in server.go registers types
// We can verify this by creating a PDS and doing a round-trip write/read
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "pds.db")
keyPath := filepath.Join(tmpDir, "signing-key")
pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", "https://atcr.io", dbPath, keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
// Bootstrap to create captain record
ownerDID := "did:plc:alice123"
if err := pds.Bootstrap(ctx, nil, BootstrapConfig{OwnerDID: ownerDID, Public: true}); err != nil {
t.Fatalf("Bootstrap failed: %v", err)
}
// GetCaptainRecord uses type assertion to *atproto.CaptainRecord
// If the type wasn't registered, this would fail with type assertion error
_, captain, err := pds.GetCaptainRecord(ctx)
if err != nil {
t.Fatalf("GetCaptainRecord failed: %v", err)
}
// Verify we got the correct concrete type
if captain == nil {
t.Fatal("Expected non-nil captain record")
}
if captain.Type != atproto.CaptainCollection {
t.Errorf("Expected captain type %s, got %s", atproto.CaptainCollection, captain.Type)
}
// Do the same for crew record
crewMembers, err := pds.ListCrewMembers(ctx)
if err != nil {
t.Fatalf("ListCrewMembers failed: %v", err)
}
if len(crewMembers) == 0 {
t.Fatal("Expected at least one crew member")
}
crew := crewMembers[0].Record
if crew.Type != atproto.CrewCollection {
t.Errorf("Expected crew type %s, got %s", atproto.CrewCollection, crew.Type)
}
}
// TestBootstrap_DidWebOwner tests bootstrap with did:web owner
func TestBootstrap_DidWebOwner(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "pds.db")
keyPath := filepath.Join(tmpDir, "signing-key")
pds, err := NewHoldPDS(ctx, "did:web:hold01.atcr.io", "https://hold01.atcr.io", "https://atcr.io", dbPath, keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
// Bootstrap with did:web owner (not did:plc)
ownerDID := "did:web:alice.example.com"
publicAccess := true
allowAllCrew := false
err = pds.Bootstrap(ctx, nil, BootstrapConfig{OwnerDID: ownerDID, Public: publicAccess, AllowAllCrew: allowAllCrew})
if err != nil {
t.Fatalf("Bootstrap failed with did:web owner: %v", err)
}
// Verify captain record was created with did:web owner
_, captain, err := pds.GetCaptainRecord(ctx)
if err != nil {
t.Fatalf("GetCaptainRecord failed: %v", err)
}
// Verify captain fields
if captain.Owner != ownerDID {
t.Errorf("Expected owner %s, got %s", ownerDID, captain.Owner)
}
if captain.Public != publicAccess {
t.Errorf("Expected public=%v, got %v", publicAccess, captain.Public)
}
if captain.AllowAllCrew != allowAllCrew {
t.Errorf("Expected allowAllCrew=%v, got %v", allowAllCrew, captain.AllowAllCrew)
}
// Verify owner was added as crew member
crewMembers, err := pds.ListCrewMembers(ctx)
if err != nil {
t.Fatalf("ListCrewMembers failed: %v", err)
}
if len(crewMembers) != 1 {
t.Fatalf("Expected 1 crew member, got %d", len(crewMembers))
}
crew := crewMembers[0]
if crew.Record.Member != ownerDID {
t.Errorf("Expected crew member %s, got %s", ownerDID, crew.Record.Member)
}
if crew.Record.Role != "admin" {
t.Errorf("Expected role admin, got %s", crew.Record.Role)
}
}
// TestBootstrap_MixedDIDs tests bootstrap with mixed DID types
func TestBootstrap_MixedDIDs(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "pds.db")
keyPath := filepath.Join(tmpDir, "signing-key")
// Create hold with did:web
holdDID := "did:web:hold.example.com"
pds, err := NewHoldPDS(ctx, holdDID, "https://hold.example.com", "https://atcr.io", dbPath, keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
// Bootstrap with did:plc owner
plcOwner := "did:plc:alice123"
err = pds.Bootstrap(ctx, nil, BootstrapConfig{OwnerDID: plcOwner, Public: true})
if err != nil {
t.Fatalf("Bootstrap failed: %v", err)
}
// Add did:web crew member
webMember := "did:web:bob.example.com"
_, err = pds.AddCrewMember(ctx, webMember, "writer", []string{"blob:read", "blob:write"}, "")
if err != nil {
t.Fatalf("AddCrewMember failed with did:web: %v", err)
}
// Verify captain
_, captain, err := pds.GetCaptainRecord(ctx)
if err != nil {
t.Fatalf("GetCaptainRecord failed: %v", err)
}
if captain.Owner != plcOwner {
t.Errorf("Expected captain owner %s, got %s", plcOwner, captain.Owner)
}
// Verify crew members (should have both did:plc and did:web)
crewMembers, err := pds.ListCrewMembers(ctx)
if err != nil {
t.Fatalf("ListCrewMembers failed: %v", err)
}
if len(crewMembers) != 2 {
t.Fatalf("Expected 2 crew members, got %d", len(crewMembers))
}
// Verify both DIDs are present
foundPLC := false
foundWeb := false
for _, cm := range crewMembers {
if cm.Record.Member == plcOwner {
foundPLC = true
}
if cm.Record.Member == webMember {
foundWeb = true
}
}
if !foundPLC {
t.Errorf("Expected to find did:plc member %s", plcOwner)
}
if !foundWeb {
t.Errorf("Expected to find did:web member %s", webMember)
}
}
// TestBootstrap_CrewWithoutCaptain tests bootstrap when crew exists but captain doesn't
// This edge case could happen if repo state is corrupted or partially initialized
func TestBootstrap_CrewWithoutCaptain(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "pds.db")
keyPath := filepath.Join(tmpDir, "signing-key")
pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", "https://atcr.io", dbPath, keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
// Initialize repo manually
err = pds.repomgr.InitNewActor(ctx, pds.uid, "", pds.did, "", "", "")
if err != nil {
t.Fatalf("InitNewActor failed: %v", err)
}
// Create crew member WITHOUT captain (unusual state)
ownerDID := "did:plc:alice123"
_, err = pds.AddCrewMember(ctx, ownerDID, "admin", []string{"blob:read", "blob:write", "crew:admin"}, "")
if err != nil {
t.Fatalf("AddCrewMember failed: %v", err)
}
// Verify crew exists
crewBefore, err := pds.ListCrewMembers(ctx)
if err != nil {
t.Fatalf("ListCrewMembers failed: %v", err)
}
if len(crewBefore) != 1 {
t.Fatalf("Expected 1 crew member before bootstrap, got %d", len(crewBefore))
}
// Verify captain doesn't exist
_, _, err = pds.GetCaptainRecord(ctx)
if err == nil {
t.Fatal("Expected captain record to not exist before bootstrap")
}
// Bootstrap should create captain record
err = pds.Bootstrap(ctx, nil, BootstrapConfig{OwnerDID: ownerDID, Public: true})
if err != nil {
t.Fatalf("Bootstrap failed: %v", err)
}
// Verify captain was created
_, captain, err := pds.GetCaptainRecord(ctx)
if err != nil {
t.Fatalf("GetCaptainRecord failed after bootstrap: %v", err)
}
if captain.Owner != ownerDID {
t.Errorf("Expected captain owner %s, got %s", ownerDID, captain.Owner)
}
// Verify crew wasn't duplicated (Bootstrap adds owner as crew, but they already exist)
// With hash-based rkeys, AddCrewMember uses PutRecord which upserts - no duplicates possible
crewAfter, err := pds.ListCrewMembers(ctx)
if err != nil {
t.Fatalf("ListCrewMembers failed after bootstrap: %v", err)
}
// Should still have 1 crew member (hash-based rkey ensures upsert, not duplicate)
if len(crewAfter) != 1 {
t.Errorf("Expected 1 crew member after bootstrap (upsert), got %d", len(crewAfter))
}
}
// TestBootstrap_CaptainWithoutCrew tests bootstrap when captain exists but owner crew doesn't
// This verifies that bootstrap properly adds the owner as crew if missing
func TestBootstrap_CaptainWithoutCrew(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "pds.db")
keyPath := filepath.Join(tmpDir, "signing-key")
pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", "https://atcr.io", dbPath, keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
// Initialize repo manually
err = pds.repomgr.InitNewActor(ctx, pds.uid, "", pds.did, "", "", "")
if err != nil {
t.Fatalf("InitNewActor failed: %v", err)
}
// Create captain record WITHOUT crew (unusual state)
ownerDID := "did:plc:alice123"
_, err = pds.CreateCaptainRecord(ctx, ownerDID, true, false, false, "")
if err != nil {
t.Fatalf("CreateCaptainRecord failed: %v", err)
}
// Verify captain exists
_, captain, err := pds.GetCaptainRecord(ctx)
if err != nil {
t.Fatalf("GetCaptainRecord failed: %v", err)
}
if captain.Owner != ownerDID {
t.Errorf("Expected captain owner %s, got %s", ownerDID, captain.Owner)
}
// Verify crew is empty
crewBefore, err := pds.ListCrewMembers(ctx)
if err != nil {
t.Fatalf("ListCrewMembers failed: %v", err)
}
if len(crewBefore) != 0 {
t.Fatalf("Expected 0 crew members before bootstrap, got %d", len(crewBefore))
}
// Bootstrap should be idempotent but notice missing crew
// Currently Bootstrap skips if captain exists, so crew won't be added
err = pds.Bootstrap(ctx, nil, BootstrapConfig{OwnerDID: ownerDID, Public: true})
if err != nil {
t.Fatalf("Bootstrap failed: %v", err)
}
// Verify crew after bootstrap
crewAfter, err := pds.ListCrewMembers(ctx)
if err != nil {
t.Fatalf("ListCrewMembers failed after bootstrap: %v", err)
}
// Bootstrap currently skips everything if captain exists
// This means crew won't be added in this case
if len(crewAfter) == 0 {
t.Logf("Note: Bootstrap skipped adding owner as crew because captain already exists")
t.Logf("This is current behavior - Bootstrap is fully idempotent and skips if captain exists")
} else {
// If we change Bootstrap to be smarter, it might add crew
t.Logf("Bootstrap added %d crew members", len(crewAfter))
// Verify owner was added
foundOwner := false
for _, cm := range crewAfter {
if cm.Record.Member == ownerDID {
foundOwner = true
if cm.Record.Role != "admin" {
t.Errorf("Expected owner role admin, got %s", cm.Record.Role)
}
}
}
if !foundOwner {
t.Error("Expected owner to be added as crew member")
}
}
}
// Tests for RecordsIndex feature
// TestHoldPDS_RecordsIndex_Nil tests that RecordsIndex is nil for :memory: database
func TestHoldPDS_RecordsIndex_Nil(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
keyPath := filepath.Join(tmpDir, "signing-key")
// Create with :memory: database
pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", "https://atcr.io", ":memory:", keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
// RecordsIndex should be nil for :memory:
if pds.RecordsIndex() != nil {
t.Error("Expected RecordsIndex() to be nil for :memory: database")
}
}
// TestHoldPDS_RecordsIndex_NonNil tests that RecordsIndex is created for file database
func TestHoldPDS_RecordsIndex_NonNil(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "pds.db")
keyPath := filepath.Join(tmpDir, "signing-key")
// Create with file database
pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", "https://atcr.io", dbPath, keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
// RecordsIndex should be non-nil for file database
if pds.RecordsIndex() == nil {
t.Error("Expected RecordsIndex() to be non-nil for file database")
}
}
// TestHoldPDS_Carstore tests the Carstore getter
func TestHoldPDS_Carstore(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
keyPath := filepath.Join(tmpDir, "signing-key")
pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", "https://atcr.io", ":memory:", keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
if pds.Carstore() == nil {
t.Error("Expected Carstore() to be non-nil")
}
}
// TestHoldPDS_UID tests the UID getter
func TestHoldPDS_UID(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
keyPath := filepath.Join(tmpDir, "signing-key")
pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", "https://atcr.io", ":memory:", keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
// UID should be 1 for single-user PDS
if pds.UID() != 1 {
t.Errorf("Expected UID() to be 1, got %d", pds.UID())
}
}
// TestHoldPDS_CreateRecordsIndexEventHandler tests event handler wrapper
func TestHoldPDS_CreateRecordsIndexEventHandler(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "pds.db")
keyPath := filepath.Join(tmpDir, "signing-key")
pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", "https://atcr.io", dbPath, keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
// Track if broadcaster was called
broadcasterCalled := false
broadcasterHandler := func(ctx context.Context, event *RepoEvent) {
broadcasterCalled = true
}
// Create handler
handler := pds.CreateRecordsIndexEventHandler(broadcasterHandler)
if handler == nil {
t.Fatal("Expected handler to be non-nil")
}
// Create a test event with create operation
event := &RepoEvent{
Ops: []RepoOp{
{
Kind: EvtKindCreateRecord,
Collection: "io.atcr.hold.crew",
Rkey: "testrkey",
RecCid: nil, // Will be nil string
},
},
}
// Call handler
handler(ctx, event)
// Verify broadcaster was called
if !broadcasterCalled {
t.Error("Expected broadcaster handler to be called")
}
// Verify record was indexed
if pds.RecordsIndex() != nil {
count, err := pds.RecordsIndex().Count("io.atcr.hold.crew")
if err != nil {
t.Fatalf("Count() error = %v", err)
}
if count != 1 {
t.Errorf("Expected 1 indexed record, got %d", count)
}
}
}
// TestHoldPDS_CreateRecordsIndexEventHandler_Delete tests delete operation
func TestHoldPDS_CreateRecordsIndexEventHandler_Delete(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "pds.db")
keyPath := filepath.Join(tmpDir, "signing-key")
pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", "https://atcr.io", dbPath, keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
handler := pds.CreateRecordsIndexEventHandler(nil)
// First, create a record
createEvent := &RepoEvent{
Ops: []RepoOp{
{
Kind: EvtKindCreateRecord,
Collection: "io.atcr.hold.crew",
Rkey: "testrkey",
},
},
}
handler(ctx, createEvent)
// Verify it was indexed
count, _ := pds.RecordsIndex().Count("io.atcr.hold.crew")
if count != 1 {
t.Fatalf("Expected 1 record after create, got %d", count)
}
// Now delete it
deleteEvent := &RepoEvent{
Ops: []RepoOp{
{
Kind: EvtKindDeleteRecord,
Collection: "io.atcr.hold.crew",
Rkey: "testrkey",
},
},
}
handler(ctx, deleteEvent)
// Verify it was removed from index
count, _ = pds.RecordsIndex().Count("io.atcr.hold.crew")
if count != 0 {
t.Errorf("Expected 0 records after delete, got %d", count)
}
}
// TestHoldPDS_CreateRecordsIndexEventHandler_NilBroadcaster tests with nil broadcaster
func TestHoldPDS_CreateRecordsIndexEventHandler_NilBroadcaster(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "pds.db")
keyPath := filepath.Join(tmpDir, "signing-key")
pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", "https://atcr.io", dbPath, keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
// Create handler with nil broadcaster (should not panic)
handler := pds.CreateRecordsIndexEventHandler(nil)
event := &RepoEvent{
Ops: []RepoOp{
{
Kind: EvtKindCreateRecord,
Collection: "io.atcr.hold.crew",
Rkey: "testrkey",
},
},
}
// Should not panic
handler(ctx, event)
// Verify record was still indexed
count, _ := pds.RecordsIndex().Count("io.atcr.hold.crew")
if count != 1 {
t.Errorf("Expected 1 indexed record, got %d", count)
}
}
// TestHoldPDS_BackfillRecordsIndex tests backfilling the records index from MST
func TestHoldPDS_BackfillRecordsIndex(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "pds.db")
keyPath := filepath.Join(tmpDir, "signing-key")
pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", "https://atcr.io", dbPath, keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
// Bootstrap to create some records in MST (captain + crew)
ownerDID := "did:plc:testowner"
err = pds.Bootstrap(ctx, nil, BootstrapConfig{OwnerDID: ownerDID, Public: true})
if err != nil {
t.Fatalf("Bootstrap failed: %v", err)
}
// Clear the index to simulate out-of-sync state
_, err = pds.RecordsIndex().db.Exec("DELETE FROM records")
if err != nil {
t.Fatalf("Failed to clear index: %v", err)
}
// Verify index is empty
count, _ := pds.RecordsIndex().TotalCount()
if count != 0 {
t.Fatalf("Expected empty index, got %d", count)
}
// Backfill
err = pds.BackfillRecordsIndex(ctx)
if err != nil {
t.Fatalf("BackfillRecordsIndex failed: %v", err)
}
// Verify records were backfilled
// Bootstrap creates: 1 captain + 1 crew + 1 profile = 3 records
count, _ = pds.RecordsIndex().TotalCount()
if count < 2 {
t.Errorf("Expected at least 2 records after backfill (captain + crew), got %d", count)
}
}
// TestHoldPDS_BackfillRecordsIndex_NilIndex tests backfill with nil index
func TestHoldPDS_BackfillRecordsIndex_NilIndex(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
keyPath := filepath.Join(tmpDir, "signing-key")
// Use :memory: to get nil index
pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", "https://atcr.io", ":memory:", keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
// Backfill should be no-op and not error
err = pds.BackfillRecordsIndex(ctx)
if err != nil {
t.Errorf("BackfillRecordsIndex should not error with nil index, got: %v", err)
}
}
// TestHoldPDS_BackfillRecordsIndex_SkipsWhenSynced tests backfill skip when already synced
func TestHoldPDS_BackfillRecordsIndex_SkipsWhenSynced(t *testing.T) {
ctx := context.Background()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "pds.db")
keyPath := filepath.Join(tmpDir, "signing-key")
pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", "https://atcr.io", dbPath, keyPath, false)
if err != nil {
t.Fatalf("NewHoldPDS failed: %v", err)
}
defer pds.Close()
// Bootstrap to create records
err = pds.Bootstrap(ctx, nil, BootstrapConfig{OwnerDID: "did:plc:testowner", Public: true})
if err != nil {
t.Fatalf("Bootstrap failed: %v", err)
}
// Backfill once to sync
err = pds.BackfillRecordsIndex(ctx)
if err != nil {
t.Fatalf("First BackfillRecordsIndex failed: %v", err)
}
count1, _ := pds.RecordsIndex().TotalCount()
// Backfill again - should skip (counts match)
err = pds.BackfillRecordsIndex(ctx)
if err != nil {
t.Fatalf("Second BackfillRecordsIndex failed: %v", err)
}
count2, _ := pds.RecordsIndex().TotalCount()
// Count should be unchanged
if count1 != count2 {
t.Errorf("Expected count to remain %d after second backfill, got %d", count1, count2)
}
}