


























@@ -15,13 +15,17 @@ const { logger, makeStorePath } = setupCronServiceSuite({
1515const STORE_TEST_NOW = Date.parse("2026-03-23T12:00:00.000Z");
16161717async function writeSingleJobStore(storePath: string, job: Record<string, unknown>) {
18+await writeJobStore(storePath, [job]);
19+}
20+21+async function writeJobStore(storePath: string, jobs: Array<Record<string, unknown>>) {
1822await fs.mkdir(path.dirname(storePath), { recursive: true });
1923await fs.writeFile(
2024storePath,
2125JSON.stringify(
2226{
2327version: 1,
24-jobs: [job],
28+ jobs,
2529},
2630null,
27312,
@@ -130,6 +134,95 @@ describe("cron service store seam coverage", () => {
130134expect((state.storeFileMtimeMs ?? 0) >= (firstMtime ?? 0)).toBe(true);
131135});
132136137+it("preserves unsupported payload-kind rows across full persistence without loading them", async () => {
138+const { storePath } = await makeStorePath();
139+140+await writeJobStore(storePath, [
141+{
142+id: "valid-job",
143+name: "valid job",
144+enabled: true,
145+createdAtMs: STORE_TEST_NOW - 60_000,
146+updatedAtMs: STORE_TEST_NOW - 60_000,
147+schedule: { kind: "every", everyMs: 60_000 },
148+sessionTarget: "main",
149+wakeMode: "now",
150+payload: { kind: "systemEvent", text: "tick" },
151+state: {},
152+},
153+{
154+id: "legacy-command",
155+name: "legacy command",
156+enabled: true,
157+createdAtMs: STORE_TEST_NOW - 60_000,
158+updatedAtMs: STORE_TEST_NOW - 60_000,
159+schedule: { kind: "cron", expr: "0 8 * * *", tz: "UTC" },
160+sessionTarget: "main",
161+wakeMode: "now",
162+payload: { kind: "command", command: "echo daily" },
163+state: { lastRunAtMs: STORE_TEST_NOW - 3_600_000 },
164+},
165+{
166+id: "legacy-agentmessage",
167+name: "legacy agentmessage",
168+enabled: true,
169+createdAtMs: STORE_TEST_NOW - 60_000,
170+schedule: { kind: "cron", expr: "0 9 * * *", tz: "UTC" },
171+sessionTarget: "isolated",
172+wakeMode: "now",
173+payload: { kind: "agentmessage", message: "summarize" },
174+metadata: { preserve: { nested: true } },
175+},
176+]);
177+178+const state = createStoreTestState(storePath);
179+await ensureLoaded(state, { skipRecompute: true });
180+181+expect(state.store?.jobs.map((job) => job.id)).toEqual(["valid-job"]);
182+expect(() => findJobOrThrow(state, "legacy-command")).toThrow(/unknown cron job id/);
183+expect(() => findJobOrThrow(state, "legacy-agentmessage")).toThrow(/unknown cron job id/);
184+185+const valid = findJobOrThrow(state, "valid-job");
186+valid.name = "valid job renamed";
187+await persist(state);
188+189+const config = JSON.parse(await fs.readFile(storePath, "utf8")) as {
190+jobs: Array<Record<string, unknown>>;
191+};
192+expect(config.jobs.map((job) => job.id)).toEqual([
193+"valid-job",
194+"legacy-command",
195+"legacy-agentmessage",
196+]);
197+expect(config.jobs[0]?.name).toBe("valid job renamed");
198+expect(config.jobs[1]).toMatchObject({
199+id: "legacy-command",
200+payload: { kind: "command", command: "echo daily" },
201+state: { lastRunAtMs: STORE_TEST_NOW - 3_600_000 },
202+});
203+expect(config.jobs[2]).toMatchObject({
204+id: "legacy-agentmessage",
205+payload: { kind: "agentmessage", message: "summarize" },
206+metadata: { preserve: { nested: true } },
207+});
208+expect(config.jobs[2]).not.toHaveProperty("state");
209+expect(config.jobs[2]).not.toHaveProperty("updatedAtMs");
210+211+const stateFile = JSON.parse(
212+await fs.readFile(storePath.replace(/\.json$/, "-state.json"), "utf8"),
213+) as { jobs: Record<string, unknown> };
214+expect(Object.keys(stateFile.jobs)).toEqual(["valid-job"]);
215+216+const invalidPayloadWarns = logger.warn.mock.calls.filter((call) => {
217+const msg = typeof call[1] === "string" ? call[1] : "";
218+return msg.includes("skipped invalid persisted job");
219+});
220+expect(invalidPayloadWarns.map((call) => (call[0] as { jobId?: string }).jobId)).toEqual([
221+"legacy-command",
222+"legacy-agentmessage",
223+]);
224+});
225+133226it("normalizes jobId-only jobs in memory so scheduler lookups resolve by stable id", async () => {
134227const { storePath } = await makeStorePath();
135228此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。