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

259 lines
9.9 KiB
Go

package plugins
import (
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
)
func TestCopilotRegistryUsesNativeUserHooks(t *testing.T) {
want := AgentRegistryEntry{
Name: "GitHub Copilot CLI",
ConfigDir: "~/.copilot/hooks",
ConfigFile: "elydora-audit.json",
}
if got := SupportedAgents[copilotAgentKey]; !reflect.DeepEqual(got, want) {
t.Fatalf("Copilot registry = %#v, want %#v", got, want)
}
}
func TestCopilotInstallPreservesUserHooksMigratesLegacyAndIsIdempotent(t *testing.T) {
fixture := prepareCopilotFixture(t, copilotFixtureOptions{
userRaw: copilotJSON(map[string]any{
"version": float64(1),
"disableAllHooks": false,
"hooks": map[string]any{
"sessionStart": []any{map[string]any{"type": "command", "command": "user-session"}},
"preToolUse": []any{map[string]any{"type": "command", "command": "user-pre"}},
},
}),
})
writeCopilotObject(t, fixture.legacyPath, legacyCopilotConfig(fixture, map[string]any{
"notification": []any{map[string]any{"type": "command", "command": "user-notification"}},
}))
installCopilotFixture(t, fixture)
installCopilotFixture(t, fixture)
settings := readCopilotObject(t, fixture.configPath)
if settings["disableAllHooks"] != false {
t.Fatalf("disableAllHooks = %#v", settings["disableAllHooks"])
}
hooks := requireCopilotObject(t, settings["hooks"])
if got := requireCopilotArray(t, hooks["sessionStart"]); len(got) != 1 {
t.Fatalf("sessionStart hooks = %#v", got)
}
preHooks := requireCopilotArray(t, hooks["preToolUse"])
postHooks := requireCopilotArray(t, hooks["postToolUse"])
if len(preHooks) != 2 || len(postHooks) != 1 {
t.Fatalf("Copilot hooks = %#v", hooks)
}
if requireCopilotObject(t, preHooks[0])["command"] != "user-pre" {
t.Fatalf("first preToolUse hook = %#v", preHooks[0])
}
assertNativeCopilotHandler(t, managedCopilotTestHandler(
t, settings, "preToolUse", copilotGuardScript,
))
assertNativeCopilotHandler(t, managedCopilotTestHandler(
t, settings, "postToolUse", copilotAuditScript,
))
legacy := readCopilotObject(t, fixture.legacyPath)
legacyHooks := requireCopilotObject(t, legacy["hooks"])
if len(legacyHooks) != 1 || legacyHooks["notification"] == nil {
t.Fatalf("migrated project hooks = %#v", legacyHooks)
}
runtimeConfig := readCopilotObject(t, fixture.runtimeConfig)
if runtimeConfig["agent_name"] != copilotAgentKey ||
runtimeConfig["agent_id"] != copilotTestAgentID {
t.Fatalf("runtime config = %#v", runtimeConfig)
}
privateKey, err := os.ReadFile(fixture.privateKey)
if err != nil || string(privateKey) != fixture.config.PrivateKey {
t.Fatalf("private key = %q, %v", privateKey, err)
}
if exists, err := regularFileExists(fixture.hookPath, "test audit runtime"); err != nil || !exists {
t.Fatalf("audit runtime exists = %v, %v", exists, err)
}
}
func TestCopilotMigrationRemovesOwnedProjectFile(t *testing.T) {
fixture := prepareCopilotFixture(t, copilotFixtureOptions{})
writeCopilotObject(t, fixture.legacyPath, legacyCopilotConfig(fixture, nil))
installCopilotFixture(t, fixture)
if _, err := os.Stat(fixture.legacyPath); !os.IsNotExist(err) {
t.Fatalf("owned project hook file remains: %v", err)
}
settings := readCopilotObject(t, fixture.configPath)
assertNativeCopilotHandler(t, managedCopilotTestHandler(
t, settings, "preToolUse", copilotGuardScript,
))
}
func TestCopilotCommandsBlockAndForwardNativePayloadByteForByte(t *testing.T) {
fixture := prepareCopilotFixture(t, copilotFixtureOptions{})
installCopilotFixture(t, fixture)
capturePath := filepath.Join(t.TempDir(), "captured-event.json")
captureScript := "const fs = require('node:fs');\n" +
"fs.writeFileSync(process.env.ELYDORA_CAPTURE, fs.readFileSync(0));\n"
if err := os.WriteFile(fixture.hookPath, []byte(captureScript), 0700); err != nil {
t.Fatalf("write capture audit runtime: %v", err)
}
settings := readCopilotObject(t, fixture.configPath)
prePayload := `{"sessionId":"session-1","timestamp":1,"cwd":"project","toolName":"powershell","toolArgs":{"command":"Get-ChildItem"}}` + "\n"
guard := managedCopilotTestHandler(t, settings, "preToolUse", copilotGuardScript)
guardResult := runCopilotHandler(t, guard, prePayload)
if guardResult.exitCode != 2 || !strings.Contains(guardResult.stderr, "Agent is frozen by Elydora") {
t.Fatalf("guard result = %#v", guardResult)
}
postPayload := `{"sessionId":"session-1","timestamp":2,"cwd":"project","toolName":"powershell","toolArgs":{"command":"Get-ChildItem"},"toolResult":{"output":"ok"}}` + "\n"
audit := managedCopilotTestHandler(t, settings, "postToolUse", copilotAuditScript)
auditResult := runCopilotHandler(t, audit, postPayload, "ELYDORA_CAPTURE="+capturePath)
if auditResult.exitCode != 0 {
t.Fatalf("audit result = %#v", auditResult)
}
captured, err := os.ReadFile(capturePath)
if err != nil || string(captured) != postPayload {
t.Fatalf("captured payload = %q, %v", captured, err)
}
}
func TestCopilotEmptyHomeOverrideUsesOfficialDefault(t *testing.T) {
fixture := prepareCopilotFixture(t, copilotFixtureOptions{emptyOverride: true})
installCopilotFixture(t, fixture)
settings := readCopilotObject(t, fixture.configPath)
assertNativeCopilotHandler(t, managedCopilotTestHandler(
t, settings, "preToolUse", copilotGuardScript,
))
status, err := fixture.plugin.Status()
if err != nil || status.ConfigPath != fixture.configPath {
t.Fatalf("Copilot status = %#v, %v", status, err)
}
}
func TestCopilotStatusRequiresEnabledPairIdentityAndRuntimeFiles(t *testing.T) {
fixture := prepareCopilotFixture(t, copilotFixtureOptions{})
installCopilotFixture(t, fixture)
status, err := fixture.plugin.Status()
if err != nil || !status.Installed || !status.HookConfigured || !status.HookScriptExists {
t.Fatalf("installed status = %#v, %v", status, err)
}
settings := readCopilotObject(t, fixture.configPath)
hooks := requireCopilotObject(t, settings["hooks"])
delete(hooks, "postToolUse")
writeCopilotObject(t, fixture.configPath, settings)
status, err = fixture.plugin.Status()
if err != nil || status.HookConfigured || status.Installed {
t.Fatalf("incomplete status = %#v, %v", status, err)
}
installCopilotFixture(t, fixture)
settings = readCopilotObject(t, fixture.configPath)
settings["disableAllHooks"] = true
writeCopilotObject(t, fixture.configPath, settings)
status, err = fixture.plugin.Status()
if err != nil || status.HookConfigured || status.Installed {
t.Fatalf("disabled status = %#v, %v", status, err)
}
settings["disableAllHooks"] = false
writeCopilotObject(t, fixture.configPath, settings)
if err := os.Remove(fixture.hookPath); err != nil {
t.Fatalf("remove audit runtime: %v", err)
}
status, err = fixture.plugin.Status()
if err != nil || !status.HookConfigured || status.HookScriptExists || status.Installed {
t.Fatalf("missing runtime status = %#v, %v", status, err)
}
installCopilotFixture(t, fixture)
runtimeConfig := readCopilotObject(t, fixture.runtimeConfig)
runtimeConfig["agent_id"] = "another-agent"
writeCopilotObject(t, fixture.runtimeConfig, runtimeConfig)
status, err = fixture.plugin.Status()
if err != nil || status.HookScriptExists || status.Installed {
t.Fatalf("identity mismatch status = %#v, %v", status, err)
}
}
func TestCopilotStatusRecognizesActiveProjectDelivery(t *testing.T) {
fixture := prepareCopilotFixture(t, copilotFixtureOptions{})
writeCopilotObject(t, fixture.legacyPath, legacyCopilotConfig(fixture, nil))
writeCopilotObject(t, fixture.runtimeConfig, map[string]any{
"agent_id": copilotTestAgentID, "agent_name": copilotAgentKey,
})
if err := os.WriteFile(fixture.hookPath, []byte("process.stdin.resume();\n"), 0700); err != nil {
t.Fatalf("write audit runtime: %v", err)
}
status, err := fixture.plugin.Status()
if err != nil || !status.Installed || status.ConfigPath != fixture.legacyPath {
t.Fatalf("project delivery status = %#v, %v", status, err)
}
}
func TestCopilotUninstallRemovesExactOwnershipAndPreservesUserEntries(t *testing.T) {
fixture := prepareCopilotFixture(t, copilotFixtureOptions{
userRaw: copilotJSON(map[string]any{
"version": float64(1),
"hooks": map[string]any{
"notification": []any{map[string]any{"type": "command", "command": "keep"}},
},
}),
})
installCopilotFixture(t, fixture)
settings := readCopilotObject(t, fixture.configPath)
hooks := requireCopilotObject(t, settings["hooks"])
preHooks := requireCopilotArray(t, hooks["preToolUse"])
nodePath, err := resolveNodeRuntime()
if err != nil {
t.Fatalf("resolve Node.js: %v", err)
}
preHooks = append(preHooks,
buildCopilotHandler(
nodePath,
filepath.Join(filepath.Dir(fixture.agentDir), "agent-10", copilotGuardScript),
),
map[string]any{
"type": "command", "bash": "echo elydora", "powershell": "Write-Output elydora",
"timeoutSec": copilotHookTimeout,
},
)
hooks["preToolUse"] = preHooks
writeCopilotObject(t, fixture.configPath, settings)
uninstallID := copilotTestAgentID
if runtime.GOOS == "windows" {
uninstallID = strings.ToUpper(uninstallID)
}
if err := fixture.plugin.Uninstall(uninstallID); err != nil {
t.Fatalf("uninstall GitHub Copilot hooks: %v", err)
}
remaining := readCopilotObject(t, fixture.configPath)
remainingHooks := requireCopilotObject(t, remaining["hooks"])
if remainingHooks["postToolUse"] != nil || len(requireCopilotArray(t, remainingHooks["preToolUse"])) != 2 {
t.Fatalf("remaining hooks = %#v", remainingHooks)
}
if len(requireCopilotArray(t, remainingHooks["notification"])) != 1 {
t.Fatalf("notification hooks = %#v", remainingHooks["notification"])
}
}
func TestCopilotUninstallLeavesAbsentSourcesAbsent(t *testing.T) {
fixture := prepareCopilotFixture(t, copilotFixtureOptions{})
if err := fixture.plugin.Uninstall(copilotTestAgentID); err != nil {
t.Fatalf("uninstall absent GitHub Copilot hooks: %v", err)
}
for _, path := range []string{fixture.configPath, fixture.legacyPath} {
if _, err := os.Stat(path); !os.IsNotExist(err) {
t.Fatalf("absent source created at %s: %v", path, err)
}
}
}