Add tests and correct a lot of validation
This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
import { isValidCron } from "./cron"
|
||||||
|
|
||||||
|
describe("isValidCron", () => {
|
||||||
|
const valid = [
|
||||||
|
"0 0 * * *",
|
||||||
|
"15 * * * *",
|
||||||
|
"2,10 4,5 * * *",
|
||||||
|
"30 4-6 * * *",
|
||||||
|
"20/15 * * * *",
|
||||||
|
"30 5,17 * * *",
|
||||||
|
"30 5 * * 1,3",
|
||||||
|
"30 5 * * 2,4",
|
||||||
|
"28 */4 * * *",
|
||||||
|
"28 5,*/4 * * *",
|
||||||
|
"28 5,*/4,6 * * *",
|
||||||
|
"28 5,*/4,6-8 * * *",
|
||||||
|
"0 0 * * SUN",
|
||||||
|
"0 0 * * SUN-TUE",
|
||||||
|
"0 0 * * SUN-2",
|
||||||
|
"0 * * */FEB */TUE",
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const cron of valid) {
|
||||||
|
it(`${cron} should be valid`, () => {
|
||||||
|
expect(isValidCron(cron)).toBe(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const invalid = [
|
||||||
|
"0 0 * *",
|
||||||
|
"0 0 * * BUN",
|
||||||
|
"0 0 * SUN JAN",
|
||||||
|
"0 0 * * FRI-TUE",
|
||||||
|
"0 12-4 * * *",
|
||||||
|
"0 */0 * * *"
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const cron of invalid) {
|
||||||
|
it(`${cron} should be invalid`, () => {
|
||||||
|
expect(isValidCron(cron)).toBe(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,95 +1,111 @@
|
|||||||
const MONTHS = {
|
const MONTHS = {
|
||||||
jan: 1,
|
jan: 1,
|
||||||
feb: 2,
|
feb: 2,
|
||||||
mar: 3,
|
mar: 3,
|
||||||
apr: 4,
|
apr: 4,
|
||||||
may: 5,
|
may: 5,
|
||||||
jun: 6,
|
jun: 6,
|
||||||
jul: 7,
|
jul: 7,
|
||||||
aug: 8,
|
aug: 8,
|
||||||
sep: 9,
|
sep: 9,
|
||||||
oct: 10,
|
oct: 10,
|
||||||
nov: 11,
|
nov: 11,
|
||||||
dec: 12,
|
dec: 12,
|
||||||
|
}
|
||||||
|
|
||||||
|
const DAYS = {
|
||||||
|
sun: 0,
|
||||||
|
mon: 1,
|
||||||
|
tue: 2,
|
||||||
|
wed: 3,
|
||||||
|
thu: 4,
|
||||||
|
fri: 5,
|
||||||
|
sat: 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make this parseCron
|
||||||
|
export function isValidCron(cron: string): boolean {
|
||||||
|
// https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
|
||||||
|
|
||||||
|
const parts = cron.split(" ")
|
||||||
|
if (parts.length != 5) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const DAYS = {
|
const [minutes, hours, dom, months, dow] = parts
|
||||||
sun: 0,
|
|
||||||
mon: 1,
|
return (
|
||||||
tue: 2,
|
validateRange(minutes, { min: 0, max: 59 }) &&
|
||||||
wed: 3,
|
validateRange(hours, { min: 0, max: 23 }) &&
|
||||||
thu: 4,
|
validateRange(dom, { min: 1, max: 31 }) &&
|
||||||
fri: 5,
|
validateRange(months, { min: 1, max: 12, names: MONTHS }) &&
|
||||||
sat: 6,
|
validateRange(dow, { min: 0, max: 6, names: DAYS })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Range = {
|
||||||
|
min: number
|
||||||
|
max: number
|
||||||
|
names?: Record<string, number>
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateRange(
|
||||||
|
value: string,
|
||||||
|
range: Range,
|
||||||
|
allowSeparators = true
|
||||||
|
): boolean {
|
||||||
|
if (range.names && range.names[value.toLowerCase()] !== undefined) {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make this parseCron
|
if (value === "*") {
|
||||||
export function isValidCron(cron: string): boolean {
|
return true
|
||||||
// https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
|
}
|
||||||
|
|
||||||
const parts = cron.split(" ")
|
if (value.includes(",")) {
|
||||||
if (parts.length != 5) {
|
if (!allowSeparators) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Allow separators
|
||||||
|
return value.split(",").every((v) => validateRange(v, range))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.includes("/")) {
|
||||||
|
if (!allowSeparators) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const [minutes, hours, dom, months, dow] = parts
|
const [start, step, ...rest] = value.split("/")
|
||||||
|
// Supports */TUE and similar, which nees to be verified with the go cron library
|
||||||
|
const stepNumber = convertToNumber(range, step)
|
||||||
|
if (rest.length > 0 || stepNumber <= 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
validateRange(minutes, { min: 0, max: 59 }) &&
|
validateRange(start, range, false) && validateRange(step, range, false)
|
||||||
validateRange(hours, { min: 0, max: 23 }) &&
|
|
||||||
validateRange(dom, { min: 1, max: 31 }) &&
|
|
||||||
validateRange(months, { min: 1, max: 12, names: MONTHS }) &&
|
|
||||||
validateRange(dow, { min: 0, max: 6, names: DAYS })
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Range = {
|
if (value.includes("-")) {
|
||||||
min: number
|
const [start, end] = value.split("-")
|
||||||
max: number
|
|
||||||
names?: Record<string, number>
|
// Convert name to integers so we can make sure end >= start
|
||||||
|
const startNumber = convertToNumber(range, start)
|
||||||
|
const endNumber = convertToNumber(range, end)
|
||||||
|
return (
|
||||||
|
validateRange(start, range, false) && validateRange(end, range, false) && endNumber >= startNumber
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateRange(
|
const number = +value
|
||||||
value: string,
|
return !isNaN(number) && number >= range.min && number <= range.max
|
||||||
range: Range,
|
}
|
||||||
allowSeparators = true
|
|
||||||
): boolean {
|
|
||||||
if (range.names && range.names[value.toLowerCase()] !== undefined) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value === "*") {
|
function convertToNumber(range: Range, value: string): number {
|
||||||
return true
|
if (range.names && range.names[value.toLowerCase()] !== undefined) {
|
||||||
}
|
return +range.names[value.toLowerCase()]
|
||||||
|
|
||||||
if (value.includes(",")) {
|
|
||||||
if (!allowSeparators) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return value.split(",").every((v) => validateRange(v, range, false))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.includes("/")) {
|
|
||||||
if (!allowSeparators) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const [start, step, ...rest] = value.split("/")
|
|
||||||
if (rest.length > 0) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
validateRange(start, range, false) && validateRange(step, range, false)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.includes("-")) {
|
|
||||||
const [start, end] = value.split("-")
|
|
||||||
return (
|
|
||||||
validateRange(start, range, false) && validateRange(end, range, false)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const number = parseInt(value, 10)
|
|
||||||
return !isNaN(number) && number >= range.min && number <= range.max
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
return +value
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user