import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { cp, mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { ToolRegistry, type ToolContext } from "../../src/agent/tools.js"; import { cphCheckTool, cphBuildTool } from "../../src/agent/cph.js"; const EXAMPLES_DIR = join(process.cwd(), "..", "examples", "TH-141"); describe("cph subprocess tools (integration, real cph binary)", () => { let ws: string; let tools: ToolRegistry; beforeEach(async () => { // Use a writable copy of the real TH-141 example as the workspace. ws = await mkdtemp(join(tmpdir(), "hub-cph-example-")); await cp(EXAMPLES_DIR, ws, { recursive: true }); tools = new ToolRegistry(); tools.register("cph_check", cphCheckTool); tools.register("cph_build", cphBuildTool); }); afterEach(async () => { await rm(ws, { recursive: true, force: true }); }); it("cph_check runs on the TH-141 example and returns diagnostics", async () => { const ctx = { runId: "r", projectId: "p", boundChatId: "c", workspaceDir: ws }; const out = await executeTool(tools, "cph_check", {}, ctx); const result = JSON.parse(out) as { exitCode: number; stdout: string; stderr: string }; // cph check exits 0 on a legal lesson (no error diagnostics, ADR-0010). // If the example has warnings, exit is still 0. expect(result.exitCode).toBe(0); }, 30000); it("cph_build renders the student target PDF", async () => { const ctx = { runId: "r", projectId: "p", boundChatId: "c", workspaceDir: ws }; const out = await executeTool(tools, "cph_build", { target: "student", output: "build/test-student.pdf" }, ctx); const result = JSON.parse(out) as { exitCode: number; stdout: string; stderr: string; output: string }; expect(result.exitCode).toBe(0); }, 30000); it("cph_check on a temp dir with no engineering file returns non-zero", async () => { const empty = await mkdtemp(join(tmpdir(), "hub-cph-empty-")); try { const ctx = { runId: "r", projectId: "p", boundChatId: "c", workspaceDir: empty }; const out = await executeTool(tools, "cph_check", {}, ctx); const result = JSON.parse(out) as { exitCode: number }; expect(result.exitCode).not.toBe(0); } finally { await rm(empty, { recursive: true, force: true }); } }, 15000); }); async function executeTool(tools: ToolRegistry, name: string, args: unknown, ctx: ToolContext): Promise { const def = tools.build(ctx)[name]; if (def?.execute === undefined) { throw new Error(`missing executable tool: ${name}`); } return def.execute(args, { toolCallId: "call", messages: [], context: undefined }) as Promise; }