Files
stack/packages/cli/tests/transport.test.mjs

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));
});