249 lines
8.3 KiB
Go
249 lines
8.3 KiB
Go
package plugins
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestCopilotPrepareRejectsSameContentStaleHookSnapshot(t *testing.T) {
|
|
fixture := prepareCopilotFixture(t, copilotFixtureOptions{
|
|
userRaw: copilotJSON(map[string]any{
|
|
"version": float64(1),
|
|
"hooks": map[string]any{"notification": []any{}},
|
|
}),
|
|
})
|
|
sources, _, err := readCopilotSources()
|
|
if err != nil {
|
|
t.Fatalf("read GitHub Copilot sources: %v", err)
|
|
}
|
|
paths, nodePath, err := preflightCopilotInstallation(fixture.config, sources)
|
|
if err != nil {
|
|
t.Fatalf("preflight GitHub Copilot installation: %v", err)
|
|
}
|
|
rendered, err := renderCopilotInstallation(
|
|
sources,
|
|
paths.guardPath,
|
|
paths.auditPath,
|
|
nodePath,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("render GitHub Copilot installation: %v", err)
|
|
}
|
|
original, err := os.ReadFile(fixture.configPath)
|
|
if err != nil {
|
|
t.Fatalf("read user hooks: %v", err)
|
|
}
|
|
replacement := fixture.configPath + ".replacement"
|
|
if err := os.WriteFile(replacement, original, 0600); err != nil {
|
|
t.Fatalf("write replacement hooks: %v", err)
|
|
}
|
|
if err := os.Rename(replacement, fixture.configPath); err != nil {
|
|
t.Fatalf("replace user hooks: %v", err)
|
|
}
|
|
|
|
_, err = prepareCopilotInstallation(fixture.config, sources, rendered)
|
|
if err == nil || !strings.Contains(err.Error(), "changed before update") {
|
|
t.Fatalf("prepare error = %v", err)
|
|
}
|
|
assertCopilotRuntimeAbsent(t, fixture)
|
|
}
|
|
|
|
func TestCopilotInstallRollsBackWhenEffectiveSettingsChange(t *testing.T) {
|
|
fixture := prepareCopilotFixture(t, copilotFixtureOptions{
|
|
userSettingsRaw: copilotJSON(map[string]any{"disableAllHooks": false}),
|
|
})
|
|
commits := 0
|
|
mutated := false
|
|
fixture.plugin.rename = func(source, destination string) error {
|
|
if err := os.Rename(source, destination); err != nil {
|
|
return err
|
|
}
|
|
if !mutated && strings.HasSuffix(source, ".tmp") {
|
|
commits++
|
|
if commits == 3 {
|
|
mutated = true
|
|
writeCopilotObject(t, fixture.userSettings, map[string]any{
|
|
"disableAllHooks": true,
|
|
})
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
err := fixture.plugin.Install(fixture.config)
|
|
if err == nil || !strings.Contains(err.Error(), "user settings changed") {
|
|
t.Fatalf("install error = %v", err)
|
|
}
|
|
settings := readCopilotObject(t, fixture.userSettings)
|
|
if settings["disableAllHooks"] != true {
|
|
t.Fatalf("concurrent settings = %#v", settings)
|
|
}
|
|
if _, err := os.Lstat(fixture.configPath); !errors.Is(err, os.ErrNotExist) {
|
|
t.Fatalf("hook source exists after rollback: %v", err)
|
|
}
|
|
assertCopilotRuntimeAbsent(t, fixture)
|
|
assertNoCopilotTransactionArtifacts(t, fixture.homeDir, fixture.projectDir)
|
|
}
|
|
|
|
func TestCopilotIdempotentCommitChecksSameContentSettingsIdentity(t *testing.T) {
|
|
fixture := prepareCopilotFixture(t, copilotFixtureOptions{
|
|
userSettingsRaw: copilotJSON(map[string]any{"disableAllHooks": false}),
|
|
})
|
|
installCopilotFixture(t, fixture)
|
|
sources, _, err := readCopilotSources()
|
|
if err != nil {
|
|
t.Fatalf("read GitHub Copilot sources: %v", err)
|
|
}
|
|
paths, nodePath, err := preflightCopilotInstallation(fixture.config, sources)
|
|
if err != nil {
|
|
t.Fatalf("preflight GitHub Copilot installation: %v", err)
|
|
}
|
|
rendered, err := renderCopilotInstallation(
|
|
sources,
|
|
paths.guardPath,
|
|
paths.auditPath,
|
|
nodePath,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("render GitHub Copilot installation: %v", err)
|
|
}
|
|
prepared, err := prepareCopilotInstallation(fixture.config, sources, rendered)
|
|
if err != nil {
|
|
t.Fatalf("prepare GitHub Copilot installation: %v", err)
|
|
}
|
|
original, err := os.ReadFile(fixture.userSettings)
|
|
if err != nil {
|
|
t.Fatalf("read user settings: %v", err)
|
|
}
|
|
replacement := fixture.userSettings + ".replacement"
|
|
if err := os.WriteFile(replacement, original, 0600); err != nil {
|
|
t.Fatalf("write replacement settings: %v", err)
|
|
}
|
|
if err := os.Rename(replacement, fixture.userSettings); err != nil {
|
|
t.Fatalf("replace user settings: %v", err)
|
|
}
|
|
|
|
err = commitCopilotInstallation(prepared, fixture.plugin.rename)
|
|
if err == nil || !strings.Contains(err.Error(), "user settings changed") {
|
|
t.Fatalf("commit error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCopilotInstallPreservesOrphanedRuntimeWithoutIdentity(t *testing.T) {
|
|
fixture := prepareCopilotFixture(t, copilotFixtureOptions{})
|
|
if err := os.MkdirAll(fixture.agentDir, 0700); err != nil {
|
|
t.Fatalf("create agent runtime directory: %v", err)
|
|
}
|
|
if err := os.WriteFile(fixture.hookPath, []byte("orphaned\n"), 0700); err != nil {
|
|
t.Fatalf("write orphaned runtime: %v", err)
|
|
}
|
|
|
|
err := fixture.plugin.Install(fixture.config)
|
|
if err == nil || !strings.Contains(err.Error(), "cannot be verified") {
|
|
t.Fatalf("install error = %v", err)
|
|
}
|
|
current, readErr := os.ReadFile(fixture.hookPath)
|
|
if readErr != nil || string(current) != "orphaned\n" {
|
|
t.Fatalf("orphaned runtime = %q, %v", current, readErr)
|
|
}
|
|
for _, path := range []string{
|
|
fixture.configPath, fixture.guardPath, fixture.runtimeConfig, fixture.privateKey,
|
|
} {
|
|
if _, err := os.Lstat(path); !errors.Is(err, os.ErrNotExist) {
|
|
t.Fatalf("unexpected file at %s: %v", path, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCopilotInstallRejectsLinkedHomeAndRuntimeDirectories(t *testing.T) {
|
|
for _, location := range []string{"home", "runtime"} {
|
|
t.Run(location, func(t *testing.T) {
|
|
fixture := prepareCopilotFixture(t, copilotFixtureOptions{})
|
|
target := filepath.Join(filepath.Dir(fixture.homeDir), location+"-target")
|
|
if err := os.MkdirAll(target, 0700); err != nil {
|
|
t.Fatalf("create symlink target: %v", err)
|
|
}
|
|
linkPath := fixture.copilotHome
|
|
if location == "runtime" {
|
|
linkPath = fixture.agentDir
|
|
if err := os.MkdirAll(filepath.Dir(linkPath), 0700); err != nil {
|
|
t.Fatalf("create runtime root: %v", err)
|
|
}
|
|
}
|
|
if err := os.Symlink(target, linkPath); err != nil {
|
|
t.Skipf("create directory symlink: %v", err)
|
|
}
|
|
|
|
err := fixture.plugin.Install(fixture.config)
|
|
if err == nil || !strings.Contains(err.Error(), "physical directory") {
|
|
t.Fatalf("install error = %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCopilotStatusRequiresExactRuntimeSourcesAndPrivateKey(t *testing.T) {
|
|
for _, name := range []string{"guard", "audit"} {
|
|
t.Run(name, func(t *testing.T) {
|
|
fixture := prepareCopilotFixture(t, copilotFixtureOptions{})
|
|
installCopilotFixture(t, fixture)
|
|
path := fixture.guardPath
|
|
if name == "audit" {
|
|
path = fixture.hookPath
|
|
}
|
|
if err := os.WriteFile(path, []byte("// tampered\n"), 0700); err != nil {
|
|
t.Fatalf("tamper %s runtime: %v", name, err)
|
|
}
|
|
status, err := fixture.plugin.Status()
|
|
if err != nil || status.Installed || status.HookScriptExists {
|
|
t.Fatalf("tampered %s status = %#v, %v", name, status, err)
|
|
}
|
|
})
|
|
}
|
|
fixture := prepareCopilotFixture(t, copilotFixtureOptions{})
|
|
installCopilotFixture(t, fixture)
|
|
if err := os.WriteFile(fixture.privateKey, []byte("invalid"), 0600); err != nil {
|
|
t.Fatalf("corrupt private key: %v", err)
|
|
}
|
|
if _, err := fixture.plugin.Status(); err == nil ||
|
|
!strings.Contains(err.Error(), "canonical 32-byte") {
|
|
t.Fatalf("invalid key status error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCopilotValidatesManagedRuntimeInputsBeforeWrites(t *testing.T) {
|
|
for _, testCase := range []struct {
|
|
name string
|
|
edit func(*copilotFixture)
|
|
want string
|
|
}{
|
|
{"agent name", func(f *copilotFixture) { f.config.AgentName = "codex" }, "requires agent name copilot"},
|
|
{"organization", func(f *copilotFixture) { f.config.OrgID = " " }, "organization ID is required"},
|
|
{"key ID", func(f *copilotFixture) { f.config.KID = "\t" }, "key ID is required"},
|
|
{"token", func(f *copilotFixture) { f.config.Token = " " }, "token must contain"},
|
|
{"private key", func(f *copilotFixture) { f.config.PrivateKey = "invalid" }, "canonical 32-byte"},
|
|
{"base URL", func(f *copilotFixture) {
|
|
f.config.BaseURL = "https://api.elydora.com/path?token=secret"
|
|
}, "query parameters"},
|
|
{"guard path", func(f *copilotFixture) {
|
|
f.config.GuardScriptPath = filepath.Join(f.homeDir, "outside", "guard.js")
|
|
}, "managed agent directory"},
|
|
{"audit path", func(f *copilotFixture) {
|
|
f.config.HookScript = filepath.Join(f.homeDir, "outside", "hook.js")
|
|
}, "managed agent directory"},
|
|
} {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
fixture := prepareCopilotFixture(t, copilotFixtureOptions{})
|
|
testCase.edit(fixture)
|
|
err := fixture.plugin.Install(fixture.config)
|
|
if err == nil || !strings.Contains(err.Error(), testCase.want) {
|
|
t.Fatalf("install error = %v, want %q", err, testCase.want)
|
|
}
|
|
assertCopilotRuntimeAbsent(t, fixture)
|
|
})
|
|
}
|
|
}
|