Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
import fs from "node:fs/promises";
import path from "node:path";
import { describe, expect, it, vi } from "vitest";
import { setupCronServiceSuite } from "../service.test-harness.js";
import { findJobOrThrow } from "./jobs.js";
import { createCronServiceState } from "./state.js";
import { ensureLoaded, persist } from "./store.js";
const { logger, makeStorePath } = setupCronServiceSuite({
prefix: "cron-service-store-seam",
});
const STORE_TEST_NOW = Date.parse("2026-03-23T12:00:00.000Z");
async function writeSingleJobStore(storePath: string, job: Record<string, unknown>) {
await fs.mkdir(path.dirname(storePath), { recursive: true });
await fs.writeFile(
storePath,
JSON.stringify(
{
version: 1,
jobs: [job],
},
null,
2,
),
"utf8",
);
}
function createStoreTestState(storePath: string) {
return createCronServiceState({
storePath,
cronEnabled: true,
log: logger,
nowMs: () => STORE_TEST_NOW,
enqueueSystemEvent: vi.fn(),
requestHeartbeatNow: vi.fn(),
runIsolatedAgentJob: vi.fn(async () => ({ status: "ok" as const })),
});
}
describe("cron service store seam coverage", () => {
it("loads stored jobs, recomputes next runs, and does not rewrite the store on load", async () => {
const { storePath } = await makeStorePath();
await writeSingleJobStore(storePath, {
id: "modern-job",
name: "modern job",
enabled: true,
createdAtMs: STORE_TEST_NOW - 60_000,
updatedAtMs: STORE_TEST_NOW - 60_000,
schedule: { kind: "every", everyMs: 60_000 },
sessionTarget: "isolated",
wakeMode: "now",
payload: { kind: "agentTurn", message: "ping" },
delivery: { mode: "announce", channel: "telegram", to: "123" },
state: {},
});
const state = createStoreTestState(storePath);
await ensureLoaded(state);
const job = state.store?.jobs[0];
expect(job).toBeDefined();
expect(job?.sessionTarget).toBe("isolated");
expect(job?.payload.kind).toBe("agentTurn");
if (job?.payload.kind === "agentTurn") {
expect(job.payload.message).toBe("ping");
}
expect(job?.delivery).toMatchObject({
mode: "announce",
channel: "telegram",
to: "123",
});
expect(job?.state.nextRunAtMs).toBe(STORE_TEST_NOW);
const persisted = JSON.parse(await fs.readFile(storePath, "utf8")) as {
jobs: Array<Record<string, unknown>>;
};
const persistedJob = persisted.jobs[0];
expect(persistedJob?.payload).toMatchObject({
kind: "agentTurn",
message: "ping",
});
expect(persistedJob?.delivery).toMatchObject({
mode: "announce",
channel: "telegram",
to: "123",
});
const firstMtime = state.storeFileMtimeMs;
expect(typeof firstMtime).toBe("number");
await persist(state);
expect(typeof state.storeFileMtimeMs).toBe("number");
expect((state.storeFileMtimeMs ?? 0) >= (firstMtime ?? 0)).toBe(true);
});
it("normalizes jobId-only jobs in memory so scheduler lookups resolve by stable id", async () => {
const { storePath } = await makeStorePath();
await writeSingleJobStore(storePath, {
jobId: "repro-stable-id",
name: "handed",
enabled: true,
createdAtMs: STORE_TEST_NOW - 60_000,
updatedAtMs: STORE_TEST_NOW - 60_000,
schedule: { kind: "every", everyMs: 60_000 },
sessionTarget: "main",
wakeMode: "now",
payload: { kind: "systemEvent", text: "tick" },
state: {},
});
const state = createStoreTestState(storePath);
await ensureLoaded(state);
expect(logger.warn).toHaveBeenCalledWith(
expect.objectContaining({ storePath, jobId: "repro-stable-id" }),
expect.stringContaining("legacy jobId"),
);
const job = findJobOrThrow(state, "repro-stable-id");
expect(job.id).toBe("repro-stable-id");
expect((job as { jobId?: unknown }).jobId).toBeUndefined();
const raw = JSON.parse(await fs.readFile(storePath, "utf8")) as {
jobs: Array<Record<string, unknown>>;
};
expect(raw.jobs[0]?.jobId).toBe("repro-stable-id");
expect(raw.jobs[0]?.id).toBeUndefined();
});
it("preserves disabled jobs when persisted booleans roundtrip through string values", async () => {
const { storePath } = await makeStorePath();
await writeSingleJobStore(storePath, {
id: "disabled-string-job",
name: "disabled string job",
enabled: "false",
createdAtMs: STORE_TEST_NOW - 60_000,
updatedAtMs: STORE_TEST_NOW - 60_000,
schedule: { kind: "every", everyMs: 60_000 },
sessionTarget: "main",
wakeMode: "now",
payload: { kind: "systemEvent", text: "tick" },
state: {},
});
const before = await fs.readFile(storePath, "utf8");
const state = createStoreTestState(storePath);
await ensureLoaded(state);
const job = findJobOrThrow(state, "disabled-string-job");
expect(job.enabled).toBe(false);
const after = await fs.readFile(storePath, "utf8");
expect(after).toBe(before);
});
it("loads persisted jobs with unsafe custom session ids so run paths can fail closed", async () => {
const { storePath } = await makeStorePath();
await writeSingleJobStore(storePath, {
id: "unsafe-session-target-job",
name: "unsafe session target job",
enabled: true,
createdAtMs: STORE_TEST_NOW - 60_000,
updatedAtMs: STORE_TEST_NOW - 60_000,
schedule: { kind: "every", everyMs: 60_000 },
sessionTarget: "session:../../outside",
wakeMode: "now",
payload: { kind: "agentTurn", message: "ping" },
state: {},
});
const state = createStoreTestState(storePath);
await ensureLoaded(state, { skipRecompute: true });
const job = findJobOrThrow(state, "unsafe-session-target-job");
expect(job.sessionTarget).toBe("session:../../outside");
expect(logger.warn).toHaveBeenCalledWith(
expect.objectContaining({ storePath, jobId: "unsafe-session-target-job" }),
expect.stringContaining("invalid persisted sessionTarget"),
);
});
});
¤ Dauer der Verarbeitung: 0.27 Sekunden
(vorverarbeitet am 2026-04-27)
¤
*© Formatika GbR, Deutschland
|
|