From 08273a85d8027b4b66876a91eb23c4c59e5efab6 Mon Sep 17 00:00:00 2001 From: Jacob Wallraff Date: Mon, 23 Jan 2023 14:18:05 -0800 Subject: [PATCH] Add constants and re-add tests: --- .../src/model/converter/cron-constants.ts | 32 +++ .../src/model/converter/cron.test.ts | 209 +++++++++++++----- 2 files changed, 184 insertions(+), 57 deletions(-) create mode 100644 actions-workflow-parser/src/model/converter/cron-constants.ts diff --git a/actions-workflow-parser/src/model/converter/cron-constants.ts b/actions-workflow-parser/src/model/converter/cron-constants.ts new file mode 100644 index 0000000..02df399 --- /dev/null +++ b/actions-workflow-parser/src/model/converter/cron-constants.ts @@ -0,0 +1,32 @@ +// Constants for parsing and validating cron expressions + +const MONTHS = { + jan: 1, + feb: 2, + mar: 3, + apr: 4, + may: 5, + jun: 6, + jul: 7, + aug: 8, + sep: 9, + oct: 10, + nov: 11, + dec: 12, +} + +const DAYS = { + sun: 0, + mon: 1, + tue: 2, + wed: 3, + thu: 4, + fri: 5, + sat: 6, +} + +export const MINUTE_RANGE = { min: 0, max: 59 } +export const HOUR_RANGE = { min: 0, max: 23 } +export const DOM_RANGE = { min: 1, max: 31 } +export const MONTH_RANGE = { min: 1, max: 12, names: MONTHS } +export const DOW_RANGE = { min: 0, max: 6, names: DAYS } \ No newline at end of file diff --git a/actions-workflow-parser/src/model/converter/cron.test.ts b/actions-workflow-parser/src/model/converter/cron.test.ts index d407558..7c88fa7 100644 --- a/actions-workflow-parser/src/model/converter/cron.test.ts +++ b/actions-workflow-parser/src/model/converter/cron.test.ts @@ -1,63 +1,158 @@ -import { isValidCron } from "./cron" +import { isValidCron, getSchedule, getSentence } from "./cron" -describe("isValidCron", () => { - const valid = [ - ["0 0 * * *", "every day at midnight"], - ["0 000 001 * *", "accepts leading zeros"], - ["15 * * * *", "accepts numbers in range"], - ["2,10 4,5 * * *", "accepts comma separated values"], - ["30 4-6 * * *", "accepts range"], - ["0 4-4 * * *", "accepts range with two equal values"], - ["20/15 * * * *", "accepts step with numerical values"], - ["30 5,17 * * *", "accepts numbers and ranges"], - ["28 */4 * * *", "accepts step with * and numerical value"], - ["28 5,*/4 * * *", "accepts comma separated value with step"], - ["28 5,*/4,6-8 * * *", "accepts comma separated value with step and range"], - ["0 0 * * SUN", "accepts day of week short name"], - ["0 0 * * SUN-TUE", "accepts day of week short name range"], - ["0 0 * * SUN-2", "accepts day of week range combined with number"], - ["0 2-4/5 * * *", "accepts range with step"], - ["0 0 * * *", "accepts multiple spaces"], - ] +describe("cron", () => { + describe("valid cron", () => { + const valid = [ + ["0 0 * * *", "every day at midnight"], + ["0 000 001 * *", "accepts leading zeros"], + ["15 * * * *", "accepts numbers in range"], + ["2,10 4,5 * * *", "accepts comma separated values"], + ["30 4-6 * * *", "accepts range"], + ["0 4-4 * * *", "accepts range with two equal values"], + ["20/15 * * * *", "accepts step with numerical values"], + ["30 5,17 * * *", "accepts numbers and ranges"], + ["28 */4 * * *", "accepts step with * and numerical value"], + ["28 5,*/4 * * *", "accepts comma separated value with step"], + ["28 5,*/4,6-8 * * *", "accepts comma separated value with step and range"], + ["0 0 * * SUN", "accepts day of week short name"], + ["0 0 * * SUN-TUE", "accepts day of week short name range"], + ["0 0 * * SUN-2", "accepts day of week range combined with number"], + ["0 2-4/5 * * *", "accepts range with step"], + ["0 0 * * *", "accepts multiple spaces"], + ] - for (const [cron, reason] of valid) { - it(`${cron} should be valid: ${reason}`, () => { - expect(isValidCron(cron)).toBe(true) + for (const [cron, reason] of valid) { + it(`${cron} should be valid: ${reason}`, () => { + expect(isValidCron(cron)).toBe(true) + }) + } + }) + + describe("invalid cron", () => { + const invalid = [ + ["0 0 * *", "too few parts"], + ["0 0 * * * * *", "too many parts"], + ["0 -1 * * *", "should not accept negative numbers"], + ["0 1- * * *", "should not accept trailing -"], + ["0 /1 * * *", "should not accept leading / (empty value)"], + ["0 1/ * * *", "should not accept trailing / (empty value)"], + ["0 ,1 * * *", "should not accept leading , (empty value)"], + ["0 1, * * *", "should not accept trailing , (empty value)"], + ["0 5--5 * * *", "should not accept multiple -"], + ["0 *//5 * * *", "should not accept multiple /"], + ["0 */* * * *", "step start and size may not both be *"], + ["0 5/* * * *", "steps size may not be *"], + ["0 *-4 5-* *-* *", "range may not contain *"], + ["0 *//5 * * *", "should not accept multiple /"], + ["0 ,, * * *", "should not accept multiple ,"], + [", , , , ,", "comma is not a valid part"], + ["0 ** * * *", "should not accept multiple *"], + ["0 0 * * BUN", "invalid short name"], + ["0 0 * SUN JAN", "short name in incorrect position"], + ["0 0 * * FRI-TUE", "should not accept short name range with start > end"], + ["0 12-4 * * *", "should not accept nuerical range with start > end"], + ["0 */0 * * *", "step size may not be 0"], + ["0 2/4-5 * * *", "step size may not be a range"], + ["0 2-4-6 * * *", "range may not contain multiple -"], + ["0 2/4/6 * * *", "step may not contain multiple /"], + ["0 * * */FEB */TUE", "step size may not be a short name"], + ] + + for (const [cron, reason] of invalid) { + it(`${cron} should be invalid: ${reason}`, () => { + expect(isValidCron(cron)).toBe(false) + }) + } + }) + + describe("getSchedule", () => { + const testCases = [ + { + cron: "0 1 2 3 4", + expected: { + minutes: [0], + hours: [1], + dom: [2], + months: [3], + dow: [4], + } + }, + { + cron: "2,4 * * * *", + expected: { + minutes: [2, 4], + hours: undefined, + dom: undefined, + months: undefined, + dow: undefined, + } + }, + { + cron: "2-4 * * * *", + expected: { + minutes: [2, 3, 4], + hours: undefined, + dom: undefined, + months: undefined, + dow: undefined, + } + }, + { + cron: "*/15 * * * *", + expected: { + minutes: [0, 15, 30, 45], + hours: undefined, + dom: undefined, + months: undefined, + dow: undefined, + } + }, + { + cron: "10/15 * * * *", + expected: { + minutes: [10, 25, 40, 55], + hours: undefined, + dom: undefined, + months: undefined, + dow: undefined, + } + }, + { + cron: "5,*/20 * * * *", + expected: { + minutes: [0, 5, 20, 40], + hours: undefined, + dom: undefined, + months: undefined, + dow: undefined, + } + }, + { + cron: "* * * JAN SUN", + expected: { + minutes: undefined, + hours: undefined, + dom: undefined, + months: [1], + dow: [0], + } + }, + ] + + for (const testCase of testCases) { + it(`getSchedule '${testCase.cron}'`, () => { + expect(getSchedule(testCase.cron)).toEqual(testCase.expected) + }) + } + }) + + describe("getSentence", () => { + it(`Produces a sentence for valid cron`, () => { + expect(getSentence("0 * * * *")).toEqual("Every hour") }) - } - const invalid = [ - ["0 0 * *", "too few parts"], - ["0 0 * * * * *", "too many parts"], - ["0 -1 * * *", "should not accept negative numbers"], - ["0 1- * * *", "should not accept trailing -"], - ["0 /1 * * *", "should not accept leading / (empty value)"], - ["0 1/ * * *", "should not accept trailing / (empty value)"], - ["0 ,1 * * *", "should not accept leading , (empty value)"], - ["0 1, * * *", "should not accept trailing , (empty value)"], - ["0 5--5 * * *", "should not accept multiple -"], - ["0 *//5 * * *", "should not accept multiple /"], - ["0 */* * * *", "step start and size may not both be *"], - ["0 5/* * * *", "steps size may not be *"], - ["0 *-4 5-* *-* *", "range may not contain *"], - ["0 *//5 * * *", "should not accept multiple /"], - ["0 ,, * * *", "should not accept multiple ,"], - [", , , , ,", "comma is not a valid part"], - ["0 ** * * *", "should not accept multiple *"], - ["0 0 * * BUN", "invalid short name"], - ["0 0 * SUN JAN", "short name in incorrect position"], - ["0 0 * * FRI-TUE", "should not accept short name range with start > end"], - ["0 12-4 * * *", "should not accept nuerical range with start > end"], - ["0 */0 * * *", "step size may not be 0"], - ["0 2/4-5 * * *", "step size may not be a range"], - ["0 2-4-6 * * *", "range may not contain multiple -"], - ["0 2/4/6 * * *", "step may not contain multiple /"], - ["0 * * */FEB */TUE", "step size may not be a short name"], - ] - - for (const [cron, reason] of invalid) { - it(`${cron} should be invalid: ${reason}`, () => { - expect(isValidCron(cron)).toBe(false) + it(`Returns nothing for invalid cron`, () => { + expect(getSentence("* * * * * *")).toBeUndefined() }) - } + }) }) \ No newline at end of file