mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-14 20:25:26 +02:00
188 lines
6.9 KiB
JavaScript
188 lines
6.9 KiB
JavaScript
const AGENT_COUNT = require('fs').readdirSync(require('path').join(__dirname,'../../agents')).filter(f => f.endsWith('.md')).length;
|
|
/**
|
|
* Tests for the ECC Agent IR converter CLI (scripts/agent-convert.js).
|
|
*
|
|
* Verifies the CLI contract: `--check`, `--json`, `--out` (write and
|
|
* side-effect-free `--dry-run`), and argument/exit-code behavior.
|
|
*/
|
|
|
|
const assert = require("assert")
|
|
const fs = require("fs")
|
|
const os = require("os")
|
|
const path = require("path")
|
|
const { execFileSync } = require("child_process")
|
|
|
|
const CLI = path.join(__dirname, "..", "..", "scripts", "agent-convert.js")
|
|
const NODE = process.execPath
|
|
|
|
function runTest(name, fn) {
|
|
try {
|
|
fn()
|
|
console.log(` ✓ ${name}`)
|
|
return true
|
|
} catch (error) {
|
|
console.log(` ✗ ${name}`)
|
|
console.error(` ${error.message}`)
|
|
return false
|
|
}
|
|
}
|
|
|
|
function runCli(args) {
|
|
return execFileSync(NODE, [CLI, ...args], { encoding: "utf8" })
|
|
}
|
|
|
|
function tmpdir() {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), "ecc-agent-convert-"))
|
|
}
|
|
|
|
function main() {
|
|
let passed = 0
|
|
let failed = 0
|
|
|
|
const tests = [
|
|
["--check exits 0 and reports AGENT_COUNT agents", () => {
|
|
const out = runCli(["--check"])
|
|
assert.match(out, new RegExp(`OK: ${AGENT_COUNT} agents parsed and validated`))
|
|
}],
|
|
|
|
["--json emits a machine-readable summary", () => {
|
|
const out = runCli(["--from", "claude", "--to", "pi", "--json"])
|
|
const summary = JSON.parse(out)
|
|
assert.strictEqual(summary.schema, "ecc.agent-ir.v1")
|
|
assert.strictEqual(summary.from, "claude")
|
|
assert.strictEqual(summary.to, "pi")
|
|
assert.strictEqual(summary.converted, AGENT_COUNT)
|
|
assert.ok(Array.isArray(summary.warningsDetail))
|
|
}],
|
|
|
|
["--out writes AGENT_COUNT files", () => {
|
|
const dir = tmpdir()
|
|
runCli(["--from", "claude", "--to", "pi", "--out", dir])
|
|
const files = fs.readdirSync(dir).filter(f => f.endsWith(".md"))
|
|
assert.strictEqual(files.length, AGENT_COUNT, "expected AGENT_COUNT emitted files")
|
|
const planner = fs.readFileSync(path.join(dir, "planner.md"), "utf8")
|
|
assert.match(planner, /name: planner\n/);
|
|
assert.match(planner, /prompt_mode: replace/);
|
|
}],
|
|
|
|
["--out preserves user-owned agents before writing any output", () => {
|
|
const dir = tmpdir();
|
|
const target = path.join(dir, "planner.md");
|
|
fs.writeFileSync(target, "USER_OWNED_AGENT\n");
|
|
assert.throws(() => runCli(["--out", dir]), /refusing to overwrite existing agent/);
|
|
assert.strictEqual(fs.readFileSync(target, "utf8"), "USER_OWNED_AGENT\n");
|
|
assert.deepStrictEqual(fs.readdirSync(dir), ["planner.md"]);
|
|
}],
|
|
|
|
["--out rejects a file created at exclusive-open time without overwriting bytes", () => {
|
|
const root = tmpdir();
|
|
const dir = path.join(root, "out");
|
|
const preload = path.join(root, "race.js");
|
|
fs.mkdirSync(dir);
|
|
fs.writeFileSync(preload, `
|
|
const fs = require('fs');
|
|
const open = fs.openSync;
|
|
let injected = false;
|
|
fs.openSync = function(file, flags, mode) {
|
|
if (!injected && flags === 'wx' && String(file).endsWith('.md')) {
|
|
injected = true;
|
|
const fd = open(file, 'wx', 0o600);
|
|
try { fs.writeFileSync(fd, 'RACED_OPERATOR_BYTES'); }
|
|
finally { fs.closeSync(fd); }
|
|
}
|
|
return open(file, flags, mode);
|
|
};
|
|
`);
|
|
try {
|
|
assert.throws(() => execFileSync(NODE, ["--require", preload, CLI, "--out", dir], { encoding: "utf8", stdio: "pipe" }), /EEXIST/);
|
|
const files = fs.readdirSync(dir);
|
|
assert.strictEqual(files.length, 1);
|
|
assert.strictEqual(fs.readFileSync(path.join(dir, files[0]), "utf8"), "RACED_OPERATOR_BYTES");
|
|
} finally { fs.rmSync(root, { recursive: true, force: true }); }
|
|
}],
|
|
|
|
["--out fails closed when existing output changes after batch preflight", () => {
|
|
const root = tmpdir();
|
|
const dir = path.join(root, "out");
|
|
const preload = path.join(root, "change.js");
|
|
runCli(["--out", dir]);
|
|
const target = path.join(dir, fs.readdirSync(dir).sort()[0]);
|
|
fs.writeFileSync(preload, `
|
|
const fs = require('fs');
|
|
const mkdir = fs.mkdirSync;
|
|
fs.mkdirSync = function(file, options) {
|
|
if (String(file) === ${JSON.stringify(dir)}) fs.writeFileSync(${JSON.stringify(target)}, 'CHANGED_AFTER_PREFLIGHT');
|
|
return mkdir(file, options);
|
|
};
|
|
`);
|
|
try {
|
|
assert.throws(() => execFileSync(NODE, ["--require", preload, CLI, "--out", dir], { encoding: "utf8", stdio: "pipe" }), /refusing to overwrite existing agent/);
|
|
assert.strictEqual(fs.readFileSync(target, "utf8"), "CHANGED_AFTER_PREFLIGHT");
|
|
} finally { fs.rmSync(root, { recursive: true, force: true }); }
|
|
}],
|
|
|
|
["--out repeats identical output without changing bytes", () => {
|
|
const dir = tmpdir();
|
|
runCli(["--out", dir]);
|
|
const target = path.join(dir, "planner.md");
|
|
const before = fs.readFileSync(target, "utf8");
|
|
runCli(["--out", dir]);
|
|
assert.strictEqual(fs.readFileSync(target, "utf8"), before);
|
|
}],
|
|
|
|
["--out --dry-run writes nothing", () => {
|
|
const dir = tmpdir()
|
|
const out = runCli(["--from", "claude", "--to", "pi", "--out", dir, "--dry-run"])
|
|
assert.match(out, /\[dry-run\] would write/)
|
|
assert.strictEqual(fs.readdirSync(dir).length, 0, "dry-run must not create files")
|
|
}],
|
|
|
|
["unsupported conversion exits non-zero", () => {
|
|
assert.throws(() => runCli(["--from", "claude", "--to", "zed"]), /unsupported conversion/);
|
|
}],
|
|
|
|
["unknown flag exits non-zero", () => {
|
|
assert.throws(() => runCli(["--bogus"]), /unknown argument/)
|
|
}],
|
|
|
|
["--json --out keeps stdout parseable JSON", () => {
|
|
const dir = tmpdir()
|
|
const out = runCli(["--from", "claude", "--to", "pi", "--out", dir, "--json"])
|
|
const summary = JSON.parse(out)
|
|
assert.strictEqual(summary.converted, AGENT_COUNT)
|
|
assert.strictEqual(summary.schema, "ecc.agent-ir.v1")
|
|
}],
|
|
|
|
["missing value for --out exits non-zero", () => {
|
|
assert.throws(() => runCli(["--from", "claude", "--to", "pi", "--out"]), /missing value for --out/)
|
|
}],
|
|
|
|
["--check --json emits JSON", () => {
|
|
const out = runCli(["--check", "--json"])
|
|
const summary = JSON.parse(out)
|
|
assert.strictEqual(summary.ok, true)
|
|
assert.strictEqual(summary.checked, AGENT_COUNT)
|
|
}],
|
|
|
|
["--out followed by a flag is rejected (no unintended write)", () => {
|
|
const dir = tmpdir()
|
|
assert.throws(() => runCli(["--from", "claude", "--to", "pi", "--out", "--dry-run"]), /missing value for --out/)
|
|
assert.strictEqual(fs.readdirSync(dir).length, 0, "must not write anything")
|
|
}],
|
|
]
|
|
|
|
for (const [name, fn] of tests) {
|
|
if (runTest(name, fn)) {
|
|
passed += 1
|
|
} else {
|
|
failed += 1
|
|
}
|
|
}
|
|
|
|
console.log(`\nPassed: ${passed}`)
|
|
console.log(`Failed: ${failed}`)
|
|
process.exit(failed > 0 ? 1 : 0)
|
|
}
|
|
|
|
main()
|