test: add harness tests for interrupt-gate, flat-ledger, repo-mapper
interrupt-gate: 5 tests (stdin defaults, requireApproval) flat-ledger: 6 tests (write/read, lastStep, summary, clear, sessionName) repo-mapper: 4 tests (real dir, empty path, minifiedTree, treeString) Fix FlatLedger to accept setLedgerDir() for test isolation. Total: 68 files, 291 tests passing.
This commit is contained in:
parent
d4e2d3c36b
commit
0236f351ec
|
|
@ -0,0 +1,77 @@
|
|||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as os from "node:os";
|
||||
import { FlatLedger, setLedgerDir } from "./flat-ledger.js";
|
||||
|
||||
describe("flat ledger", () => {
|
||||
const testDir = path.join(os.tmpdir(), "fable-ledger-test");
|
||||
|
||||
beforeEach(() => {
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
setLedgerDir(testDir);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("writes and reads entries", () => {
|
||||
const ledger = new FlatLedger("test-session");
|
||||
const entry = ledger.append({
|
||||
timestamp: new Date().toISOString(),
|
||||
agent: "test-agent",
|
||||
action: "build",
|
||||
target: "src/main.ts",
|
||||
result: "ok",
|
||||
durationMs: 100,
|
||||
});
|
||||
expect(entry.step).toBe(1);
|
||||
expect(entry.result).toBe("ok");
|
||||
|
||||
const state = ledger.readState();
|
||||
expect(state.length).toBe(1);
|
||||
expect(state[0].step).toBe(1);
|
||||
});
|
||||
|
||||
it("returns last step", () => {
|
||||
const ledger = new FlatLedger("test-session");
|
||||
expect(ledger.lastStep()).toBeNull();
|
||||
|
||||
ledger.append({ timestamp: new Date().toISOString(), agent: "a", action: "build", target: "x", result: "ok", durationMs: 10 });
|
||||
ledger.append({ timestamp: new Date().toISOString(), agent: "a", action: "test", target: "x", result: "fail", durationMs: 20 });
|
||||
|
||||
const last = ledger.lastStep();
|
||||
expect(last?.step).toBe(2);
|
||||
expect(last?.result).toBe("fail");
|
||||
expect(ledger.lastActionResult()).toBe("fail");
|
||||
});
|
||||
|
||||
it("produces summary", () => {
|
||||
const ledger = new FlatLedger("test-session");
|
||||
ledger.append({ timestamp: "t1", agent: "a", action: "b", target: "t", result: "ok", durationMs: 1 });
|
||||
ledger.append({ timestamp: "t2", agent: "a", action: "b", target: "t", result: "ok", durationMs: 1 });
|
||||
ledger.append({ timestamp: "t3", agent: "a", action: "b", target: "t", result: "fail", durationMs: 1 });
|
||||
ledger.append({ timestamp: "t4", agent: "a", action: "b", target: "t", result: "skip", durationMs: 1 });
|
||||
|
||||
const summary = ledger.summary();
|
||||
expect(summary).toContain("4 steps");
|
||||
expect(summary).toContain("2 ok");
|
||||
expect(summary).toContain("1 fail");
|
||||
expect(summary).toContain("1 skip");
|
||||
});
|
||||
|
||||
it("clear resets state", () => {
|
||||
const ledger = new FlatLedger("test-session");
|
||||
ledger.append({ timestamp: "t1", agent: "a", action: "b", target: "t", result: "ok", durationMs: 1 });
|
||||
expect(ledger.readState().length).toBe(1);
|
||||
ledger.clear();
|
||||
expect(ledger.readState().length).toBe(0);
|
||||
});
|
||||
|
||||
it("sessionNameFromTask generates safe names", () => {
|
||||
const name = FlatLedger.sessionNameFromTask("Fix: broken auth on /api/v2/users");
|
||||
expect(name).toBe("fix-broken-auth-on-api-v2-users");
|
||||
expect(name.length).toBeLessThanOrEqual(40);
|
||||
});
|
||||
});
|
||||
|
|
@ -2,7 +2,11 @@ import * as fs from "node:fs";
|
|||
import * as path from "node:path";
|
||||
import * as os from "node:os";
|
||||
|
||||
const LEDGER_DIR = path.join(os.homedir(), ".fable-agent", "ledger");
|
||||
let LEDGER_DIR = path.join(os.homedir(), ".fable-agent", "ledger");
|
||||
|
||||
export function setLedgerDir(dir: string) {
|
||||
LEDGER_DIR = dir;
|
||||
}
|
||||
|
||||
export interface LedgerEntry {
|
||||
step: number;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { humanInterruptGate, requireApproval } from "./interrupt-gate.js";
|
||||
|
||||
describe("interrupt gate", () => {
|
||||
it("defaults to allow when stdin is not a TTY", async () => {
|
||||
const decision = await humanInterruptGate({ defaultValue: "allow" });
|
||||
expect(decision).toBe("allow");
|
||||
});
|
||||
|
||||
it("defaults to deny when configured", async () => {
|
||||
const decision = await humanInterruptGate({ defaultValue: "deny" });
|
||||
expect(decision).toBe("deny");
|
||||
});
|
||||
|
||||
it("returns skip when configured", async () => {
|
||||
const decision = await humanInterruptGate({ defaultValue: "skip" });
|
||||
expect(decision).toBe("skip");
|
||||
});
|
||||
|
||||
it("requireApproval returns true by default on non-TTY", async () => {
|
||||
const ok = await requireApproval("test-action");
|
||||
expect(ok).toBe(true);
|
||||
});
|
||||
|
||||
it("requireApproval returns false when default is deny", async () => {
|
||||
const ok = await requireApproval("test-action", { defaultValue: "deny" });
|
||||
expect(ok).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import * as path from "node:path";
|
||||
import * as fs from "node:fs";
|
||||
import { buildRepoMap, minifiedTree, repoTreeString } from "./repo-mapper.js";
|
||||
|
||||
describe("repo mapper", () => {
|
||||
const fixtures = path.resolve("test", "fixtures", "fake-repo");
|
||||
|
||||
it("builds a map from a real directory", () => {
|
||||
const map = buildRepoMap(process.cwd(), 2, ["node_modules", ".git", "dist", ".pi"]);
|
||||
expect(map.fileCount).toBeGreaterThan(0);
|
||||
expect(map.dirCount).toBeGreaterThan(0);
|
||||
expect(map.totalBytes).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("returns empty for nonexistent path", () => {
|
||||
const map = buildRepoMap("/nonexistent/path");
|
||||
expect(map.fileCount).toBe(0);
|
||||
expect(map.tree.length).toBe(0);
|
||||
});
|
||||
|
||||
it("minifiedTree truncates long lists", () => {
|
||||
const map = buildRepoMap(process.cwd(), 1, ["node_modules", ".git", "dist", ".pi"]);
|
||||
const result = minifiedTree(map);
|
||||
expect(result).toContain("f");
|
||||
expect(result).toContain("d");
|
||||
});
|
||||
|
||||
it("repoTreeString builds readable output", () => {
|
||||
const map = buildRepoMap(process.cwd(), 1, ["node_modules", ".git", "dist", ".pi"]);
|
||||
const output = repoTreeString(map);
|
||||
expect(output).toContain("files");
|
||||
expect(output).toContain("dirs");
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue