address audit findings: schedule validation, settings json guard, pipeline types, a11y labels
All checks were successful
Build and Push Docker Image / build (push) Successful in 58s
All checks were successful
Build and Push Docker Image / build (push) Successful in 58s
This commit is contained in:
@@ -95,7 +95,11 @@ app.get("/schedule", (c) => {
|
||||
|
||||
app.patch("/schedule", async (c) => {
|
||||
const body = await c.req.json<Partial<ScheduleConfig>>();
|
||||
updateScheduleConfig(body);
|
||||
try {
|
||||
updateScheduleConfig(body);
|
||||
} catch (e) {
|
||||
return c.json({ error: e instanceof Error ? e.message : String(e) }, 400);
|
||||
}
|
||||
return c.json(getScheduleConfig());
|
||||
});
|
||||
|
||||
|
||||
21
server/services/__tests__/scheduler.test.ts
Normal file
21
server/services/__tests__/scheduler.test.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { updateScheduleConfig } from "../scheduler";
|
||||
|
||||
// These tests only exercise the pure validation path in updateScheduleConfig —
|
||||
// invalid payloads must throw before any setConfig() call, so they never touch
|
||||
// the DB. Valid payloads would hit the DB, so we don't exercise them here.
|
||||
describe("updateScheduleConfig validation", () => {
|
||||
test("rejects malformed HH:MM start/end", () => {
|
||||
expect(() => updateScheduleConfig({ scan: { enabled: true, start: "xx:yy", end: "07:00" } })).toThrow();
|
||||
expect(() => updateScheduleConfig({ scan: { enabled: true, start: "1:00", end: "07:00" } })).toThrow();
|
||||
expect(() => updateScheduleConfig({ scan: { enabled: true, start: "24:00", end: "07:00" } })).toThrow();
|
||||
expect(() => updateScheduleConfig({ process: { enabled: true, start: "01:00", end: "99:99" } })).toThrow();
|
||||
});
|
||||
|
||||
test("rejects non-integer, negative, or out-of-bounds job_sleep_seconds", () => {
|
||||
expect(() => updateScheduleConfig({ job_sleep_seconds: Number.NaN })).toThrow();
|
||||
expect(() => updateScheduleConfig({ job_sleep_seconds: -1 })).toThrow();
|
||||
expect(() => updateScheduleConfig({ job_sleep_seconds: 1.5 })).toThrow();
|
||||
expect(() => updateScheduleConfig({ job_sleep_seconds: 86_401 })).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -41,10 +41,33 @@ export function getScheduleConfig(): ScheduleConfig {
|
||||
};
|
||||
}
|
||||
|
||||
const HHMM = /^([01]\d|2[0-3]):[0-5]\d$/;
|
||||
|
||||
function validateWindow(kind: WindowKind, w: Partial<ScheduleWindow>): void {
|
||||
if (w.start != null && !HHMM.test(w.start)) {
|
||||
throw new Error(`${kind}.start must be HH:MM 24h, got ${JSON.stringify(w.start)}`);
|
||||
}
|
||||
if (w.end != null && !HHMM.test(w.end)) {
|
||||
throw new Error(`${kind}.end must be HH:MM 24h, got ${JSON.stringify(w.end)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function updateScheduleConfig(updates: Partial<ScheduleConfig>): void {
|
||||
if (updates.job_sleep_seconds != null) setConfig("job_sleep_seconds", String(updates.job_sleep_seconds));
|
||||
if (updates.scan) writeWindow("scan", updates.scan);
|
||||
if (updates.process) writeWindow("process", updates.process);
|
||||
if (updates.job_sleep_seconds != null) {
|
||||
const n = updates.job_sleep_seconds;
|
||||
if (!Number.isFinite(n) || !Number.isInteger(n) || n < 0 || n > 86_400) {
|
||||
throw new Error(`job_sleep_seconds must be an integer in [0, 86400], got ${JSON.stringify(n)}`);
|
||||
}
|
||||
setConfig("job_sleep_seconds", String(n));
|
||||
}
|
||||
if (updates.scan) {
|
||||
validateWindow("scan", updates.scan);
|
||||
writeWindow("scan", updates.scan);
|
||||
}
|
||||
if (updates.process) {
|
||||
validateWindow("process", updates.process);
|
||||
writeWindow("process", updates.process);
|
||||
}
|
||||
}
|
||||
|
||||
function parseTime(hhmm: string): number {
|
||||
|
||||
Reference in New Issue
Block a user