test(cc-config): cover the env runClaudePlugin actually spawns with

`autoInstallSkillPlugins` takes `run` as a seam, so every existing test
stops short of the real `spawnSync` — the line that decides whether
CLAUDE_CONFIG_DIR is pinned was untested. Restoring the unconditional
pin here (the bug that sent `.claude.json` to `~/.claude/.claude.json`)
kept the whole suite green.

Export `runClaudePlugin` and drive it against a fake `claude` on PATH
that prints the variable back: one test for CLAUDE_HOME set, one for
unset. Mutation-checked — the unconditional pin fails the second.
This commit is contained in:
2026-09-09 10:16:58 +07:00
parent 114823ceef
commit 697384145a
2 changed files with 53 additions and 0 deletions
+49
View File
@@ -238,3 +238,52 @@ describe("autoInstallSkillPlugins", () => {
assert.deepEqual(run.calls, []);
});
});
describe("runClaudePlugin: the env it spawns with", () => {
// Every other test in this file injects `run`, so nothing exercises the real
// spawn. That gap is how a wrong CLAUDE_CONFIG_DIR pin lived here: the CLI
// writes `.claude.json` INSIDE that dir, while cc-discovery reads it beside
// ~/.claude by default, so pinning it unconditionally aimed writes at a file
// nothing reads. cc-plugin-cli.js's runClaude has the mirror of this test.
const PRINT_ENV = 'printf "%s\\n" "CLAUDE_CONFIG_DIR=${CLAUDE_CONFIG_DIR-<unset>}"';
function stubClaude(body) {
const bin = fs.mkdtempSync(path.join(os.tmpdir(), "agenthub-skillplug-bin-"));
const file = path.join(bin, "claude");
fs.writeFileSync(file, `#!/usr/bin/env bash\n${body}\n`);
fs.chmodSync(file, 0o755);
const originalPath = process.env.PATH;
process.env.PATH = `${bin}${path.delimiter}${originalPath}`;
return () => {
process.env.PATH = originalPath;
fs.rmSync(bin, { recursive: true, force: true });
};
}
it("leaves CLAUDE_CONFIG_DIR unset when CLAUDE_HOME is not set", () => {
const original = process.env.CLAUDE_HOME;
delete process.env.CLAUDE_HOME;
const restore = stubClaude(PRINT_ENV);
try {
const res = skillPlugins.runClaudePlugin(["plugin", "list"], 10_000);
assert.equal(res.output.trim(), "CLAUDE_CONFIG_DIR=<unset>");
} finally {
restore();
if (original !== undefined) process.env.CLAUDE_HOME = original;
}
});
it("pins CLAUDE_CONFIG_DIR to CLAUDE_HOME when the operator set it", () => {
const original = process.env.CLAUDE_HOME;
process.env.CLAUDE_HOME = "/tmp/some-other-claude-home";
const restore = stubClaude(PRINT_ENV);
try {
const res = skillPlugins.runClaudePlugin(["plugin", "list"], 10_000);
assert.equal(res.output.trim(), "CLAUDE_CONFIG_DIR=/tmp/some-other-claude-home");
} finally {
restore();
if (original === undefined) delete process.env.CLAUDE_HOME;
else process.env.CLAUDE_HOME = original;
}
});
});
+4
View File
@@ -220,4 +220,8 @@ module.exports = {
requiredSkillPlugins,
missingSkillPlugins,
autoInstallSkillPlugins,
// Exported only so a test can pin the env it spawns with. Every other test
// injects `run` and so never reaches the real spawn — which is how an
// unconditional CLAUDE_CONFIG_DIR pin could be restored here unnoticed.
runClaudePlugin,
};