






















@@ -0,0 +1,125 @@
1+import fs from "node:fs/promises";
2+import os from "node:os";
3+import path from "node:path";
4+import { describe, expect, it } from "vitest";
5+import { readCronRunLogEntriesPage } from "./run-log.js";
6+7+describe("cron run log errorReason", () => {
8+it("backfills errorReason from timeout error text for older entries", async () => {
9+const dir = await fs.mkdtemp(path.join(os.tmpdir(), "cron-run-log-"));
10+const file = path.join(dir, "job.jsonl");
11+await fs.writeFile(
12+file,
13+`${JSON.stringify({
14+ ts: 1,
15+ jobId: "job-1",
16+ action: "finished",
17+ status: "error",
18+ error: "cron: job execution timed out",
19+ })}\n`,
20+"utf8",
21+);
22+23+const page = await readCronRunLogEntriesPage(file, { limit: 10 });
24+expect(page.entries[0]?.errorReason).toBe("timeout");
25+});
26+27+it("validates persisted errorReason against the full failover reason set", async () => {
28+const dir = await fs.mkdtemp(path.join(os.tmpdir(), "cron-run-log-"));
29+const file = path.join(dir, "job.jsonl");
30+const reasons = [
31+"auth",
32+"auth_permanent",
33+"format",
34+"rate_limit",
35+"overloaded",
36+"billing",
37+"server_error",
38+"timeout",
39+"model_not_found",
40+"session_expired",
41+"empty_response",
42+"no_error_details",
43+"unclassified",
44+"unknown",
45+];
46+await fs.writeFile(
47+file,
48+reasons
49+.map((errorReason, index) =>
50+JSON.stringify({
51+ts: index + 1,
52+jobId: "job-1",
53+action: "finished",
54+status: "error",
55+ errorReason,
56+}),
57+)
58+.join("\n") + "\n",
59+"utf8",
60+);
61+62+const page = await readCronRunLogEntriesPage(file, { limit: 50, sortDir: "asc" });
63+expect(page.entries.map((entry) => entry.errorReason)).toEqual(reasons);
64+});
65+66+it("derives an invalid persisted reason from raw error text before exposing entries", async () => {
67+const dir = await fs.mkdtemp(path.join(os.tmpdir(), "cron-run-log-"));
68+const file = path.join(dir, "job.jsonl");
69+await fs.writeFile(
70+file,
71+`${JSON.stringify({
72+ ts: 1,
73+ jobId: "job-1",
74+ action: "finished",
75+ status: "error",
76+ error: "upstream unavailable: 503 overloaded",
77+ errorReason: "not-a-real-reason",
78+ })}\n`,
79+"utf8",
80+);
81+82+const page = await readCronRunLogEntriesPage(file, { limit: 10 });
83+expect(page.entries[0]?.errorReason).toBe("overloaded");
84+});
85+86+it("uses provider context when deriving persisted run-log reasons", async () => {
87+const dir = await fs.mkdtemp(path.join(os.tmpdir(), "cron-run-log-"));
88+const file = path.join(dir, "job.jsonl");
89+await fs.writeFile(
90+file,
91+`${JSON.stringify({
92+ ts: 1,
93+ jobId: "job-1",
94+ action: "finished",
95+ status: "error",
96+ error: "403 Key limit exceeded (monthly limit)",
97+ provider: "openrouter",
98+ })}\n`,
99+"utf8",
100+);
101+102+const page = await readCronRunLogEntriesPage(file, { limit: 10 });
103+expect(page.entries[0]?.errorReason).toBe("billing");
104+});
105+106+it("includes derived errorReason values in run-log search", async () => {
107+const dir = await fs.mkdtemp(path.join(os.tmpdir(), "cron-run-log-"));
108+const file = path.join(dir, "job.jsonl");
109+await fs.writeFile(
110+file,
111+`${JSON.stringify({
112+ ts: 1,
113+ jobId: "job-1",
114+ action: "finished",
115+ status: "error",
116+ error: "cron: job execution timed out",
117+ })}\n`,
118+"utf8",
119+);
120+121+const page = await readCronRunLogEntriesPage(file, { limit: 10, query: "timeout" });
122+expect(page.entries).toHaveLength(1);
123+expect(page.entries[0]?.errorReason).toBe("timeout");
124+});
125+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。