import { test, expect, vi } from "vitest"; import { EventEmitter } from "node:events"; import { ThtRunner } from "../src/tht/tht-runner.js"; // Spy on child_process.spawn so we can capture the resolved argv (incl. -c config) // that ThtRunner.run() builds, without launching a real process. vi.mock("node:child_process", () => ({ spawn: vi.fn(() => { const ch: any = new EventEmitter(); ch.stdout = new EventEmitter(); ch.stderr = new EventEmitter(); queueMicrotask(() => { ch.stdout.emit("data", Buffer.from('{"id":"x"}')); ch.emit("close", 0); }); return ch; }), })); import { spawn } from "node:child_process"; test("sessionNew parses id from JSON", async () => { const r = new ThtRunner({ thtBin: "tht", harnessDir: "/h", configPath: "config/tht.yaml" }); r.run = async () => ({ code: 0, stdout: '{"id":"2026-06-27-100000-x"}', stderr: "" }); expect(await r.sessionNew({ question: "q" })).toEqual({ id: "2026-06-27-100000-x" }); }); test("run with exit != 0 propagates error with stderr", async () => { const r = new ThtRunner({ thtBin: "tht", harnessDir: "/h", configPath: "config/tht.yaml" }); r.run = async () => ({ code: 1, stdout: "", stderr: "ERRORE: boom" }); await expect(r.sessionList()).rejects.toThrow(/boom/); }); test("sessionNew with missing workspace file falls back to default configPath argv", async () => { // harnessDir "/nope" has no workspaces/foo.yaml -> configArg falls back to default. (spawn as any).mockClear(); const r = new ThtRunner({ thtBin: "tht", harnessDir: "/nope", configPath: "config/tht.yaml" }); await r.sessionNew({ question: "q", workspace: "foo" }); const [bin, argv] = (spawn as any).mock.calls[0]; expect(bin).toBe("tht"); expect(argv).toEqual(["-c", "config/tht.yaml", "session", "new", "q", "--json"]); });