fix(copilot): implement official transactional hook contract
This commit is contained in:
1 parent
050d0043ea
commit
827374e7f8
16 files changed
+2243
-510
No files matched your search
@@ -0,0 +1,109 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOfficialCopilot1071LoadsAllManagedHooks(t *testing.T) {
|
||||
entry := os.Getenv("ELYDORA_COPILOT_ENTRY")
|
||||
runtimeEntry := os.Getenv("ELYDORA_COPILOT_RUNTIME_ENTRY")
|
||||
nodePath, nodeErr := exec.LookPath("node")
|
||||
if entry == "" || runtimeEntry == "" || nodeErr != nil {
|
||||
t.Skip(
|
||||
"set ELYDORA_COPILOT_ENTRY and ELYDORA_COPILOT_RUNTIME_ENTRY to official package files",
|
||||
)
|
||||
}
|
||||
fixture := prepareCopilotFixture(t, copilotFixtureOptions{})
|
||||
installCopilotFixture(t, fixture)
|
||||
environment := append(os.Environ(),
|
||||
"HOME="+fixture.homeDir,
|
||||
"USERPROFILE="+fixture.homeDir,
|
||||
"COPILOT_HOME="+fixture.copilotHome,
|
||||
)
|
||||
version := exec.Command(nodePath, entry, "--version") // #nosec G204 -- test paths are explicit environment inputs.
|
||||
version.Dir = fixture.projectDir
|
||||
version.Env = environment
|
||||
versionOutput, err := version.CombinedOutput()
|
||||
if err != nil || !strings.Contains(string(versionOutput), "GitHub Copilot CLI 1.0.71.") {
|
||||
t.Fatalf("official Copilot version = %q, %v", versionOutput, err)
|
||||
}
|
||||
|
||||
source := `
|
||||
import { createRequire } from 'node:module';
|
||||
const require = createRequire(import.meta.url);
|
||||
const runtime = require(process.env.ELYDORA_COPILOT_RUNTIME_ENTRY);
|
||||
const session = await runtime.hookSessionCreate({
|
||||
cwd: process.env.ELYDORA_PROJECT,
|
||||
repoRoot: process.env.ELYDORA_PROJECT,
|
||||
sessionId: 'elydora-go-official-test',
|
||||
settingsJson: '{}',
|
||||
userHooksDir: process.env.ELYDORA_HOOKS,
|
||||
allowLocalhost: false,
|
||||
allowHttpAuthHooks: false,
|
||||
discoverPolicies: false,
|
||||
});
|
||||
try {
|
||||
const snapshot = JSON.parse(await runtime.hookSessionSnapshot(session.handle));
|
||||
console.log(JSON.stringify({ load: session.load, snapshot }));
|
||||
} finally {
|
||||
runtime.hookSessionDispose(session.handle);
|
||||
}
|
||||
`
|
||||
load := exec.Command( // #nosec G204 -- nodePath is resolved through exec.LookPath.
|
||||
nodePath,
|
||||
"--input-type=module",
|
||||
"--eval",
|
||||
source,
|
||||
)
|
||||
load.Dir = fixture.projectDir
|
||||
load.Env = append(environment,
|
||||
"ELYDORA_COPILOT_RUNTIME_ENTRY="+runtimeEntry,
|
||||
"ELYDORA_HOOKS="+filepath.Dir(fixture.configPath),
|
||||
"ELYDORA_PROJECT="+fixture.projectDir,
|
||||
)
|
||||
loaded, err := load.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("load official Copilot hooks: %s: %v", loaded, err)
|
||||
}
|
||||
var result map[string]any
|
||||
if err := json.Unmarshal(loaded, &result); err != nil {
|
||||
t.Fatalf("decode official Copilot loader output %q: %v", loaded, err)
|
||||
}
|
||||
loadResult := requireCopilotObject(t, result["load"])
|
||||
if loadResult["hookCount"] != float64(3) ||
|
||||
len(requireCopilotArray(t, loadResult["errors"])) != 0 ||
|
||||
len(requireCopilotArray(t, loadResult["warnings"])) != 0 {
|
||||
t.Fatalf("official Copilot load result = %#v", loadResult)
|
||||
}
|
||||
snapshot := requireCopilotObject(t, result["snapshot"])
|
||||
hooks := requireCopilotArray(t, snapshot["hooks"])
|
||||
events := make([]string, 0, len(hooks))
|
||||
for _, value := range hooks {
|
||||
hook := requireCopilotObject(t, value)
|
||||
events = append(events, stringValue(hook["eventName"]))
|
||||
if filepath.Base(stringValue(hook["source"])) != copilotConfigFile {
|
||||
t.Fatalf("official Copilot hook source = %#v", hook["source"])
|
||||
}
|
||||
var spec map[string]any
|
||||
if err := json.Unmarshal([]byte(stringValue(hook["specJson"])), &spec); err != nil {
|
||||
t.Fatalf("decode official hook spec: %v", err)
|
||||
}
|
||||
config := requireCopilotObject(t, spec["config"])
|
||||
if config["type"] != "command" || config["timeoutSec"] != copilotHookTimeout ||
|
||||
!strings.Contains(strings.ToLower(stringValue(config["bash"])), "node") ||
|
||||
!strings.HasPrefix(stringValue(config["powershell"]), "& ") {
|
||||
t.Fatalf("official Copilot hook spec = %#v", spec)
|
||||
}
|
||||
}
|
||||
sort.Strings(events)
|
||||
want := []string{"postToolUse", "postToolUseFailure", "preToolUse"}
|
||||
if strings.Join(events, ",") != strings.Join(want, ",") {
|
||||
t.Fatalf("official Copilot events = %#v, want %#v", events, want)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user