Move the four providers onto managed_runtime.go and shell_command.go; keep the Auggie wrappers, Cline metadata wrappers, and the Copilot and Droid precondition handling provider-local. Claude-Session: https://claude.ai/code/session_01Agoc9bRVE9KL7mmWtey9Vq
395 lines
13 KiB
Go
395 lines
13 KiB
Go
package plugins
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func prepareAugmentTestInstallation(
|
|
t *testing.T,
|
|
fixture *augmentFixture,
|
|
) ([]*fileChange, *augmentRuntimePaths, *augmentDocument) {
|
|
t.Helper()
|
|
document, err := readAugmentDocument()
|
|
if err != nil {
|
|
t.Fatalf("read Auggie document: %v", err)
|
|
}
|
|
paths, nodePath, err := preflightAugmentInstallation(fixture.config, document)
|
|
if err != nil {
|
|
t.Fatalf("preflight Auggie installation: %v", err)
|
|
}
|
|
hooks, _ := removeManagedAugmentHooks(document.hooks, "", paths.runtimeRoot)
|
|
hooks["PreToolUse"] = append(
|
|
hooks["PreToolUse"],
|
|
buildAugmentGroup(buildAugmentHandler(paths.guardWrapperPath)),
|
|
)
|
|
hooks["PostToolUse"] = append(
|
|
hooks["PostToolUse"],
|
|
buildAugmentGroup(buildAugmentHandler(paths.auditWrapperPath)),
|
|
)
|
|
rendered, err := renderAugmentDocument(document, hooks)
|
|
if err != nil {
|
|
t.Fatalf("render Auggie document: %v", err)
|
|
}
|
|
changes, err := prepareAugmentInstallationChanges(
|
|
fixture.config,
|
|
paths,
|
|
nodePath,
|
|
rendered,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("prepare Auggie installation: %v", err)
|
|
}
|
|
return changes, paths, document
|
|
}
|
|
|
|
func TestAugmentInstallRollsBackAllSevenFiles(t *testing.T) {
|
|
original := "{\"telemetryEnabled\":true}\n"
|
|
fixture := prepareAugmentFixture(
|
|
t,
|
|
augmentFixtureOptions{existingRaw: &original},
|
|
)
|
|
failed := false
|
|
fixture.plugin.rename = func(source, destination string) error {
|
|
if sameManagedPath(destination, fixture.configPath) &&
|
|
strings.HasSuffix(source, ".tmp") &&
|
|
!failed {
|
|
failed = true
|
|
return errors.New("injected Auggie settings failure")
|
|
}
|
|
return os.Rename(source, destination)
|
|
}
|
|
err := fixture.plugin.Install(fixture.config)
|
|
if err == nil || !strings.Contains(err.Error(), "injected Auggie settings failure") {
|
|
t.Fatalf("install error = %v", err)
|
|
}
|
|
raw, readErr := os.ReadFile(fixture.configPath)
|
|
if readErr != nil || string(raw) != original {
|
|
t.Fatalf("original settings changed: %q, %v", raw, readErr)
|
|
}
|
|
assertNoAugmentRuntimeWrites(t, fixture)
|
|
assertNoAugmentTransactionArtifacts(t, fixture.homeDir)
|
|
}
|
|
|
|
func TestPreparedAugmentInstallRejectsConcurrentSettingsChange(t *testing.T) {
|
|
original := `{"telemetryEnabled":true}`
|
|
fixture := prepareAugmentFixture(
|
|
t,
|
|
augmentFixtureOptions{existingRaw: &original},
|
|
)
|
|
changes, paths, document := prepareAugmentTestInstallation(t, fixture)
|
|
concurrent := []byte(
|
|
"{\"telemetryEnabled\":false,\"hooks\":{\"Notification\":[]}}\n",
|
|
)
|
|
if err := os.WriteFile(fixture.configPath, concurrent, 0600); err != nil {
|
|
t.Fatalf("write concurrent settings: %v", err)
|
|
}
|
|
err := writeManagedChanges(
|
|
changes,
|
|
"Install Augment Code CLI hooks",
|
|
nil,
|
|
paths.runtimeRoot,
|
|
paths.agentDirectory,
|
|
filepath.Dir(document.configPath),
|
|
"Auggie configuration directory",
|
|
)
|
|
if err == nil || !strings.Contains(err.Error(), "changed during installation") {
|
|
t.Fatalf("prepared install error = %v", err)
|
|
}
|
|
actual, readErr := os.ReadFile(fixture.configPath)
|
|
if readErr != nil || string(actual) != string(concurrent) {
|
|
t.Fatalf("concurrent settings changed: %q, %v", actual, readErr)
|
|
}
|
|
assertNoAugmentRuntimeWrites(t, fixture)
|
|
assertNoAugmentTransactionArtifacts(t, fixture.homeDir)
|
|
}
|
|
|
|
func TestAugmentInstallDetectsConcurrentSettingsIdentityReplacement(t *testing.T) {
|
|
source := []byte(`{"owner":"original"}`)
|
|
fixture := prepareAugmentFixture(
|
|
t,
|
|
augmentFixtureOptions{existingRaw: augmentString(string(source))},
|
|
)
|
|
mutated := false
|
|
fixture.plugin.rename = func(stagedPath, destination string) error {
|
|
if !mutated && strings.HasSuffix(stagedPath, ".tmp") {
|
|
mutated = true
|
|
external := fixture.configPath + ".external"
|
|
if err := os.Rename(fixture.configPath, external); err != nil {
|
|
return err
|
|
}
|
|
if err := os.WriteFile(fixture.configPath, source, 0600); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return os.Rename(stagedPath, destination)
|
|
}
|
|
err := fixture.plugin.Install(fixture.config)
|
|
if err == nil || !strings.Contains(err.Error(), "changed during installation") {
|
|
t.Fatalf("concurrent identity error = %v", err)
|
|
}
|
|
actual, readErr := os.ReadFile(fixture.configPath)
|
|
if readErr != nil || string(actual) != string(source) {
|
|
t.Fatalf("replacement settings changed: %q, %v", actual, readErr)
|
|
}
|
|
assertNoAugmentRuntimeWrites(t, fixture)
|
|
assertNoAugmentTransactionArtifacts(t, fixture.homeDir)
|
|
}
|
|
|
|
func TestPreparedAugmentInstallRejectsStaleSettingsBeforeStaging(t *testing.T) {
|
|
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
|
|
document, err := readAugmentDocument()
|
|
if err != nil {
|
|
t.Fatalf("read Auggie document: %v", err)
|
|
}
|
|
paths, nodePath, err := preflightAugmentInstallation(fixture.config, document)
|
|
if err != nil {
|
|
t.Fatalf("preflight Auggie installation: %v", err)
|
|
}
|
|
hooks, _ := removeManagedAugmentHooks(document.hooks, "", paths.runtimeRoot)
|
|
hooks["PreToolUse"] = append(
|
|
hooks["PreToolUse"],
|
|
buildAugmentGroup(buildAugmentHandler(paths.guardWrapperPath)),
|
|
)
|
|
hooks["PostToolUse"] = append(
|
|
hooks["PostToolUse"],
|
|
buildAugmentGroup(buildAugmentHandler(paths.auditWrapperPath)),
|
|
)
|
|
rendered, err := renderAugmentDocument(document, hooks)
|
|
if err != nil {
|
|
t.Fatalf("render Auggie document: %v", err)
|
|
}
|
|
writeAugmentTestFile(
|
|
t,
|
|
fixture.configPath,
|
|
[]byte("{\"hooks\":{\"Notification\":[]}}\n"),
|
|
0600,
|
|
)
|
|
_, err = prepareAugmentInstallationChanges(
|
|
fixture.config,
|
|
paths,
|
|
nodePath,
|
|
rendered,
|
|
)
|
|
if err == nil || !strings.Contains(err.Error(), "changed before update") {
|
|
t.Fatalf("stale settings error = %v", err)
|
|
}
|
|
assertNoAugmentRuntimeWrites(t, fixture)
|
|
assertNoAugmentTransactionArtifacts(t, fixture.homeDir)
|
|
}
|
|
|
|
func TestAugmentOrphanRuntimeArtifactsFailBeforeSettingsWrites(t *testing.T) {
|
|
for _, runtimeName := range []string{
|
|
"private.key",
|
|
augmentGuardScript,
|
|
augmentAuditScript,
|
|
augmentGuardWrapperName(),
|
|
augmentAuditWrapperName(),
|
|
"chain-state.json",
|
|
"status-cache.json",
|
|
"error.log",
|
|
} {
|
|
t.Run(runtimeName, func(t *testing.T) {
|
|
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
|
|
artifact := filepath.Join(fixture.agentDir, runtimeName)
|
|
writeAugmentTestFile(t, artifact, []byte("orphan\n"), 0600)
|
|
err := fixture.plugin.Install(fixture.config)
|
|
if err == nil ||
|
|
!strings.Contains(err.Error(), "identity cannot be verified") {
|
|
t.Fatalf("orphan runtime error = %v", err)
|
|
}
|
|
raw, readErr := os.ReadFile(artifact)
|
|
if readErr != nil || string(raw) != "orphan\n" {
|
|
t.Fatalf("orphan artifact changed: %q, %v", raw, readErr)
|
|
}
|
|
if _, err := os.Lstat(fixture.configPath); !errors.Is(err, os.ErrNotExist) {
|
|
t.Fatalf("settings written after orphan error: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAugmentMismatchedRuntimeIdentityFailsBeforeWrites(t *testing.T) {
|
|
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
|
|
writeAugmentTestObject(t, fixture.runtimeConfig, map[string]any{
|
|
"agent_id": "another-agent", "agent_name": augmentAgentKey,
|
|
})
|
|
original, err := os.ReadFile(fixture.runtimeConfig)
|
|
if err != nil {
|
|
t.Fatalf("read runtime config: %v", err)
|
|
}
|
|
err = fixture.plugin.Install(fixture.config)
|
|
if err == nil || !strings.Contains(err.Error(), "identity does not match") {
|
|
t.Fatalf("identity error = %v", err)
|
|
}
|
|
actual, readErr := os.ReadFile(fixture.runtimeConfig)
|
|
if readErr != nil || string(actual) != string(original) {
|
|
t.Fatalf("runtime config changed: %q, %v", actual, readErr)
|
|
}
|
|
if _, err := os.Lstat(fixture.configPath); !errors.Is(err, os.ErrNotExist) {
|
|
t.Fatalf("settings written after identity error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAugmentLinkedSettingsRuntimeAndWrappersAreRejected(t *testing.T) {
|
|
for _, kind := range []string{
|
|
"configuration",
|
|
"settings",
|
|
"runtime",
|
|
"agent-directory",
|
|
"runtime-config",
|
|
"wrapper",
|
|
} {
|
|
t.Run(kind, func(t *testing.T) {
|
|
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
|
|
target := filepath.Join(t.TempDir(), kind+"-target")
|
|
switch kind {
|
|
case "settings":
|
|
writeAugmentTestFile(t, target, []byte("{}\n"), 0600)
|
|
if err := os.MkdirAll(filepath.Dir(fixture.configPath), 0700); err != nil {
|
|
t.Fatalf("create settings directory: %v", err)
|
|
}
|
|
augmentSymlinkOrSkip(t, target, fixture.configPath)
|
|
case "configuration":
|
|
if err := os.MkdirAll(target, 0700); err != nil {
|
|
t.Fatalf("create target directory: %v", err)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(filepath.Dir(fixture.configPath)), 0700); err != nil {
|
|
t.Fatalf("create configuration parent: %v", err)
|
|
}
|
|
augmentSymlinkOrSkip(t, target, filepath.Dir(fixture.configPath))
|
|
case "runtime":
|
|
if err := os.MkdirAll(target, 0700); err != nil {
|
|
t.Fatalf("create target directory: %v", err)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(filepath.Dir(fixture.agentDir)), 0700); err != nil {
|
|
t.Fatalf("create runtime parent: %v", err)
|
|
}
|
|
augmentSymlinkOrSkip(t, target, filepath.Dir(fixture.agentDir))
|
|
case "agent-directory":
|
|
if err := os.MkdirAll(target, 0700); err != nil {
|
|
t.Fatalf("create target directory: %v", err)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(fixture.agentDir), 0700); err != nil {
|
|
t.Fatalf("create runtime root: %v", err)
|
|
}
|
|
augmentSymlinkOrSkip(t, target, fixture.agentDir)
|
|
case "runtime-config":
|
|
writeAugmentTestFile(
|
|
t,
|
|
target,
|
|
[]byte(`{"agent_id":"agent-1","agent_name":"augment"}`),
|
|
0600,
|
|
)
|
|
if err := os.MkdirAll(fixture.agentDir, 0700); err != nil {
|
|
t.Fatalf("create agent directory: %v", err)
|
|
}
|
|
augmentSymlinkOrSkip(t, target, fixture.runtimeConfig)
|
|
case "wrapper":
|
|
if err := fixture.plugin.Install(fixture.config); err != nil {
|
|
t.Fatalf("install Auggie hooks: %v", err)
|
|
}
|
|
writeAugmentTestFile(t, target, []byte("external wrapper\n"), 0600)
|
|
if err := os.Remove(fixture.guardWrapper); err != nil {
|
|
t.Fatalf("remove guard wrapper: %v", err)
|
|
}
|
|
augmentSymlinkOrSkip(t, target, fixture.guardWrapper)
|
|
}
|
|
err := fixture.plugin.Install(fixture.config)
|
|
if err == nil || !strings.Contains(err.Error(), "physical") {
|
|
t.Fatalf("linked %s error = %v", kind, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAugmentInstallValidatesRuntimeInputsBeforeWrites(t *testing.T) {
|
|
for _, testCase := range []struct {
|
|
name, field, value, want string
|
|
}{
|
|
{"agent-name", "agent_name", "codex", "requires agent name augment"},
|
|
{"agent-id", "agent_id", "../escape", "invalid agent ID"},
|
|
{"organization", "org_id", " ", "organization ID is required"},
|
|
{"key-id", "kid", " ", "key ID is required"},
|
|
{"private-key", "private_key", "invalid", "canonical 32-byte"},
|
|
{"token", "token", " ", "non-whitespace"},
|
|
{"base-url", "base_url", "https://api.elydora.com?q=1", "query parameters"},
|
|
{"guard-path", "guard_script_path", "outside", "managed agent directory"},
|
|
{"audit-path", "hook_script", "outside", "managed agent directory"},
|
|
} {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
|
|
switch testCase.field {
|
|
case "agent_name":
|
|
fixture.config.AgentName = testCase.value
|
|
case "agent_id":
|
|
fixture.config.AgentID = testCase.value
|
|
case "org_id":
|
|
fixture.config.OrgID = testCase.value
|
|
case "kid":
|
|
fixture.config.KID = testCase.value
|
|
case "private_key":
|
|
fixture.config.PrivateKey = testCase.value
|
|
case "token":
|
|
fixture.config.Token = testCase.value
|
|
case "base_url":
|
|
fixture.config.BaseURL = testCase.value
|
|
case "guard_script_path":
|
|
fixture.config.GuardScriptPath = testCase.value
|
|
case "hook_script":
|
|
fixture.config.HookScript = testCase.value
|
|
}
|
|
err := fixture.plugin.Install(fixture.config)
|
|
if err == nil || !strings.Contains(err.Error(), testCase.want) {
|
|
t.Fatalf("validation error = %v, want %q", err, testCase.want)
|
|
}
|
|
if _, err := os.Lstat(fixture.configPath); !errors.Is(err, os.ErrNotExist) {
|
|
t.Fatalf("settings written after validation error: %v", err)
|
|
}
|
|
assertNoAugmentRuntimeWrites(t, fixture)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAugmentInstallLeavesNoTransactionArtifacts(t *testing.T) {
|
|
fixture := prepareAugmentFixture(t, augmentFixtureOptions{})
|
|
if err := fixture.plugin.Install(fixture.config); err != nil {
|
|
t.Fatalf("install Auggie hooks: %v", err)
|
|
}
|
|
assertNoAugmentTransactionArtifacts(t, fixture.homeDir)
|
|
}
|
|
|
|
func TestAugmentUninstallPreservesSettingsAfterCommitFailure(t *testing.T) {
|
|
original := `{"owner":"user"}`
|
|
fixture := prepareAugmentFixture(
|
|
t,
|
|
augmentFixtureOptions{existingRaw: &original},
|
|
)
|
|
if err := fixture.plugin.Install(fixture.config); err != nil {
|
|
t.Fatalf("install Auggie hooks: %v", err)
|
|
}
|
|
before, err := os.ReadFile(fixture.configPath)
|
|
if err != nil {
|
|
t.Fatalf("read installed settings: %v", err)
|
|
}
|
|
fixture.plugin.rename = func(source, destination string) error {
|
|
if sameManagedPath(destination, fixture.configPath) &&
|
|
strings.HasSuffix(source, ".tmp") {
|
|
return errors.New("injected Auggie uninstall failure")
|
|
}
|
|
return os.Rename(source, destination)
|
|
}
|
|
err = fixture.plugin.Uninstall(augmentTestAgentID)
|
|
if err == nil || !strings.Contains(err.Error(), "injected Auggie uninstall failure") {
|
|
t.Fatalf("uninstall error = %v", err)
|
|
}
|
|
after, readErr := os.ReadFile(fixture.configPath)
|
|
if readErr != nil || string(after) != string(before) {
|
|
t.Fatalf("Auggie settings changed: %q, %v", after, readErr)
|
|
}
|
|
assertNoAugmentTransactionArtifacts(t, fixture.homeDir)
|
|
}
|