|
| 1 | +import { describe, expect, it } from "vitest"; |
| 2 | +import { |
| 3 | + type AutomationScheduleDraft, |
| 4 | + buildCronExpression, |
| 5 | + createDefaultScheduleDraft, |
| 6 | + deriveAutomationName, |
| 7 | + formatAutomationScheduleSummary, |
| 8 | + formatScheduleSummary, |
| 9 | + parseCronExpression, |
| 10 | + sanitizeHour, |
| 11 | + sanitizeMinute, |
| 12 | + WEEKDAY_OPTIONS, |
| 13 | +} from "./automationSchedule"; |
| 14 | + |
| 15 | +describe("automationSchedule", () => { |
| 16 | + it("creates the default daily schedule draft", () => { |
| 17 | + expect(createDefaultScheduleDraft()).toEqual({ |
| 18 | + mode: "daily", |
| 19 | + hour: "09", |
| 20 | + minute: "00", |
| 21 | + weekday: "1", |
| 22 | + rawCron: "0 9 * * *", |
| 23 | + }); |
| 24 | + }); |
| 25 | + |
| 26 | + it("provides cron weekday values in display order", () => { |
| 27 | + expect(WEEKDAY_OPTIONS).toEqual([ |
| 28 | + { value: "1", label: "Mon" }, |
| 29 | + { value: "2", label: "Tue" }, |
| 30 | + { value: "3", label: "Wed" }, |
| 31 | + { value: "4", label: "Thu" }, |
| 32 | + { value: "5", label: "Fri" }, |
| 33 | + { value: "6", label: "Sat" }, |
| 34 | + { value: "0", label: "Sun" }, |
| 35 | + ]); |
| 36 | + }); |
| 37 | + |
| 38 | + it.each([ |
| 39 | + ["", ""], |
| 40 | + ["a", ""], |
| 41 | + ["7", "07"], |
| 42 | + ["09", "09"], |
| 43 | + ["2x3", "23"], |
| 44 | + ["24", "23"], |
| 45 | + ["999", "23"], |
| 46 | + ])("sanitizes hour input %j to %j", (input, expected) => { |
| 47 | + expect(sanitizeHour(input)).toBe(expected); |
| 48 | + }); |
| 49 | + |
| 50 | + it.each([ |
| 51 | + ["", ""], |
| 52 | + ["a", ""], |
| 53 | + ["7", "07"], |
| 54 | + ["09", "09"], |
| 55 | + ["5x9", "59"], |
| 56 | + ["60", "59"], |
| 57 | + ["999", "59"], |
| 58 | + ])("sanitizes minute input %j to %j", (input, expected) => { |
| 59 | + expect(sanitizeMinute(input)).toBe(expected); |
| 60 | + }); |
| 61 | + |
| 62 | + it.each<{ |
| 63 | + name: string; |
| 64 | + changes: Partial<AutomationScheduleDraft>; |
| 65 | + expected: string; |
| 66 | + }>([ |
| 67 | + { |
| 68 | + name: "hourly", |
| 69 | + changes: { mode: "hourly", minute: "15" }, |
| 70 | + expected: "15 * * * *", |
| 71 | + }, |
| 72 | + { |
| 73 | + name: "daily", |
| 74 | + changes: { mode: "daily", hour: "09", minute: "15" }, |
| 75 | + expected: "15 9 * * *", |
| 76 | + }, |
| 77 | + { |
| 78 | + name: "weekdays", |
| 79 | + changes: { mode: "weekdays", hour: "10", minute: "00" }, |
| 80 | + expected: "0 10 * * 1-5", |
| 81 | + }, |
| 82 | + { |
| 83 | + name: "weekly", |
| 84 | + changes: { |
| 85 | + mode: "weekly", |
| 86 | + hour: "11", |
| 87 | + minute: "30", |
| 88 | + weekday: "4", |
| 89 | + }, |
| 90 | + expected: "30 11 * * 4", |
| 91 | + }, |
| 92 | + { |
| 93 | + name: "weekly with a missing weekday", |
| 94 | + changes: { mode: "weekly", weekday: "" }, |
| 95 | + expected: "0 9 * * 1", |
| 96 | + }, |
| 97 | + { |
| 98 | + name: "preset with missing time values", |
| 99 | + changes: { mode: "daily", hour: "", minute: "" }, |
| 100 | + expected: "0 9 * * *", |
| 101 | + }, |
| 102 | + { |
| 103 | + name: "custom", |
| 104 | + changes: { mode: "custom", rawCron: " */15 * * * * " }, |
| 105 | + expected: "*/15 * * * *", |
| 106 | + }, |
| 107 | + ])("builds the $name cron expression", ({ changes, expected }) => { |
| 108 | + expect( |
| 109 | + buildCronExpression({ ...createDefaultScheduleDraft(), ...changes }), |
| 110 | + ).toBe(expected); |
| 111 | + }); |
| 112 | + |
| 113 | + it.each([ |
| 114 | + [ |
| 115 | + "15 * * * *", |
| 116 | + { |
| 117 | + mode: "hourly", |
| 118 | + hour: "09", |
| 119 | + minute: "15", |
| 120 | + weekday: "*", |
| 121 | + rawCron: "15 * * * *", |
| 122 | + }, |
| 123 | + ], |
| 124 | + [ |
| 125 | + "0 9 * * *", |
| 126 | + { |
| 127 | + mode: "daily", |
| 128 | + hour: "09", |
| 129 | + minute: "00", |
| 130 | + weekday: "*", |
| 131 | + rawCron: "0 9 * * *", |
| 132 | + }, |
| 133 | + ], |
| 134 | + [ |
| 135 | + "0 9 * * 1-5", |
| 136 | + { |
| 137 | + mode: "weekdays", |
| 138 | + hour: "09", |
| 139 | + minute: "00", |
| 140 | + weekday: "1", |
| 141 | + rawCron: "0 9 * * 1-5", |
| 142 | + }, |
| 143 | + ], |
| 144 | + [ |
| 145 | + "30 14 * * 2", |
| 146 | + { |
| 147 | + mode: "weekly", |
| 148 | + hour: "14", |
| 149 | + minute: "30", |
| 150 | + weekday: "2", |
| 151 | + rawCron: "30 14 * * 2", |
| 152 | + }, |
| 153 | + ], |
| 154 | + ] as const)("parses %s into a schedule draft", (cron, expected) => { |
| 155 | + expect(parseCronExpression(cron)).toEqual(expected); |
| 156 | + }); |
| 157 | + |
| 158 | + it.each(["*/15 * * * *", "0 9 1 * *", "0 9 * 1 *", "0 9 * * 1,3"])( |
| 159 | + "keeps unsupported cron expression %s in custom mode", |
| 160 | + (cron) => { |
| 161 | + expect(parseCronExpression(cron)).toMatchObject({ |
| 162 | + mode: "custom", |
| 163 | + rawCron: cron, |
| 164 | + }); |
| 165 | + }, |
| 166 | + ); |
| 167 | + |
| 168 | + it("normalizes surrounding and repeated cron whitespace", () => { |
| 169 | + expect(parseCronExpression(" 5 8 * * * ")).toEqual({ |
| 170 | + mode: "daily", |
| 171 | + hour: "08", |
| 172 | + minute: "05", |
| 173 | + weekday: "*", |
| 174 | + rawCron: "5 8 * * *", |
| 175 | + }); |
| 176 | + }); |
| 177 | + |
| 178 | + it("uses default draft fields for a cron expression with the wrong arity", () => { |
| 179 | + expect(parseCronExpression("0 9 * *")).toEqual({ |
| 180 | + mode: "custom", |
| 181 | + hour: "09", |
| 182 | + minute: "00", |
| 183 | + weekday: "1", |
| 184 | + rawCron: "0 9 * *", |
| 185 | + }); |
| 186 | + }); |
| 187 | + |
| 188 | + it("derives a compact name from the first non-empty prompt line", () => { |
| 189 | + expect( |
| 190 | + deriveAutomationName( |
| 191 | + "\n Review every open PostHog PR for stale comments \nIgnore this line", |
| 192 | + ), |
| 193 | + ).toBe("Review every open PostHog PR for stale comments"); |
| 194 | + }); |
| 195 | + |
| 196 | + it("returns an empty name for a blank prompt", () => { |
| 197 | + expect(deriveAutomationName(" \n\t\n ")).toBe(""); |
| 198 | + }); |
| 199 | + |
| 200 | + it("limits derived names to 80 characters", () => { |
| 201 | + expect(deriveAutomationName("a".repeat(100))).toBe("a".repeat(80)); |
| 202 | + }); |
| 203 | + |
| 204 | + it.each([ |
| 205 | + ["15 * * * *", "Europe/London", "Every hour at :15 · Europe/London"], |
| 206 | + ["0 9 * * *", null, "Daily at 09:00"], |
| 207 | + ["0 9 * * 1-5", "UTC", "Weekdays at 09:00 · UTC"], |
| 208 | + ["30 14 * * 2", undefined, "Tue at 14:30"], |
| 209 | + ["30 14 * * 7", "UTC", "Weekly at 14:30 · UTC"], |
| 210 | + ["*/15 * * * *", "UTC", "Custom schedule · UTC"], |
| 211 | + ])("formats %s with timezone %j", (cronExpression, timezone, expected) => { |
| 212 | + expect(formatScheduleSummary(cronExpression, timezone)).toBe(expected); |
| 213 | + }); |
| 214 | + |
| 215 | + it("formats a schedule from an automation-shaped input", () => { |
| 216 | + expect( |
| 217 | + formatAutomationScheduleSummary({ |
| 218 | + cron_expression: "0 18 * * 0", |
| 219 | + timezone: "America/New_York", |
| 220 | + }), |
| 221 | + ).toBe("Sun at 18:00 · America/New_York"); |
| 222 | + }); |
| 223 | +}); |
0 commit comments