fable-agent/src/fable5/cyber-preflight.test.ts

56 lines
1.8 KiB
TypeScript

import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { runCyberPreflight } from "./cyber-preflight.js";
const roots: string[] = [];
afterEach(() => {
for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true });
});
function tmpFile(): string {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "fable-cyber-preflight-"));
roots.push(root);
return path.join(root, "receipt.json");
}
function runner(failNames: string[] = []) {
return (_command: string, args: string[]) => {
const joined = args.join(" ");
const shouldFail = failNames.some((name) => joined.includes(name));
return {
status: shouldFail ? 22 : 0,
stdout: shouldFail ? "" : "ok token=SHOULD_REDACT",
stderr: shouldFail ? "curl: returned 404 Authorization: token SHOULD_REDACT" : "",
};
};
}
describe("cyber preflight", () => {
it("allows read-only when required live checks pass", () => {
const receipt = runCyberPreflight({ host: "127.0.0.1", runner: runner(), now: new Date("2026-06-16T00:00:00.000Z") });
expect(receipt.decision).toBe("allow_read_only");
expect(receipt.deploy_allowed).toBe(false);
expect(receipt.checks).toHaveLength(5);
});
it("fails closed when a required endpoint is missing", () => {
const receipt = runCyberPreflight({ host: "127.0.0.1", runner: runner(["factory-status"]) });
expect(receipt.decision).toBe("fail_closed");
expect(receipt.deploy_allowed).toBe(false);
});
it("writes a redacted receipt", () => {
const out = tmpFile();
runCyberPreflight({ host: "127.0.0.1", out, runner: runner(["health"]) });
const text = fs.readFileSync(out, "utf-8");
expect(text).toContain("[REDACTED]");
expect(text).not.toContain("SHOULD_REDACT");
});
});