agent-r/test/store.test.ts
2026-04-14 20:44:06 +09:00

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();
});
});