import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { DatabaseSync } from "node:sqlite"; import path from "node:path"; import { describe, expect, test } from "vitest"; import { RunStore } from "../src/store.js"; describe("RunStore", () => { test("creates runs, tasks, and snapshots", () => { const tempDir = mkdtempSync(path.join(tmpdir(), "agent-r-store-")); const store = new RunStore(path.join(tempDir, "state.sqlite")); const run = store.createRun("Build something", tempDir); store.replacePendingTasks(run.id, [ { title: "Task 1", objective: "Do work", acceptanceCriteria: ["It works"], verificationSteps: ["Run tests"], allowedPaths: ["src"], runtimeClass: "short", }, { title: "Task 2", objective: "Do more work", acceptanceCriteria: ["It still works"], verificationSteps: ["Run tests again"], allowedPaths: ["tests"], runtimeClass: "long", }, ]); const claimed = store.claimNextPendingTask(run.id); expect(claimed?.status).toBe("in_progress"); store.completeTask(claimed!.id, "Finished"); store.addEvent(run.id, "test", "custom", "Recorded a test event.", { ok: true }); const snapshot = store.buildSnapshot(run.id); expect(snapshot.pendingTasks).toHaveLength(1); expect(snapshot.completedTasks).toHaveLength(1); expect(snapshot.recentEvents.at(-1)?.kind).toBe("custom"); expect(snapshot.pendingTasks[0]?.runtimeClass).toBe("long"); expect(snapshot.run.waitingInputs).toEqual([]); }); test("keeps only recent checkpoints per run", () => { const tempDir = mkdtempSync(path.join(tmpdir(), "agent-r-store-")); const dbPath = path.join(tempDir, "state.sqlite"); const store = new RunStore(dbPath); const run = store.createRun("Build something", tempDir); for (let index = 0; index < 30; index += 1) { store.addCheckpoint(run.id, "planning", { index }); } const db = new DatabaseSync(dbPath); const rows = db .prepare("SELECT payload_json FROM checkpoints WHERE run_id = ? ORDER BY rowid DESC") .all(run.id) as Array<{ payload_json: string }>; expect(rows).toHaveLength(24); expect(JSON.parse(rows[0]!.payload_json)).toEqual({ index: 29 }); expect(JSON.parse(rows.at(-1)!.payload_json)).toEqual({ index: 6 }); }); test("continues when activity updates hit SQLITE_FULL", () => { const tempDir = mkdtempSync(path.join(tmpdir(), "agent-r-store-")); const store = new RunStore(path.join(tempDir, "state.sqlite")); const run = store.createRun("Build something", tempDir); const subject = store as unknown as { writeRun: (runId: string, next: unknown) => void; }; const originalWriteRun = subject.writeRun.bind(subject); subject.writeRun = () => { throw new Error("database or disk is full"); }; expect(() => store.updateRunActivity(run.id, { lastEventAt: new Date().toISOString(), activeRole: "strategy", activeItemKind: "command_execution", activeCommandSummary: "npm test", }), ).not.toThrow(); subject.writeRun = originalWriteRun; expect(store.getRun(run.id).activeRole).toBeNull(); }); });