56 lines
1.8 KiB
TypeScript
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");
|
|
});
|
|
});
|