91 lines
3.2 KiB
TypeScript
91 lines
3.2 KiB
TypeScript
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();
|
|
});
|
|
});
|