53 lines
2.6 KiB
JavaScript
53 lines
2.6 KiB
JavaScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { busExit, humanTransport, refuseInsideAgent } from "../src/transport.mjs";
|
|
import { fakeCli, humanEnv, tmp } from "./helpers.mjs";
|
|
|
|
// The fake records argv and stdin, then answers per verb. The real
|
|
// human-cli.mjs is never run here: its proof needs a human shell.
|
|
const BODY = `
|
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
const input = readFileSync(0, "utf8");
|
|
writeFileSync(process.env.FAKE_LOG, JSON.stringify({ argv: process.argv.slice(2), input }));
|
|
const { verb } = JSON.parse(input);
|
|
if (verb === "inbox") process.stdout.write(JSON.stringify([{ id: "d1" }]) + "\\n");
|
|
else if (verb === "garbage") process.stdout.write("not json");
|
|
else if (verb === "hang") setTimeout(() => {}, 60000);
|
|
else { process.stderr.write("some noise\\n" + verb + "\\n"); process.exit(2); }
|
|
`;
|
|
|
|
function setup(t) {
|
|
const root = tmp(t);
|
|
const log = join(root, "log.json");
|
|
const cli = fakeCli(root, BODY);
|
|
const call = humanTransport({ socket: "/run/fake.sock", business: "acme", env: humanEnv({ FAKE_LOG: log }), cli, timeoutMs: 2000 });
|
|
return { call, seen: () => JSON.parse(readFileSync(log, "utf8")) };
|
|
}
|
|
|
|
test("the transport writes {business, verb, args} to the child and reads its JSON", async (t) => {
|
|
const s = setup(t);
|
|
assert.deepEqual(await s.call("inbox"), [{ id: "d1" }]);
|
|
assert.deepEqual(s.seen().argv, ["/run/fake.sock"]);
|
|
assert.deepEqual(JSON.parse(s.seen().input), { business: "acme", verb: "inbox", args: {} });
|
|
});
|
|
|
|
test("a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems", async (t) => {
|
|
const s = setup(t);
|
|
for (const [code, exit] of [["human-required", 3], ["read-only", 3], ["decision-closed", 2], ["outcome-unknown", 1]]) {
|
|
await assert.rejects(s.call(code), (e) => e.code === code && e.exitCode === exit, code);
|
|
}
|
|
await assert.rejects(s.call("garbage"), (e) => e.code === "invalid-response" && e.exitCode === 1);
|
|
await assert.rejects(s.call("hang"), (e) => e.exitCode === 1 && /outcome-unknown/.test(e.message));
|
|
});
|
|
|
|
test("busExit and refuseInsideAgent", () => {
|
|
assert.equal(busExit("unauthenticated"), 3);
|
|
assert.equal(busExit("invalid-request"), 2);
|
|
// Row 239 (#1745): a write whose outcome is unknown after its revoke exits as outcome-unknown does.
|
|
assert.equal(busExit("outcome-unknown-revoked"), 1);
|
|
assert.doesNotThrow(() => refuseInsideAgent(humanEnv()));
|
|
assert.throws(() => refuseInsideAgent({ PI_AGENT_DIR: "/x" }), (e) => e.exitCode === 3 && /PI_AGENT_DIR/.test(e.message));
|
|
});
|