Files
Elydora-Go-SDK/cmd/elydora/plugins/droid_transaction_test.go
T

271 lines
9.1 KiB
Go

package plugins
import (
"errors"
"os"
"strings"
"testing"
)
func prepareDroidTestInstallation(
t *testing.T,
fixture *droidFixture,
config InstallConfig,
) *preparedDroidInstallation {
t.Helper()
sources, err := readDroidSources()
if err != nil {
t.Fatalf("read Factory Droid sources: %v", err)
}
paths, nodePath, err := preflightDroidInstallation(config, sources)
if err != nil {
t.Fatalf("preflight Factory Droid installation: %v", err)
}
rendered, err := renderDroidInstallation(
sources,
paths.guardPath,
paths.auditPath,
nodePath,
paths.runtimeRoot,
)
if err != nil {
t.Fatalf("render Factory Droid installation: %v", err)
}
prepared, err := prepareDroidInstallation(config, sources, rendered)
if err != nil {
t.Fatalf("prepare Factory Droid installation: %v", err)
}
return prepared
}
func droidManagedPaths(fixture *droidFixture) []string {
return []string{
fixture.guardPath,
fixture.runtimeConfig,
fixture.privateKey,
fixture.hookPath,
fixture.configPath,
}
}
func TestDroidInstallRollsBackRuntimeAfterLateHookCommitFailure(t *testing.T) {
fixture := prepareDroidFixture(t, droidFixtureOptions{})
prepared := prepareDroidTestInstallation(t, fixture, fixture.config)
failed := false
rename := func(source, destination string) error {
if !failed && sameDroidPath(destination, fixture.configPath) {
failed = true
return errors.New("injected Droid hook commit failure")
}
return os.Rename(source, destination)
}
err := commitDroidInstallation(prepared, rename)
if err == nil || !strings.Contains(err.Error(), "injected Droid hook commit failure") || !failed {
t.Fatalf("transaction error = %v, failed = %v", err, failed)
}
for _, path := range droidManagedPaths(fixture) {
requireMissingDroidFile(t, path)
}
requireNoDroidStagingFiles(t, fixture.homeDir)
}
func TestDroidConcurrentActiveHookReplacementPreventsRuntimeCommit(t *testing.T) {
fixture := prepareDroidFixture(t, droidFixtureOptions{})
installDroidFixture(t, fixture)
before := snapshotDroidFiles(t, droidManagedPaths(fixture)...)
updated := fixture.config
updated.OrgID = "org-updated"
prepared := prepareDroidTestInstallation(t, fixture, updated)
concurrent := "{\"hooks\":{\"PreToolUse\":[]},\"owner\":\"concurrent\"}\n"
if err := os.WriteFile(fixture.configPath, []byte(concurrent), 0600); err != nil {
t.Fatalf("replace active hook source: %v", err)
}
err := commitDroidInstallation(prepared, nil)
if err == nil || !strings.Contains(err.Error(), "changed during Install Factory Droid") {
t.Fatalf("concurrent source error = %v", err)
}
for path, source := range before {
if sameDroidPath(path, fixture.configPath) {
continue
}
if readDroidTestFile(t, path) != source {
t.Fatalf("runtime changed after concurrent source replacement: %s", path)
}
}
if readDroidTestFile(t, fixture.configPath) != concurrent {
t.Fatal("concurrent source was overwritten")
}
requireNoDroidStagingFiles(t, fixture.homeDir)
}
func TestDroidConcurrentInactiveLocalSourceCreationIsDetected(t *testing.T) {
fixture := prepareDroidFixture(t, droidFixtureOptions{})
installDroidFixture(t, fixture)
before := snapshotDroidFiles(t, droidManagedPaths(fixture)...)
updated := fixture.config
updated.OrgID = "org-updated"
prepared := prepareDroidTestInstallation(t, fixture, updated)
concurrent := "{\"hooksDisabled\":true}\n"
writeOptionalDroidFile(t, fixture.localSettingsPath, droidString(concurrent))
err := commitDroidInstallation(prepared, nil)
if err == nil || !strings.Contains(err.Error(), "local settings changed during Install") {
t.Fatalf("concurrent local source error = %v", err)
}
requireDroidSnapshot(t, before)
if readDroidTestFile(t, fixture.localSettingsPath) != concurrent {
t.Fatal("concurrent local settings source was overwritten")
}
requireNoDroidStagingFiles(t, fixture.homeDir)
}
func TestDroidConcurrentProjectPolicyChangeIsDetected(t *testing.T) {
fixture := prepareDroidFixture(t, droidFixtureOptions{
projectSettings: droidJSON(map[string]any{"hooksDisabled": false}),
})
installDroidFixture(t, fixture)
before := snapshotDroidFiles(t, droidManagedPaths(fixture)...)
updated := fixture.config
updated.OrgID = "org-updated"
prepared := prepareDroidTestInstallation(t, fixture, updated)
concurrent := "{\"hooksDisabled\":true}\n"
if err := os.WriteFile(fixture.projectSettingsPath, []byte(concurrent), 0600); err != nil {
t.Fatalf("change project policy: %v", err)
}
err := commitDroidInstallation(prepared, nil)
if err == nil || !strings.Contains(err.Error(), "project settings changed during Install") {
t.Fatalf("concurrent project policy error = %v", err)
}
requireDroidSnapshot(t, before)
if readDroidTestFile(t, fixture.projectSettingsPath) != concurrent {
t.Fatal("concurrent project policy was overwritten")
}
requireNoDroidStagingFiles(t, fixture.homeDir)
}
func TestDroidConcurrentPolicyChangePreventsRuntimeDirectoryCreation(t *testing.T) {
fixture := prepareDroidFixture(t, droidFixtureOptions{
projectSettings: droidJSON(map[string]any{"hooksDisabled": false}),
})
prepared := prepareDroidTestInstallation(t, fixture, fixture.config)
if err := os.WriteFile(
fixture.projectSettingsPath,
[]byte("{\"hooksDisabled\":true}\n"),
0600,
); err != nil {
t.Fatalf("change project policy: %v", err)
}
err := commitDroidInstallation(prepared, nil)
if err == nil || !strings.Contains(err.Error(), "project settings changed during Install") {
t.Fatalf("concurrent project policy error = %v", err)
}
requireMissingDroidFile(t, fixture.agentDir)
requireMissingDroidFile(t, fixture.configPath)
}
func TestDroidSameContentReplacementIsRejectedByPhysicalIdentity(t *testing.T) {
fixture := prepareDroidFixture(t, droidFixtureOptions{})
installDroidFixture(t, fixture)
before := snapshotDroidFiles(t, droidManagedPaths(fixture)...)
sources, err := readDroidSources()
if err != nil {
t.Fatalf("read Factory Droid sources: %v", err)
}
original := readDroidTestFile(t, fixture.configPath)
if err := os.Remove(fixture.configPath); err != nil {
t.Fatalf("remove hook source: %v", err)
}
if err := os.WriteFile(fixture.configPath, []byte(original), 0600); err != nil {
t.Fatalf("replace hook source: %v", err)
}
updated := fixture.config
updated.OrgID = "org-updated"
paths, nodePath, err := preflightDroidInstallation(updated, sources)
if err != nil {
t.Fatalf("preflight stale sources: %v", err)
}
rendered, err := renderDroidInstallation(
sources,
paths.guardPath,
paths.auditPath,
nodePath,
paths.runtimeRoot,
)
if err != nil {
t.Fatalf("render stale sources: %v", err)
}
prepared, err := prepareDroidInstallation(updated, sources, rendered)
if err != nil {
t.Fatalf("prepare stale sources: %v", err)
}
err = commitDroidInstallation(prepared, nil)
if err == nil || !strings.Contains(err.Error(), "changed during Install") {
t.Fatalf("same-content replacement error = %v", err)
}
if readDroidTestFile(t, fixture.configPath) != original {
t.Fatal("same-content replacement changed")
}
for path, source := range before {
if !sameDroidPath(path, fixture.configPath) && readDroidTestFile(t, path) != source {
t.Fatalf("runtime changed after inode replacement: %s", path)
}
}
requireNoDroidStagingFiles(t, fixture.homeDir)
}
func TestDroidUninstallRollsBackAllHookDocuments(t *testing.T) {
fixture := prepareDroidFixture(t, droidFixtureOptions{})
installDroidFixture(t, fixture)
installed := droidCurrentHooks(t, fixture.configPath)
writeDroidTestObject(t, fixture.settingsPath, map[string]any{
"hooks": installed,
"owner": "user",
})
paths := append(droidManagedPaths(fixture), fixture.settingsPath)
before := snapshotDroidFiles(t, paths...)
sources, err := readDroidSources()
if err != nil {
t.Fatalf("read uninstall sources: %v", err)
}
runtimeRoot, err := droidRuntimeRoot()
if err != nil {
t.Fatalf("resolve runtime root: %v", err)
}
rendered, err := renderDroidUninstall(sources, droidTestAgentID, runtimeRoot)
if err != nil {
t.Fatalf("render uninstall: %v", err)
}
prepared, err := prepareDroidUninstall(rendered)
if err != nil {
t.Fatalf("prepare uninstall: %v", err)
}
failed := false
rename := func(source, destination string) error {
if !failed && sameDroidPath(destination, fixture.settingsPath) {
failed = true
return errors.New("injected Droid uninstall failure")
}
return os.Rename(source, destination)
}
err = commitDroidUninstall(prepared, rename)
if err == nil || !strings.Contains(err.Error(), "injected Droid uninstall failure") || !failed {
t.Fatalf("uninstall rollback error = %v, failed = %v", err, failed)
}
requireDroidSnapshot(t, before)
requireNoDroidStagingFiles(t, fixture.homeDir)
}
func TestDroidPreflightBlocksDisabledHooksBeforeRuntimeCreation(t *testing.T) {
fixture := prepareDroidFixture(t, droidFixtureOptions{
settings: droidJSON(map[string]any{"hooksDisabled": true}),
})
preflighter, ok := any(fixture.plugin).(InstallPreflighter)
if !ok {
t.Fatal("Factory Droid plugin does not implement InstallPreflighter")
}
err := preflighter.PreflightInstall(fixture.config)
if err == nil || !strings.Contains(err.Error(), "hooksDisabled") {
t.Fatalf("preflight error = %v", err)
}
requireMissingDroidFile(t, fixture.agentDir)
}