import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; import { appendCronRunLog, readCronRunLogEntries, resolveCronRunLogPath, } from "./run-log.js"; describe("cron run log", () => { it("resolves store path to per-job runs/.jsonl", () => { const storePath = path.join(os.tmpdir(), "cron", "jobs.json"); const p = resolveCronRunLogPath({ storePath, jobId: "job-1" }); expect( p.endsWith(path.join(os.tmpdir(), "cron", "runs", "job-1.jsonl")), ).toBe(true); }); it("appends JSONL and prunes by line count", async () => { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-cron-log-")); const logPath = path.join(dir, "runs", "job-1.jsonl"); for (let i = 0; i < 10; i++) { await appendCronRunLog( logPath, { ts: 1000 + i, jobId: "job-1", action: "finished", status: "ok", durationMs: i, }, { maxBytes: 1, keepLines: 3 }, ); } const raw = await fs.readFile(logPath, "utf-8"); const lines = raw .split("\n") .map((l) => l.trim()) .filter(Boolean); expect(lines.length).toBe(3); const last = JSON.parse(lines[2] ?? "{}") as { ts?: number }; expect(last.ts).toBe(1009); await fs.rm(dir, { recursive: true, force: true }); }); it("reads newest entries and filters by jobId", async () => { const dir = await fs.mkdtemp( path.join(os.tmpdir(), "clawdbot-cron-log-read-"), ); const logPathA = path.join(dir, "runs", "a.jsonl"); const logPathB = path.join(dir, "runs", "b.jsonl"); await appendCronRunLog(logPathA, { ts: 1, jobId: "a", action: "finished", status: "ok", }); await appendCronRunLog(logPathB, { ts: 2, jobId: "b", action: "finished", status: "error", error: "nope", summary: "oops", }); await appendCronRunLog(logPathA, { ts: 3, jobId: "a", action: "finished", status: "skipped", }); const allA = await readCronRunLogEntries(logPathA, { limit: 10 }); expect(allA.map((e) => e.jobId)).toEqual(["a", "a"]); const onlyA = await readCronRunLogEntries(logPathA, { limit: 10, jobId: "a", }); expect(onlyA.map((e) => e.ts)).toEqual([1, 3]); const lastOne = await readCronRunLogEntries(logPathA, { limit: 1 }); expect(lastOne.map((e) => e.ts)).toEqual([3]); const onlyB = await readCronRunLogEntries(logPathB, { limit: 10, jobId: "b", }); expect(onlyB[0]?.summary).toBe("oops"); const wrongFilter = await readCronRunLogEntries(logPathA, { limit: 10, jobId: "b", }); expect(wrongFilter).toEqual([]); await fs.rm(dir, { recursive: true, force: true }); }); });