154 lines
5.7 KiB
TypeScript
154 lines
5.7 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
|
|
// Mock node:fs
|
|
vi.mock("node:fs", () => ({
|
|
existsSync: vi.fn(() => false),
|
|
readFileSync: vi.fn(() => ""),
|
|
readdirSync: vi.fn(() => []),
|
|
statSync: vi.fn(() => ({ mtimeMs: Date.now() })),
|
|
}));
|
|
|
|
// Mock node:os
|
|
vi.mock("node:os", () => ({
|
|
homedir: vi.fn(() => "/home/testuser"),
|
|
}));
|
|
|
|
describe("Cron API routes", () => {
|
|
beforeEach(() => {
|
|
vi.resetModules();
|
|
vi.mock("node:fs", () => ({
|
|
existsSync: vi.fn(() => false),
|
|
readFileSync: vi.fn(() => ""),
|
|
readdirSync: vi.fn(() => []),
|
|
statSync: vi.fn(() => ({ mtimeMs: Date.now() })),
|
|
}));
|
|
vi.mock("node:os", () => ({
|
|
homedir: vi.fn(() => "/home/testuser"),
|
|
}));
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
// ─── GET /api/cron/jobs ─────────────────────────────────────────
|
|
|
|
describe("GET /api/cron/jobs", () => {
|
|
it("returns empty jobs when no config file", async () => {
|
|
const { GET } = await import("./jobs/route.js");
|
|
const res = await GET();
|
|
const json = await res.json();
|
|
expect(json.jobs).toEqual([]);
|
|
});
|
|
|
|
it("returns jobs from config file", async () => {
|
|
const { existsSync: mockExists, readFileSync: mockReadFile } = await import("node:fs");
|
|
vi.mocked(mockExists).mockReturnValue(true);
|
|
const cronStore = {
|
|
version: 1,
|
|
jobs: [
|
|
{ id: "j1", name: "Daily sync", schedule: "0 8 * * *", enabled: true, command: "sync" },
|
|
],
|
|
};
|
|
vi.mocked(mockReadFile).mockReturnValue(JSON.stringify(cronStore) as never);
|
|
|
|
const { GET } = await import("./jobs/route.js");
|
|
const res = await GET();
|
|
const json = await res.json();
|
|
expect(json.jobs).toHaveLength(1);
|
|
expect(json.jobs[0].name).toBe("Daily sync");
|
|
});
|
|
|
|
it("handles corrupt jobs file gracefully", async () => {
|
|
const { existsSync: mockExists, readFileSync: mockReadFile } = await import("node:fs");
|
|
vi.mocked(mockExists).mockReturnValue(true);
|
|
vi.mocked(mockReadFile).mockReturnValue("not json" as never);
|
|
|
|
const { GET } = await import("./jobs/route.js");
|
|
const res = await GET();
|
|
const json = await res.json();
|
|
expect(json.jobs).toEqual([]);
|
|
});
|
|
});
|
|
|
|
// ─── GET /api/cron/jobs/[jobId]/runs ────────────────────────────
|
|
|
|
describe("GET /api/cron/jobs/[jobId]/runs", () => {
|
|
it("returns empty entries when no runs file", async () => {
|
|
const { GET } = await import("./jobs/[jobId]/runs/route.js");
|
|
const res = await GET(
|
|
new Request("http://localhost/api/cron/jobs/j1/runs"),
|
|
{ params: Promise.resolve({ jobId: "j1" }) },
|
|
);
|
|
const json = await res.json();
|
|
expect(json.entries).toEqual([]);
|
|
});
|
|
|
|
it("returns run entries from jsonl file", async () => {
|
|
const { existsSync: mockExists, readFileSync: mockReadFile } = await import("node:fs");
|
|
vi.mocked(mockExists).mockReturnValue(true);
|
|
const lines = [
|
|
JSON.stringify({ ts: 1000, jobId: "j1", action: "finished", status: "completed", summary: "Done" }),
|
|
JSON.stringify({ ts: 2000, jobId: "j1", action: "finished", status: "completed", summary: "In progress" }),
|
|
].join("\n");
|
|
vi.mocked(mockReadFile).mockReturnValue(lines as never);
|
|
|
|
const { GET } = await import("./jobs/[jobId]/runs/route.js");
|
|
const res = await GET(
|
|
new Request("http://localhost/api/cron/jobs/j1/runs"),
|
|
{ params: Promise.resolve({ jobId: "j1" }) },
|
|
);
|
|
const json = await res.json();
|
|
expect(json.entries.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("respects limit query param", async () => {
|
|
const { existsSync: mockExists, readFileSync: mockReadFile } = await import("node:fs");
|
|
vi.mocked(mockExists).mockReturnValue(true);
|
|
const lines = Array.from({ length: 50 }, (_, i) =>
|
|
JSON.stringify({ ts: i, status: "completed" }),
|
|
).join("\n");
|
|
vi.mocked(mockReadFile).mockReturnValue(lines as never);
|
|
|
|
const { GET } = await import("./jobs/[jobId]/runs/route.js");
|
|
const res = await GET(
|
|
new Request("http://localhost/api/cron/jobs/j1/runs?limit=5"),
|
|
{ params: Promise.resolve({ jobId: "j1" }) },
|
|
);
|
|
const json = await res.json();
|
|
expect(json.entries.length).toBeLessThanOrEqual(5);
|
|
});
|
|
});
|
|
|
|
// ─── GET /api/cron/runs/[sessionId] ─────────────────────────────
|
|
|
|
describe("GET /api/cron/runs/[sessionId]", () => {
|
|
it("returns 404 when session not found", async () => {
|
|
const { GET } = await import("./runs/[sessionId]/route.js");
|
|
const res = await GET(
|
|
new Request("http://localhost"),
|
|
{ params: Promise.resolve({ sessionId: "nonexistent" }) },
|
|
);
|
|
expect(res.status).toBe(404);
|
|
});
|
|
});
|
|
|
|
// ─── GET /api/cron/runs/search-transcript ───────────────────────
|
|
|
|
describe("GET /api/cron/runs/search-transcript", () => {
|
|
it("returns 400 when missing required params", async () => {
|
|
const { GET } = await import("./runs/search-transcript/route.js");
|
|
const req = new Request("http://localhost/api/cron/runs/search-transcript");
|
|
const res = await GET(req);
|
|
expect(res.status).toBe(400);
|
|
});
|
|
|
|
it("returns 404 when no transcript found", async () => {
|
|
const { GET } = await import("./runs/search-transcript/route.js");
|
|
const req = new Request("http://localhost/api/cron/runs/search-transcript?jobId=j1&runAtMs=1000");
|
|
const res = await GET(req);
|
|
expect(res.status).toBe(404);
|
|
});
|
|
});
|
|
});
|