





















@@ -13,8 +13,12 @@ const { logger, makeStorePath } = setupCronServiceSuite({
1313const STORE_TEST_NOW = Date.parse("2026-03-23T12:00:00.000Z");
14141515async function writeSingleJobStore(storePath: string, job: Record<string, unknown>) {
16+await writeJobStore(storePath, [job]);
17+}
18+19+async function writeJobStore(storePath: string, jobs: Array<Record<string, unknown>>) {
1620await fs.mkdir(path.dirname(storePath), { recursive: true });
17-await fs.writeFile(storePath, JSON.stringify({ version: 1, jobs: [job] }, null, 2), "utf8");
21+await fs.writeFile(storePath, JSON.stringify({ version: 1, jobs }, null, 2), "utf8");
1822}
19232024function createStoreTestState(storePath: string) {
@@ -118,6 +122,70 @@ describe("cron service store load: missing sessionTarget", () => {
118122expect(() => assertSupportedJobSpec(bogus)).toThrow(/missing sessionTarget/);
119123});
120124125+it("skips malformed persisted schedule and payload shapes without rewriting the store", async () => {
126+const { storePath } = await makeStorePath();
127+128+await writeJobStore(storePath, [
129+{
130+id: "valid-job",
131+name: "valid job",
132+enabled: true,
133+createdAtMs: STORE_TEST_NOW - 60_000,
134+updatedAtMs: STORE_TEST_NOW - 60_000,
135+schedule: { kind: "every", everyMs: 60_000 },
136+sessionTarget: "main",
137+wakeMode: "now",
138+payload: { kind: "systemEvent", text: "tick" },
139+state: {},
140+},
141+{
142+id: "bad-schedule",
143+name: "bad schedule",
144+enabled: true,
145+createdAtMs: STORE_TEST_NOW - 60_000,
146+updatedAtMs: STORE_TEST_NOW - 60_000,
147+schedule: ["every", 60_000],
148+sessionTarget: "main",
149+wakeMode: "now",
150+payload: { kind: "systemEvent", text: "tick" },
151+state: {},
152+},
153+{
154+id: "bad-payload",
155+name: "bad payload",
156+enabled: true,
157+createdAtMs: STORE_TEST_NOW - 60_000,
158+updatedAtMs: STORE_TEST_NOW - 60_000,
159+schedule: { kind: "every", everyMs: 60_000 },
160+sessionTarget: "main",
161+wakeMode: "now",
162+payload: ["systemEvent", "tick"],
163+state: {},
164+},
165+]);
166+const beforeRaw = await fs.readFile(storePath, "utf-8");
167+const warnSpy = vi.spyOn(logger, "warn");
168+169+const state = createStoreTestState(storePath);
170+await ensureLoaded(state);
171+await ensureLoaded(state, { forceReload: true });
172+173+expect(state.store?.jobs.map((job) => job.id)).toEqual(["valid-job"]);
174+expect(findJobOrThrow(state, "valid-job").state.nextRunAtMs).toBe(STORE_TEST_NOW);
175+await expect(fs.readFile(storePath, "utf-8")).resolves.toBe(beforeRaw);
176+177+const invalidShapeWarns = warnSpy.mock.calls.filter((call) => {
178+const msg = typeof call[1] === "string" ? call[1] : "";
179+return msg.includes("skipped invalid persisted job");
180+});
181+expect(invalidShapeWarns).toHaveLength(2);
182+expect(invalidShapeWarns.map((call) => (call[0] as { reason?: string }).reason)).toEqual([
183+"missing-schedule",
184+"missing-payload",
185+]);
186+warnSpy.mockRestore();
187+});
188+121189it("warns once per jobId across repeated forceReload cycles", async () => {
122190const { storePath } = await makeStorePath();
123191此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。