From 6a19179e8d9061cb9ab4e6a63951e4d93fd08f89 Mon Sep 17 00:00:00 2001 From: Brad Harris Date: Thu, 21 May 2026 20:23:00 -0600 Subject: [PATCH] Add unit tests for template arg-parser, cron intervals, and job notifier edge cases Coverage additions from test-enforcer run: 28 new tests for the previously untested template arg-parser (parsing, modifier merging, deduplication, substitution), 5 tests for the untested cron interval validator, and 12 tests for job notifier gaps (duration formatting, mrkdwn escaping, status routing, defensive config handling). Co-Authored-By: Claude Opus 4.6 --- apps/server/test/jobs/cron.test.ts | 37 +++- apps/server/test/jobs/job-notifier.test.ts | 156 ++++++++++++++ apps/server/test/template-arg-parser.test.ts | 202 +++++++++++++++++++ 3 files changed, 394 insertions(+), 1 deletion(-) create mode 100644 apps/server/test/template-arg-parser.test.ts diff --git a/apps/server/test/jobs/cron.test.ts b/apps/server/test/jobs/cron.test.ts index bc21b1b0..628c5a8f 100644 --- a/apps/server/test/jobs/cron.test.ts +++ b/apps/server/test/jobs/cron.test.ts @@ -1,6 +1,10 @@ import { describe, expect, it } from "vitest"; -import { getNextRun, validateCronExpression } from "../../src/jobs/cron.js"; +import { + getNextRun, + validateCronExpression, + validateCronInterval, +} from "../../src/jobs/cron.js"; describe("cron utilities", () => { describe("getNextRun", () => { @@ -49,4 +53,35 @@ describe("cron utilities", () => { expect(validateCronExpression("60 * * * *")).toBe(false); }); }); + + describe("validateCronInterval", () => { + it("returns null for schedules at or above 5-minute interval", () => { + expect(validateCronInterval("*/5 * * * *")).toBeNull(); + expect(validateCronInterval("0 * * * *")).toBeNull(); + expect(validateCronInterval("0 0 * * *")).toBeNull(); + }); + + it("returns error for schedules under 5-minute interval", () => { + const result = validateCronInterval("* * * * *"); + expect(result).toMatch(/runs too frequently/); + expect(result).toMatch(/every 60s/); + }); + + it("returns error for every-2-minute schedule", () => { + const result = validateCronInterval("*/2 * * * *"); + expect(result).toMatch(/runs too frequently/); + expect(result).toMatch(/every 120s/); + }); + + it("returns error for invalid cron expression", () => { + expect(validateCronInterval("not valid")).toBe( + "Invalid cron expression." + ); + }); + + it("returns null for schedules that only fire once", () => { + const result = validateCronInterval("0 12 1 1 *"); + expect(result).toBeNull(); + }); + }); }); diff --git a/apps/server/test/jobs/job-notifier.test.ts b/apps/server/test/jobs/job-notifier.test.ts index 5cc7f123..0aa516a7 100644 --- a/apps/server/test/jobs/job-notifier.test.ts +++ b/apps/server/test/jobs/job-notifier.test.ts @@ -181,4 +181,160 @@ describe("JobNotifier", () => { const body = JSON.parse(fetchSpy.mock.calls[0][1].body); expect(body.attachments[0].color).toBe("#ef4444"); }); + + it("displays 'timed out' as status label for timed_out runs", async () => { + await setSetting( + pool, + "slack_webhook_url", + "https://hooks.slack.test/test" + ); + await notifier.onJobRunStateChange(makeRun({ status: "timed_out" })); + + const body = JSON.parse(fetchSpy.mock.calls[0][1].body); + const contextText = body.attachments[0].blocks[1].elements[0].text; + expect(contextText).toContain("`timed out`"); + }); + + it("escapes special characters in job name and summary", async () => { + await setSetting( + pool, + "slack_webhook_url", + "https://hooks.slack.test/test" + ); + await notifier.onJobRunStateChange( + makeRun({ + config: { + ...makeRun().config, + name: "job & special chars", + }, + report: { + status: "completed", + summary: "Fixed