mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-08 06:54:24 +00:00
fix(cron): guard list sorting against malformed legacy jobs (#28896)
* fix(cron): guard list sorting against malformed legacy jobs Prevent list operations from crashing when old or corrupted cron entries are missing name/id fields by hardening sort comparators. Closes #28862 * cron: format list sort guard test imports --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
53
src/cron/service.list-page-sort-guards.test.ts
Normal file
53
src/cron/service.list-page-sort-guards.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createMockCronStateForJobs } from "./service.test-harness.js";
|
||||
import { listPage } from "./service/ops.js";
|
||||
import type { CronJob } from "./types.js";
|
||||
|
||||
function createBaseJob(overrides?: Partial<CronJob>): CronJob {
|
||||
return {
|
||||
id: "job-1",
|
||||
name: "job",
|
||||
enabled: true,
|
||||
schedule: { kind: "cron", expr: "*/5 * * * *", tz: "UTC" },
|
||||
sessionTarget: "main",
|
||||
wakeMode: "now",
|
||||
payload: { kind: "systemEvent", text: "tick" },
|
||||
state: { nextRunAtMs: Date.parse("2026-02-27T15:30:00.000Z") },
|
||||
createdAtMs: Date.parse("2026-02-27T15:00:00.000Z"),
|
||||
updatedAtMs: Date.parse("2026-02-27T15:05:00.000Z"),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("cron listPage sort guards", () => {
|
||||
it("does not throw when sorting by name with malformed name fields", async () => {
|
||||
const jobs = [
|
||||
createBaseJob({ id: "job-a", name: undefined as unknown as string }),
|
||||
createBaseJob({ id: "job-b", name: "beta" }),
|
||||
];
|
||||
const state = createMockCronStateForJobs({ jobs });
|
||||
|
||||
const page = await listPage(state, { sortBy: "name", sortDir: "asc" });
|
||||
expect(page.jobs).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("does not throw when tie-break sorting encounters missing ids", async () => {
|
||||
const nextRunAtMs = Date.parse("2026-02-27T15:30:00.000Z");
|
||||
const jobs = [
|
||||
createBaseJob({
|
||||
id: undefined as unknown as string,
|
||||
name: "alpha",
|
||||
state: { nextRunAtMs },
|
||||
}),
|
||||
createBaseJob({
|
||||
id: undefined as unknown as string,
|
||||
name: "alpha",
|
||||
state: { nextRunAtMs },
|
||||
}),
|
||||
];
|
||||
const state = createMockCronStateForJobs({ jobs });
|
||||
|
||||
const page = await listPage(state, { sortBy: "nextRunAtMs", sortDir: "asc" });
|
||||
expect(page.jobs).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
@@ -164,7 +164,9 @@ function sortJobs(jobs: CronJob[], sortBy: CronJobsSortBy, sortDir: CronSortDir)
|
||||
return jobs.toSorted((a, b) => {
|
||||
let cmp = 0;
|
||||
if (sortBy === "name") {
|
||||
cmp = a.name.localeCompare(b.name, undefined, { sensitivity: "base" });
|
||||
const aName = typeof a.name === "string" ? a.name : "";
|
||||
const bName = typeof b.name === "string" ? b.name : "";
|
||||
cmp = aName.localeCompare(bName, undefined, { sensitivity: "base" });
|
||||
} else if (sortBy === "updatedAtMs") {
|
||||
cmp = a.updatedAtMs - b.updatedAtMs;
|
||||
} else {
|
||||
@@ -183,7 +185,9 @@ function sortJobs(jobs: CronJob[], sortBy: CronJobsSortBy, sortDir: CronSortDir)
|
||||
if (cmp !== 0) {
|
||||
return cmp * dir;
|
||||
}
|
||||
return a.id.localeCompare(b.id);
|
||||
const aId = typeof a.id === "string" ? a.id : "";
|
||||
const bId = typeof b.id === "string" ? b.id : "";
|
||||
return aId.localeCompare(bId);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user