Files
Elydora-Go-SDK/cmd/elydora/plugins/transaction_boundary_test.go
T
ZacharyZhang-NY eb41b991bf feat(go): durable transaction engine and Kiro IDE 1.0 adapter
Split the monolithic transaction into focused modules: append-only
crash-recoverable journal under ~/.elydora/transactions with owner
tokens and per-transaction namespaces, staged atomic publication
(stage .pending, fsync, rename, fsync), platform-specific atomic
replacement (MoveFileEx, renameat2, renamex_np), private-directory
hardening per OS, lock-guarded recovery, and workspace cleanup that
removes only transaction-created empty directories.

Add the Kiro IDE 1.0 plugin: strict v1 hook schema with one enabled
PreToolUse guard and PostToolUse audit pair bound to the active
workspace, agent-to-workspace identity binding in runtime config, and
exact migration of the legacy elydora-audit.kiro.hook contract.

Gates: go test ./... and -race pass, go vet clean, govulncheck reports
zero reachable vulnerabilities, git diff --check clean.
2026-07-21 19:02:57 -04:00

354 lines
11 KiB
Go

package plugins
import (
"errors"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
func sameTransactionTestPath(left, right string) bool {
left = filepath.Clean(left)
right = filepath.Clean(right)
if runtime.GOOS == "windows" {
return strings.EqualFold(left, right)
}
return left == right
}
func prepareTransactionRemoval(t *testing.T, path, label string) *fileChange {
t.Helper()
original, existed, err := readOptionalFile(path, label)
if err != nil {
t.Fatalf("read removal source: %v", err)
}
change, err := prepareSourceChange(path, label, original, existed, nil, 0600, true)
if err != nil {
t.Fatalf("prepare removal: %v", err)
}
return change
}
func requireTransactionContent(t *testing.T, path, expected string) {
t.Helper()
content, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
if string(content) != expected {
t.Fatalf("content at %s = %q, want %q", path, content, expected)
}
}
func TestTransactionRejectsReplacementAtOriginalCaptureBoundary(t *testing.T) {
for _, test := range []struct {
name string
change func(*testing.T, string) *fileChange
}{
{
name: "update",
change: func(t *testing.T, path string) *fileChange {
change, err := prepareFileChange(path, "boundary file", []byte("next"), 0600)
if err != nil {
t.Fatalf("prepare update: %v", err)
}
return change
},
},
{name: "delete", change: func(t *testing.T, path string) *fileChange {
return prepareTransactionRemoval(t, path, "boundary file")
}},
} {
t.Run(test.name, func(t *testing.T) {
directory := t.TempDir()
target := filepath.Join(directory, "managed.json")
external := filepath.Join(directory, "original.external")
if err := os.WriteFile(target, []byte("original"), 0600); err != nil {
t.Fatalf("write original: %v", err)
}
change := test.change(t, target)
replaced := false
ops := transactionFileOps{moveNoReplace: func(source, destination string) error {
if !replaced && sameTransactionTestPath(source, target) &&
strings.HasSuffix(destination, ".rollback") {
replaced = true
if err := os.Rename(target, external); err != nil {
return err
}
if err := os.WriteFile(target, []byte("original"), 0600); err != nil {
return err
}
}
return atomicRenameNoReplace(source, destination)
}}
err := writeChangesWithFileOps(
[]*fileChange{change},
"capture boundary transaction",
ops,
)
if err == nil || !strings.Contains(err.Error(), "atomic capture boundary") {
t.Fatalf("transaction error = %v", err)
}
requireTransactionContent(t, target, "original")
requireTransactionContent(t, external, "original")
})
}
}
func TestTransactionCreatePreservesObjectAppearingAtCommitBoundary(t *testing.T) {
directory := t.TempDir()
target := filepath.Join(directory, "created.json")
change, err := prepareFileChange(target, "created file", []byte("managed"), 0600)
if err != nil {
t.Fatalf("prepare create: %v", err)
}
appeared := false
ops := transactionFileOps{moveNoReplace: func(source, destination string) error {
if !appeared && sameTransactionTestPath(destination, target) &&
strings.HasSuffix(source, ".tmp") {
appeared = true
if err := os.WriteFile(target, []byte("concurrent"), 0600); err != nil {
return err
}
}
return atomicRenameNoReplace(source, destination)
}}
err = writeChangesWithFileOps(
[]*fileChange{change},
"create boundary transaction",
ops,
)
if err == nil || !strings.Contains(err.Error(), "without replacing another file") {
t.Fatalf("transaction error = %v", err)
}
requireTransactionContent(t, target, "concurrent")
}
func TestTransactionRollbackRestorePreservesObjectAppearingAtBoundary(t *testing.T) {
directory := t.TempDir()
first := filepath.Join(directory, "first.json")
second := filepath.Join(directory, "second.json")
if err := os.WriteFile(first, []byte("first-original"), 0600); err != nil {
t.Fatalf("write original: %v", err)
}
firstChange, err := prepareFileChange(first, "first file", []byte("first-next"), 0600)
if err != nil {
t.Fatalf("prepare first change: %v", err)
}
secondChange, err := prepareFileChange(second, "second file", []byte("second-next"), 0600)
if err != nil {
t.Fatalf("prepare second change: %v", err)
}
appeared := false
ops := transactionFileOps{moveNoReplace: func(source, destination string) error {
if sameTransactionTestPath(destination, second) && strings.HasSuffix(source, ".tmp") {
return errors.New("injected second commit failure")
}
if !appeared && sameTransactionTestPath(destination, first) &&
strings.HasSuffix(source, ".rollback") {
appeared = true
if err := os.WriteFile(first, []byte("concurrent"), 0600); err != nil {
return err
}
}
return atomicRenameNoReplace(source, destination)
}}
err = writeChangesWithFileOps(
[]*fileChange{firstChange, secondChange},
"rollback restore boundary transaction",
ops,
)
if err == nil || !strings.Contains(err.Error(), "original content preserved at") {
t.Fatalf("transaction error = %v", err)
}
requireTransactionContent(t, first, "concurrent")
requirePreservedRollback(t, directory)
}
func TestTransactionRollbackRestoreRejectsReplacedSourceAtBoundary(t *testing.T) {
directory := t.TempDir()
first := filepath.Join(directory, "first.json")
second := filepath.Join(directory, "second.json")
external := filepath.Join(directory, "original.external")
if err := os.WriteFile(first, []byte("first-original"), 0600); err != nil {
t.Fatalf("write original: %v", err)
}
firstChange, err := prepareFileChange(first, "first file", []byte("first-next"), 0600)
if err != nil {
t.Fatalf("prepare first change: %v", err)
}
secondChange, err := prepareFileChange(second, "second file", []byte("second-next"), 0600)
if err != nil {
t.Fatalf("prepare second change: %v", err)
}
rollbackPath := ""
replaced := false
ops := transactionFileOps{moveNoReplace: func(source, destination string) error {
if sameTransactionTestPath(source, first) && strings.HasSuffix(destination, ".rollback") {
rollbackPath = destination
}
if sameTransactionTestPath(destination, second) && strings.HasSuffix(source, ".tmp") {
return errors.New("injected second commit failure")
}
if !replaced && sameTransactionTestPath(source, rollbackPath) &&
sameTransactionTestPath(destination, first) {
replaced = true
if err := os.Rename(rollbackPath, external); err != nil {
return err
}
if err := os.WriteFile(rollbackPath, []byte("concurrent"), 0600); err != nil {
return err
}
}
return atomicRenameNoReplace(source, destination)
}}
err = writeChangesWithFileOps(
[]*fileChange{firstChange, secondChange},
"rollback source boundary transaction",
ops,
)
if err == nil || !strings.Contains(err.Error(), "atomic restore boundary") {
t.Fatalf("transaction error = %v", err)
}
if _, statErr := os.Lstat(first); !errors.Is(statErr, os.ErrNotExist) {
t.Fatalf("target exists after rejected rollback source: %v", statErr)
}
requireTransactionContent(t, rollbackPath, "concurrent")
requireTransactionContent(t, external, "first-original")
}
func TestTransactionRollbackDeletePreservesReplacementAtBoundary(t *testing.T) {
directory := t.TempDir()
first := filepath.Join(directory, "first.json")
second := filepath.Join(directory, "second.json")
external := filepath.Join(directory, "managed.external")
firstChange, err := prepareFileChange(first, "first file", []byte("first-next"), 0600)
if err != nil {
t.Fatalf("prepare first change: %v", err)
}
secondChange, err := prepareFileChange(second, "second file", []byte("second-next"), 0600)
if err != nil {
t.Fatalf("prepare second change: %v", err)
}
replaced := false
ops := transactionFileOps{moveNoReplace: func(source, destination string) error {
if sameTransactionTestPath(destination, second) && strings.HasSuffix(source, ".tmp") {
return errors.New("injected second commit failure")
}
if !replaced && sameTransactionTestPath(source, first) &&
strings.HasSuffix(destination, ".discard") {
replaced = true
if err := os.Rename(first, external); err != nil {
return err
}
if err := os.WriteFile(first, []byte("concurrent"), 0600); err != nil {
return err
}
}
return atomicRenameNoReplace(source, destination)
}}
err = writeChangesWithFileOps(
[]*fileChange{firstChange, secondChange},
"rollback delete boundary transaction",
ops,
)
if err == nil || !strings.Contains(err.Error(), "atomic rollback boundary") {
t.Fatalf("transaction error = %v", err)
}
requireTransactionContent(t, first, "concurrent")
requireTransactionContent(t, external, "first-next")
}
func TestTransactionCleanupPreservesReplacedRollbackObject(t *testing.T) {
directory := t.TempDir()
target := filepath.Join(directory, "managed.json")
external := filepath.Join(directory, "original.external")
if err := os.WriteFile(target, []byte("original"), 0600); err != nil {
t.Fatalf("write original: %v", err)
}
change, err := prepareFileChange(target, "managed file", []byte("next"), 0600)
if err != nil {
t.Fatalf("prepare update: %v", err)
}
rollbackPath := ""
replaced := false
ops := transactionFileOps{moveNoReplace: func(source, destination string) error {
if sameTransactionTestPath(source, target) && strings.HasSuffix(destination, ".rollback") {
rollbackPath = destination
}
if err := atomicRenameNoReplace(source, destination); err != nil {
return err
}
if !replaced && sameTransactionTestPath(destination, target) &&
strings.HasSuffix(source, ".tmp") {
replaced = true
if err := os.Rename(rollbackPath, external); err != nil {
return err
}
if err := os.WriteFile(rollbackPath, []byte("concurrent"), 0600); err != nil {
return err
}
}
return nil
}}
err = writeChangesWithFileOps(
[]*fileChange{change},
"cleanup boundary transaction",
ops,
)
if err == nil || !strings.Contains(err.Error(), "changed during cleanup") {
t.Fatalf("transaction error = %v", err)
}
requireTransactionContent(t, target, "next")
requireTransactionContent(t, rollbackPath, "concurrent")
requireTransactionContent(t, external, "original")
}
func TestDurableJournalStateRejectsOversizedPhysicalFile(t *testing.T) {
path := filepath.Join(t.TempDir(), "state-00000000000000000001.json")
file, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
if err != nil {
t.Fatal(err)
}
if err := file.Truncate(maxDurableJournalBytes + 1); err != nil {
t.Fatal(errors.Join(err, file.Close()))
}
if err := file.Close(); err != nil {
t.Fatal(err)
}
if _, err := readDurableJournalState(path); err == nil ||
!strings.Contains(err.Error(), "exceeds") {
t.Fatalf("oversized journal error = %v", err)
}
}
func TestDurableJournalStateRejectsSymlink(t *testing.T) {
directory := t.TempDir()
target := filepath.Join(directory, "target.json")
link := filepath.Join(directory, "state-00000000000000000001.json")
if err := os.WriteFile(target, []byte("{}\n"), 0600); err != nil {
t.Fatal(err)
}
if err := os.Symlink(target, link); err != nil {
t.Skipf("symlink unavailable: %v", err)
}
if _, err := readDurableJournalState(link); err == nil ||
!strings.Contains(err.Error(), "not a physical file") {
t.Fatalf("journal symlink error = %v", err)
}
}
func TestAtomicDestinationExistsRejectsUnrelatedFailure(t *testing.T) {
if isAtomicDestinationExists(os.ErrPermission) {
t.Fatal("permission failure was classified as an occupied destination")
}
}