/** * KHAELOR * File: tests/tools/helpers.ts * Description: Shared test fixtures — real LocalWorkspace-backed ToolContext in a temp directory. * * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai */ import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import * as path from "node:path"; import type { ToolContext, ToolEmittedEvent } from "../../src/tools/index.js"; import { InMemoryFileTimeRegistry, LocalProcessManager, LocalWorkspace, } from "../../src/workspace/index.js"; export interface TestHarness { dir: string; workspace: LocalWorkspace; fileTimes: InMemoryFileTimeRegistry; processes: LocalProcessManager; events: ToolEmittedEvent[]; spills: string[]; ctx: ToolContext; abort: AbortController; } let spillCounter = 0; /** Build a real ToolContext rooted in a fresh temp directory. */ export function makeHarness(): TestHarness { const dir = mkdtempSync(path.join(tmpdir(), "khaelor-tools-")); const workspace = new LocalWorkspace(dir); const fileTimes = new InMemoryFileTimeRegistry(); const logDir = path.join(dir, ".khaelor-test-logs"); const processes = new LocalProcessManager({ logDir, shell: "/bin/sh" }); const events: ToolEmittedEvent[] = []; const spills: string[] = []; const abort = new AbortController(); const spillDir = path.join(dir, ".khaelor-test-spill"); mkdirSync(spillDir, { recursive: true }); const ctx: ToolContext = { sessionId: "s_test", callId: "toolu_test", workspace, signal: abort.signal, fileTimes, processes, emit: (event) => { events.push(event); }, progress: () => undefined, spill: (label, content) => { spillCounter += 1; const file = path.join(spillDir, `${label}-${spillCounter}.txt`); writeFileSync(file, content, "utf8"); spills.push(file); return Promise.resolve(file); }, }; return { dir, workspace, fileTimes, processes, events, spills, ctx, abort }; } /** Write a fixture file under the harness dir; returns the absolute path. */ export function fixture(harness: TestHarness, rel: string, content: string): string { const abs = path.join(harness.dir, rel); mkdirSync(path.dirname(abs), { recursive: true }); writeFileSync(abs, content, "utf8"); return abs; } /** Read a file through the harness and stamp it (simulates a prior read tool call). */ export async function markRead(harness: TestHarness, abs: string): Promise { const content = await harness.workspace.readFile(abs); harness.fileTimes.stamp(abs, content); }