Skip to content

Commit 5e10db0

Browse files
committedFeb 16, 2025··
Remove assert usage for better compability. Add and update test cases to code that was missing proper test cases
1 parent 9ddf705 commit 5e10db0

8 files changed

+413
-237
lines changed
 

‎src/CronExpression.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import assert from 'assert';
21
import { DateTime } from 'luxon';
32

43
import { CronDate, DateMathOp, TimeUnit } from './CronDate';
@@ -117,7 +116,9 @@ export class CronExpression {
117116

118117
// The first character represents the weekday
119118
const weekday = parseInt(expression.toString().charAt(0), 10) % 7;
120-
assert(!Number.isNaN(weekday), `Invalid last weekday of the month expression: ${expression}`);
119+
if (Number.isNaN(weekday)) {
120+
throw new Error(`Invalid last weekday of the month expression: ${expression}`);
121+
}
121122

122123
// Check if the current date matches the last specified weekday of the month
123124
return currentDate.getDay() === weekday && currentDate.isLastWeekdayOfMonth();
@@ -239,9 +240,7 @@ export class CronExpression {
239240
*/
240241
includesDate(date: Date | CronDate): boolean {
241242
const { second, minute, hour, dayOfMonth, month, dayOfWeek } = this.#fields;
242-
const dtStr = date.toISOString();
243-
assert(dtStr != null, 'Invalid date');
244-
const dt = DateTime.fromISO(dtStr, { zone: this.#tz });
243+
const dt = DateTime.fromISO(date.toISOString()!, { zone: this.#tz });
245244
return (
246245
dayOfMonth.values.includes(<DayOfMonthRange>dt.day) &&
247246
dayOfWeek.values.includes(<DayOfWeekRange>dt.weekday) &&

‎src/CronExpressionParser.ts

+50-43
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import assert from 'assert';
2-
31
import { CronFieldCollection } from './CronFieldCollection';
42
import { CronDate } from './CronDate';
53
import { CronExpression, CronExpressionOptions } from './CronExpression';
@@ -15,7 +13,7 @@ import {
1513
DayOfWeekRange,
1614
HourRange,
1715
MonthRange,
18-
ParseRageResponse,
16+
ParseRangeResponse,
1917
SixtyRange,
2018
} from './fields';
2119

@@ -99,10 +97,9 @@ export class CronExpressionParser {
9997

10098
expression = PredefinedExpressions[expression as keyof typeof PredefinedExpressions] || expression;
10199
const rawFields = CronExpressionParser.#getRawFields(expression, strict);
102-
assert(
103-
rawFields.dayOfMonth === '*' || rawFields.dayOfWeek === '*' || !strict,
104-
'Cannot use both dayOfMonth and dayOfWeek together in strict mode!',
105-
);
100+
if (!(rawFields.dayOfMonth === '*' || rawFields.dayOfWeek === '*' || !strict)) {
101+
throw new Error('Cannot use both dayOfMonth and dayOfWeek together in strict mode!');
102+
}
106103

107104
const second = CronExpressionParser.#parseField(
108105
CronUnit.Second,
@@ -151,11 +148,17 @@ export class CronExpressionParser {
151148
* @returns {RawCronFields} The raw fields.
152149
*/
153150
static #getRawFields(expression: string, strict: boolean): RawCronFields {
154-
assert(!strict || expression.length, 'Invalid cron expression');
151+
if (!(!strict || expression.length)) {
152+
throw new Error('Invalid cron expression');
153+
}
155154
expression = expression || '0 * * * * *';
156155
const atoms = expression.trim().split(/\s+/);
157-
assert(!strict || atoms.length === 6, 'Invalid cron expression, expected 6 fields');
158-
assert(atoms.length <= 6, 'Invalid cron expression, too many fields');
156+
if (!(!strict || atoms.length === 6)) {
157+
throw new Error('Invalid cron expression, expected 6 fields');
158+
}
159+
if (atoms.length > 6) {
160+
throw new Error('Invalid cron expression, too many fields');
161+
}
159162
const defaults = ['*', '*', '*', '*', '*', '0'];
160163
if (atoms.length < defaults.length) {
161164
atoms.unshift(...defaults.slice(atoms.length));
@@ -178,13 +181,17 @@ export class CronExpressionParser {
178181
value = value.replace(/[a-z]{3}/gi, (match) => {
179182
match = match.toLowerCase();
180183
const replacer = Months[match as keyof typeof Months] || DayOfWeek[match as keyof typeof DayOfWeek];
181-
assert(replacer != null, `Validation error, cannot resolve alias "${match}"`);
184+
if (!replacer) {
185+
throw new Error(`Validation error, cannot resolve alias "${match}"`);
186+
}
182187
return replacer.toString();
183188
});
184189
}
185190

186191
// Check for valid characters
187-
assert(constraints.validChars.test(value), `Invalid characters, got value: ${value}`);
192+
if (!constraints.validChars.test(value)) {
193+
throw new Error(`Invalid characters, got value: ${value}`);
194+
}
188195

189196
// Replace '*' and '?'
190197
value = value.replace(/[*?]/g, constraints.min + '-' + constraints.max);
@@ -200,35 +207,30 @@ export class CronExpressionParser {
200207
*/
201208
static #parseSequence(field: CronUnit, val: string, constraints: CronConstraints): (number | string)[] {
202209
const stack: (number | string)[] = [];
203-
204210
function handleResult(result: number | string | (number | string)[], constraints: CronConstraints) {
205211
if (Array.isArray(result)) {
206-
result.forEach((value) => {
207-
if (!CronExpressionParser.#isValidConstraintChar(constraints, value)) {
208-
const v = parseInt(value.toString(), 10);
209-
const isValid = v >= constraints.min && v <= constraints.max;
210-
assert(
211-
isValid,
212-
`Constraint error, got value ${value} expected range ${constraints.min}-${constraints.max}`,
213-
);
214-
stack.push(value);
215-
}
216-
});
212+
stack.push(...result);
217213
} else {
218214
if (CronExpressionParser.#isValidConstraintChar(constraints, result)) {
219215
stack.push(result);
220216
} else {
221217
const v = parseInt(result.toString(), 10);
222218
const isValid = v >= constraints.min && v <= constraints.max;
223-
assert(isValid, `Constraint error, got value ${result} expected range ${constraints.min}-${constraints.max}`);
219+
if (!isValid) {
220+
throw new Error(
221+
`Constraint error, got value ${result} expected range ${constraints.min}-${constraints.max}`,
222+
);
223+
}
224224
stack.push(field === CronUnit.DayOfWeek ? v % 7 : result);
225225
}
226226
}
227227
}
228228

229229
const atoms = val.split(',');
230230
atoms.forEach((atom) => {
231-
assert(atom.length > 0, 'Invalid list value format');
231+
if (!(atom.length > 0)) {
232+
throw new Error('Invalid list value format');
233+
}
232234
handleResult(CronExpressionParser.#parseRepeat(field, atom, constraints), constraints);
233235
});
234236
return stack;
@@ -242,9 +244,11 @@ export class CronExpressionParser {
242244
* @private
243245
* @returns {(number | string)[]} The parsed repeat.
244246
*/
245-
static #parseRepeat(field: CronUnit, val: string, constraints: CronConstraints): ParseRageResponse {
247+
static #parseRepeat(field: CronUnit, val: string, constraints: CronConstraints): ParseRangeResponse {
246248
const atoms = val.split('/');
247-
assert(atoms.length <= 2, `Invalid repeat: ${val}`);
249+
if (atoms.length > 2) {
250+
throw new Error(`Invalid repeat: ${val}`);
251+
}
248252
if (atoms.length === 2) {
249253
if (!isNaN(parseInt(atoms[0], 10))) {
250254
atoms[0] = `${atoms[0]}-${constraints.max}`;
@@ -266,8 +270,12 @@ export class CronExpressionParser {
266270
*/
267271
static #validateRange(min: number, max: number, constraints: CronConstraints): void {
268272
const isValid = !isNaN(min) && !isNaN(max) && min >= constraints.min && max <= constraints.max;
269-
assert(isValid, `Constraint error, got range ${min}-${max} expected range ${constraints.min}-${constraints.max}`);
270-
assert(min <= max, `Invalid range: ${min}-${max}, min(${min}) > max(${max})`);
273+
if (!isValid) {
274+
throw new Error(`Constraint error, got range ${min}-${max} expected range ${constraints.min}-${constraints.max}`);
275+
}
276+
if (min > max) {
277+
throw new Error(`Invalid range: ${min}-${max}, min(${min}) > max(${max})`);
278+
}
271279
}
272280

273281
/**
@@ -278,10 +286,9 @@ export class CronExpressionParser {
278286
* @throws {Error} Throws an error if the repeat interval is invalid.
279287
*/
280288
static #validateRepeatInterval(repeatInterval: number): void {
281-
assert(
282-
!isNaN(repeatInterval) && repeatInterval > 0,
283-
`Constraint error, cannot repeat at every ${repeatInterval} time.`,
284-
);
289+
if (!(!isNaN(repeatInterval) && repeatInterval > 0)) {
290+
throw new Error(`Constraint error, cannot repeat at every ${repeatInterval} time.`);
291+
}
285292
}
286293

287294
/**
@@ -320,7 +327,7 @@ export class CronExpressionParser {
320327
val: string,
321328
repeatInterval: number,
322329
constraints: CronConstraints,
323-
): ParseRageResponse {
330+
): ParseRangeResponse {
324331
const atoms: string[] = val.split('-');
325332
if (atoms.length <= 1) {
326333
return isNaN(+val) ? val : +val;
@@ -346,14 +353,14 @@ export class CronExpressionParser {
346353
}
347354
const nthValue = +atoms[atoms.length - 1];
348355
const matches = val.match(/([,-/])/);
349-
assert(
350-
matches === null,
351-
`Constraint error, invalid dayOfWeek \`#\` and \`${matches?.[0]}\` special characters are incompatible`,
352-
);
353-
assert(
354-
atoms.length <= 2 && !isNaN(nthValue) && nthValue >= 1 && nthValue <= 5,
355-
'Constraint error, invalid dayOfWeek occurrence number (#)',
356-
);
356+
if (matches !== null) {
357+
throw new Error(
358+
`Constraint error, invalid dayOfWeek \`#\` and \`${matches?.[0]}\` special characters are incompatible`,
359+
);
360+
}
361+
if (!(atoms.length <= 2 && !isNaN(nthValue) && nthValue >= 1 && nthValue <= 5)) {
362+
throw new Error('Constraint error, invalid dayOfWeek occurrence number (#)');
363+
}
357364
return { dayOfWeek: atoms[0], nthDayOfWeek: nthValue };
358365
}
359366

‎src/CronFieldCollection.ts

+37-15
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import assert from 'assert';
21
import {
32
CronSecond,
43
CronMinute,
@@ -140,18 +139,29 @@ export class CronFieldCollection {
140139
* console.log(cronFields.dayOfWeek.values); // [1, 2, 3, 4, 5]
141140
*/
142141
constructor({ second, minute, hour, dayOfMonth, month, dayOfWeek }: CronFields) {
143-
assert(second, 'Validation error, Field second is missing');
144-
assert(minute, 'Validation error, Field minute is missing');
145-
assert(hour, 'Validation error, Field hour is missing');
146-
assert(dayOfMonth, 'Validation error, Field dayOfMonth is missing');
147-
assert(month, 'Validation error, Field month is missing');
148-
assert(dayOfWeek, 'Validation error, Field dayOfWeek is missing');
142+
if (!second) {
143+
throw new Error('Validation error, Field second is missing');
144+
}
145+
if (!minute) {
146+
throw new Error('Validation error, Field minute is missing');
147+
}
148+
if (!hour) {
149+
throw new Error('Validation error, Field hour is missing');
150+
}
151+
if (!dayOfMonth) {
152+
throw new Error('Validation error, Field dayOfMonth is missing');
153+
}
154+
if (!month) {
155+
throw new Error('Validation error, Field month is missing');
156+
}
157+
if (!dayOfWeek) {
158+
throw new Error('Validation error, Field dayOfWeek is missing');
159+
}
149160

150161
if (month.values.length === 1) {
151-
assert(
152-
parseInt(dayOfMonth.values[0] as string, 10) <= CronMonth.daysInMonth[month.values[0] - 1],
153-
'Invalid explicit day of month definition',
154-
);
162+
if (!(parseInt(dayOfMonth.values[0] as string, 10) <= CronMonth.daysInMonth[month.values[0] - 1])) {
163+
throw new Error('Invalid explicit day of month definition');
164+
}
155165
}
156166

157167
this.#second = second;
@@ -332,14 +342,26 @@ export class CronFieldCollection {
332342
}
333343

334344
const multiplier = range.start === 0 ? range.count - 1 : range.count;
335-
assert(step, 'Unexpected range step');
336-
assert(range.end, 'Unexpected range end');
345+
/* istanbul ignore if */
346+
if (!step) {
347+
throw new Error('Unexpected range step');
348+
}
349+
/* istanbul ignore if */
350+
if (!range.end) {
351+
throw new Error('Unexpected range end');
352+
}
337353
if (step * multiplier > range.end) {
338354
const mapFn = (_: number, index: number) => {
339-
assert(typeof range.start === 'number', 'Unexpected range start');
355+
/* istanbul ignore if */
356+
if (typeof range.start !== 'number') {
357+
throw new Error('Unexpected range start');
358+
}
340359
return index % step === 0 ? range.start + index : null;
341360
};
342-
assert(typeof range.start === 'number', 'Unexpected range start');
361+
/* istanbul ignore if */
362+
if (typeof range.start !== 'number') {
363+
throw new Error('Unexpected range start');
364+
}
343365
const seed = { length: range.end - range.start + 1 };
344366
return Array.from(seed, mapFn)
345367
.filter((value) => value !== null)

‎src/fields/CronField.ts

+14-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import assert from 'assert';
21
import { CronChars, CronConstraints, CronFieldType, CronMax, CronMin } from './types';
32

43
export type SerializedCronField = {
@@ -64,8 +63,12 @@ export abstract class CronField {
6463
values: (number | string)[],
6564
/* istanbul ignore next - we always pass a value */ wildcard = false,
6665
) {
67-
assert(Array.isArray(values), `${this.constructor.name} Validation error, values is not an array`);
68-
assert(values.length > 0, `${this.constructor.name} Validation error, values contains no values`);
66+
if (!Array.isArray(values)) {
67+
throw new Error(`${this.constructor.name} Validation error, values is not an array`);
68+
}
69+
if (!(values.length > 0)) {
70+
throw new Error(`${this.constructor.name} Validation error, values contains no values`);
71+
}
6972
this.#values = values.sort(CronField.sorter);
7073
this.#wildcard = wildcard;
7174
}
@@ -153,12 +156,15 @@ export abstract class CronField {
153156
return typeof value === 'number' ? value >= this.min && value <= this.max : this.chars.some(charTest(value));
154157
};
155158
const isValidRange = this.#values.every(rangeTest);
156-
assert(
157-
isValidRange,
158-
`${this.constructor.name} Validation error, got value ${badValue} expected range ${this.min}-${this.max}${charsString}`,
159-
);
159+
if (!isValidRange) {
160+
throw new Error(
161+
`${this.constructor.name} Validation error, got value ${badValue} expected range ${this.min}-${this.max}${charsString}`,
162+
);
163+
}
160164
// check for duplicate value in this.#values array
161165
const duplicate = this.#values.find((value, index) => this.#values.indexOf(value) !== index);
162-
assert(!duplicate, `${this.constructor.name} Validation error, duplicate values found: ${duplicate}`);
166+
if (duplicate) {
167+
throw new Error(`${this.constructor.name} Validation error, duplicate values found: ${duplicate}`);
168+
}
163169
}
164170
}

‎src/fields/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export type CronFieldType = SixtyRange[] | HourRange[] | DayOfMonthRange[] | Mon
1414
export type CronChars = 'L' | 'W';
1515
export type CronMin = 0 | 1;
1616
export type CronMax = 7 | 12 | 23 | 31 | 59;
17-
export type ParseRageResponse = number[] | string[] | number | string;
17+
export type ParseRangeResponse = number[] | string[] | number | string;
1818

1919
export type CronConstraints = {
2020
min: CronMin;

‎tests/CronExpressionParser.test.ts

+14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ describe('CronExpressionParser', () => {
1717
);
1818
});
1919

20+
test('range order is invalid', () => {
21+
expect(() => CronExpressionParser.parse('30-20 * * * * *')).toThrow('Invalid range: 30-20, min(30) > max(20)');
22+
});
23+
2024
test('invalid range', () => {
2125
expect(() => CronExpressionParser.parse('- * * * * *')).toThrow(
2226
'Constraint error, got range NaN-NaN expected range 0-59',
@@ -153,10 +157,19 @@ describe('CronExpressionParser', () => {
153157
}
154158
});
155159

160+
test('both dayOfMonth and dayOfWeek used together in strict mode', () => {
161+
const expression = '0 0 12 1-31 * 1';
162+
expect(() => CronExpressionParser.parse(expression, { strict: true })).toThrow();
163+
});
164+
156165
test('missing fields when in strict mode', () => {
157166
const expression = '20 15 * *';
158167
expect(() => CronExpressionParser.parse(expression, { strict: true })).toThrow();
159168
});
169+
170+
test('empty expression in strict mode', () => {
171+
expect(() => CronExpressionParser.parse('', { strict: true })).toThrow();
172+
});
160173
});
161174

162175
describe('take multiple dates', () => {
@@ -1007,6 +1020,7 @@ describe('CronExpressionParser', () => {
10071020
expect(interval.includesDate(goodDate)).toBe(true);
10081021
expect(interval.includesDate(badDateBefore)).toBe(false);
10091022
expect(interval.includesDate(badDateAfter)).toBe(false);
1023+
expect(() => interval.includesDate(new Date('invalid'))).toThrow();
10101024
});
10111025

10121026
test('correctly handle 0 12 1-31 * 1 (#284)', () => {

‎tests/CronField.test.ts

+12-164
Original file line numberDiff line numberDiff line change
@@ -13,176 +13,24 @@ import {
1313
SixtyRange,
1414
} from '../src';
1515

16-
describe('CronFields', () => {
17-
// an array of numbers from 0 to 59
18-
const sixtyRange: number[] = Array.from(Array(60).keys());
19-
test('stringify() and debug() methods', () => {
20-
const expected = {
21-
second: {
22-
wildcard: false,
23-
values: sixtyRange,
24-
},
25-
minute: {
26-
wildcard: false,
27-
values: [0, 30],
28-
},
29-
hour: {
30-
wildcard: false,
31-
values: [9, 11, 13, 15, 17],
32-
},
33-
dayOfMonth: {
34-
wildcard: false,
35-
values: [1, 15],
36-
},
37-
month: {
38-
wildcard: false,
39-
values: [1, 3, 5, 7, 9, 11],
40-
},
41-
dayOfWeek: {
42-
wildcard: false,
43-
values: [1, 2, 3, 4, 5],
44-
},
45-
};
46-
const cronFields = new CronFieldCollection({
47-
second: new CronSecond(<SixtyRange[]>expected.second.values),
48-
minute: new CronMinute(<SixtyRange[]>expected.minute.values),
49-
hour: new CronHour(<HourRange[]>expected.hour.values),
50-
dayOfMonth: new CronDayOfMonth(<DayOfMonthRange[]>expected.dayOfMonth.values),
51-
month: new CronMonth(<MonthRange[]>expected.month.values),
52-
dayOfWeek: new CronDayOfWeek(<DayOfWeekRange[]>expected.dayOfWeek.values),
16+
describe('CronField', () => {
17+
describe('validate', () => {
18+
test('should throw error when values is not an array', () => {
19+
expect(() => new CronSecond(0 as any)).toThrow('CronSecond Validation error, values is not an array');
5320
});
5421

55-
expect(cronFields.stringify()).toEqual('0,30 9-17/2 1,15 */2 1-5');
56-
expect(cronFields.stringify(true)).toEqual('* 0,30 9-17/2 1,15 */2 1-5');
57-
expect(cronFields.serialize()).toEqual(expected);
58-
});
59-
60-
test('getters', () => {
61-
const cronFields = new CronFieldCollection({
62-
second: new CronSecond([0]),
63-
minute: new CronMinute([0, 30]),
64-
hour: new CronHour([9]),
65-
dayOfMonth: new CronDayOfMonth([15]),
66-
month: new CronMonth([1]),
67-
dayOfWeek: new CronDayOfWeek([1, 2, 3, 4, 5]),
68-
});
69-
70-
expect(cronFields.second).toBeInstanceOf(CronSecond);
71-
expect(cronFields.minute).toBeInstanceOf(CronMinute);
72-
expect(cronFields.hour).toBeInstanceOf(CronHour);
73-
expect(cronFields.dayOfMonth).toBeInstanceOf(CronDayOfMonth);
74-
expect(cronFields.month).toBeInstanceOf(CronMonth);
75-
expect(cronFields.dayOfWeek).toBeInstanceOf(CronDayOfWeek);
76-
});
77-
78-
test('serialize', () => {
79-
const cronFields = new CronFieldCollection({
80-
second: new CronSecond([0]),
81-
minute: new CronMinute([0, 30]),
82-
hour: new CronHour([9]),
83-
dayOfMonth: new CronDayOfMonth([15]),
84-
month: new CronMonth([1]),
85-
dayOfWeek: new CronDayOfWeek([1, 2, 3, 4, 5]),
86-
});
87-
88-
expect(cronFields.dayOfMonth.serialize()).toEqual({
89-
values: [15],
90-
wildcard: false,
91-
});
92-
});
93-
94-
describe('CronFieldCollection.compactField', () => {
95-
test('empty array', () => {
96-
const result = CronFieldCollection.compactField([]);
97-
expect(result).toEqual([]);
98-
});
99-
100-
test('single element array', () => {
101-
const result = CronFieldCollection.compactField([1]);
102-
expect(result).toEqual([{ start: 1, count: 1 }]);
103-
});
104-
105-
test('2 elements array', () => {
106-
const result = CronFieldCollection.compactField([1, 2]);
107-
expect(result).toEqual([
108-
{ start: 1, count: 1 },
109-
{ start: 2, count: 1 },
110-
]);
111-
});
112-
113-
test('2 elements array big step', () => {
114-
const result = CronFieldCollection.compactField([1, 5]);
115-
expect(result).toEqual([
116-
{ start: 1, count: 1 },
117-
{ start: 5, count: 1 },
118-
]);
119-
});
120-
121-
test('3 elements array 1 step', () => {
122-
const result = CronFieldCollection.compactField([1, 2, 3]);
123-
expect(result).toEqual([{ start: 1, end: 3, count: 3, step: 1 }]);
124-
});
125-
126-
test('3 elements array 1 step, dangling extra at end', () => {
127-
const result = CronFieldCollection.compactField([1, 2, 3, 5]);
128-
expect(result).toEqual([
129-
{ start: 1, end: 3, count: 3, step: 1 },
130-
{ start: 5, count: 1 },
131-
]);
132-
});
133-
134-
test('3 elements array 1 step, dangling extra at end and beginning', () => {
135-
const result = CronFieldCollection.compactField([1, 4, 5, 6, 9]);
136-
expect(result).toEqual([
137-
{ start: 1, count: 1 },
138-
{ start: 4, end: 6, count: 3, step: 1 },
139-
{ start: 9, count: 1 },
140-
]);
141-
});
142-
143-
test('2 ranges with dangling in the middle', () => {
144-
const result = CronFieldCollection.compactField([1, 2, 3, 6, 9, 11, 13]);
145-
expect(result).toEqual([
146-
{ start: 1, end: 3, count: 3, step: 1 },
147-
{ start: 6, count: 1 },
148-
{ start: 9, end: 13, count: 3, step: 2 },
149-
]);
150-
});
151-
152-
test('with chars', () => {
153-
const result = CronFieldCollection.compactField(['L', 'W']);
154-
expect(result).toEqual([
155-
{ start: 'L', count: 1 },
156-
{ start: 'W', count: 1 },
157-
]);
158-
});
159-
160-
test('with chars and range', () => {
161-
const result = CronFieldCollection.compactField([1, 'L', 'W']);
162-
expect(result).toEqual([
163-
{ start: 1, count: 1 },
164-
{ start: 'L', count: 1 },
165-
{ start: 'W', count: 1 },
166-
]);
22+
test('should throw error when values contains no values', () => {
23+
expect(() => new CronSecond([])).toThrow('CronSecond Validation error, values contains no values');
16724
});
16825

169-
test('with chars and range (v2)', () => {
170-
const result = CronFieldCollection.compactField([1, 2, 'L', 'W']);
171-
expect(result).toEqual([
172-
{ start: 1, count: 1 },
173-
{ start: 2, count: 1 },
174-
{ start: 'L', count: 1 },
175-
{ start: 'W', count: 1 },
176-
]);
26+
test('should throw an error when input value is out of the defined range', () => {
27+
expect(() => new CronSecond([0, 100] as SixtyRange[])).toThrow(
28+
'CronSecond Validation error, got value 100 expected range 0-59',
29+
);
17730
});
17831

179-
test('with chars and range (v3)', () => {
180-
const result = CronFieldCollection.compactField([1, 2, 3, 'L', 'W']);
181-
expect(result).toEqual([
182-
{ start: 1, end: 3, count: 3, step: 1 },
183-
{ start: 'L', count: 1 },
184-
{ start: 'W', count: 1 },
185-
]);
32+
test('should throw an error when duplicate value is provided as a range', () => {
33+
expect(() => new CronSecond([0, 59, 59])).toThrow('CronSecond Validation error, duplicate values found: 59');
18634
});
18735
});
18836
});

‎tests/CronFieldCollection.test.ts

+281-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,287 @@
11
import { CronFieldCollection } from '../src/CronFieldCollection';
2-
import { CronSecond, CronMinute, CronHour, CronDayOfMonth, CronMonth, CronDayOfWeek } from '../src/fields';
2+
import {
3+
CronSecond,
4+
CronMinute,
5+
CronHour,
6+
CronDayOfMonth,
7+
CronMonth,
8+
CronDayOfWeek,
9+
DayOfWeekRange,
10+
MonthRange,
11+
DayOfMonthRange,
12+
HourRange,
13+
SixtyRange,
14+
} from '../src/fields';
315

416
describe('CronFieldCollection', () => {
17+
describe('getters', () => {
18+
test('should access fields and assert correct types', () => {
19+
const cronFields = new CronFieldCollection({
20+
second: new CronSecond([0]),
21+
minute: new CronMinute([0, 30]),
22+
hour: new CronHour([9]),
23+
dayOfMonth: new CronDayOfMonth([15]),
24+
month: new CronMonth([1]),
25+
dayOfWeek: new CronDayOfWeek([1, 2, 3, 4, 5]),
26+
});
27+
28+
expect(cronFields.second).toBeInstanceOf(CronSecond);
29+
expect(cronFields.minute).toBeInstanceOf(CronMinute);
30+
expect(cronFields.hour).toBeInstanceOf(CronHour);
31+
expect(cronFields.dayOfMonth).toBeInstanceOf(CronDayOfMonth);
32+
expect(cronFields.month).toBeInstanceOf(CronMonth);
33+
expect(cronFields.dayOfWeek).toBeInstanceOf(CronDayOfWeek);
34+
});
35+
});
36+
37+
describe('serialize', () => {
38+
test('should serialize collection', () => {
39+
const expected = {
40+
second: {
41+
wildcard: false,
42+
values: Array.from(Array(60).keys()),
43+
},
44+
minute: {
45+
wildcard: false,
46+
values: [0, 30],
47+
},
48+
hour: {
49+
wildcard: false,
50+
values: [9, 11, 13, 15, 17],
51+
},
52+
dayOfMonth: {
53+
wildcard: false,
54+
values: [1, 15],
55+
},
56+
month: {
57+
wildcard: false,
58+
values: [1, 3, 5, 7, 9, 11],
59+
},
60+
dayOfWeek: {
61+
wildcard: false,
62+
values: [1, 2, 3, 4, 5],
63+
},
64+
};
65+
const cronFields = new CronFieldCollection({
66+
second: new CronSecond(<SixtyRange[]>expected.second.values),
67+
minute: new CronMinute(<SixtyRange[]>expected.minute.values),
68+
hour: new CronHour(<HourRange[]>expected.hour.values),
69+
dayOfMonth: new CronDayOfMonth(<DayOfMonthRange[]>expected.dayOfMonth.values),
70+
month: new CronMonth(<MonthRange[]>expected.month.values),
71+
dayOfWeek: new CronDayOfWeek(<DayOfWeekRange[]>expected.dayOfWeek.values),
72+
});
73+
74+
expect(cronFields.stringify()).toEqual('0,30 9-17/2 1,15 */2 1-5');
75+
expect(cronFields.stringify(true)).toEqual('* 0,30 9-17/2 1,15 */2 1-5');
76+
expect(cronFields.serialize()).toEqual(expected);
77+
});
78+
79+
test('should serialize fields to an object', () => {
80+
const cronFields = new CronFieldCollection({
81+
second: new CronSecond([0]),
82+
minute: new CronMinute([0, 30]),
83+
hour: new CronHour([9]),
84+
dayOfMonth: new CronDayOfMonth([15]),
85+
month: new CronMonth([1]),
86+
dayOfWeek: new CronDayOfWeek([1, 2, 3, 4, 5]),
87+
});
88+
89+
expect(cronFields.dayOfMonth.serialize()).toEqual({
90+
values: [15],
91+
wildcard: false,
92+
});
93+
});
94+
});
95+
96+
describe('constructor validation', () => {
97+
test('should throw error when second field is missing', () => {
98+
expect(
99+
() =>
100+
new CronFieldCollection({
101+
minute: new CronMinute([0]),
102+
hour: new CronHour([12]),
103+
dayOfMonth: new CronDayOfMonth([1]),
104+
month: new CronMonth([1]),
105+
dayOfWeek: new CronDayOfWeek([1]),
106+
} as any),
107+
).toThrow('Validation error, Field second is missing');
108+
});
109+
110+
test('should throw error when minute field is missing', () => {
111+
expect(
112+
() =>
113+
new CronFieldCollection({
114+
second: new CronSecond([0]),
115+
hour: new CronHour([12]),
116+
dayOfMonth: new CronDayOfMonth([1]),
117+
month: new CronMonth([1]),
118+
dayOfWeek: new CronDayOfWeek([1]),
119+
} as any),
120+
).toThrow('Validation error, Field minute is missing');
121+
});
122+
123+
test('should throw error when hour field is missing', () => {
124+
expect(
125+
() =>
126+
new CronFieldCollection({
127+
second: new CronSecond([0]),
128+
minute: new CronMinute([0]),
129+
dayOfMonth: new CronDayOfMonth([1]),
130+
month: new CronMonth([1]),
131+
dayOfWeek: new CronDayOfWeek([1]),
132+
} as any),
133+
).toThrow('Validation error, Field hour is missing');
134+
});
135+
136+
test('should throw error when dayOfMonth field is missing', () => {
137+
expect(
138+
() =>
139+
new CronFieldCollection({
140+
second: new CronSecond([0]),
141+
minute: new CronMinute([0]),
142+
hour: new CronHour([12]),
143+
month: new CronMonth([1]),
144+
dayOfWeek: new CronDayOfWeek([1]),
145+
} as any),
146+
).toThrow('Validation error, Field dayOfMonth is missing');
147+
});
148+
149+
test('should throw error when month field is missing', () => {
150+
expect(
151+
() =>
152+
new CronFieldCollection({
153+
second: new CronSecond([0]),
154+
minute: new CronMinute([0]),
155+
hour: new CronHour([12]),
156+
dayOfMonth: new CronDayOfMonth([1]),
157+
dayOfWeek: new CronDayOfWeek([1]),
158+
} as any),
159+
).toThrow('Validation error, Field month is missing');
160+
});
161+
162+
test('should throw error when dayOfWeek field is missing', () => {
163+
expect(
164+
() =>
165+
new CronFieldCollection({
166+
second: new CronSecond([0]),
167+
minute: new CronMinute([0]),
168+
hour: new CronHour([12]),
169+
dayOfMonth: new CronDayOfMonth([1]),
170+
month: new CronMonth([1]),
171+
} as any),
172+
).toThrow('Validation error, Field dayOfWeek is missing');
173+
});
174+
175+
test('should throw error for invalid day of month definition', () => {
176+
expect(
177+
() =>
178+
new CronFieldCollection({
179+
second: new CronSecond([0]),
180+
minute: new CronMinute([0]),
181+
hour: new CronHour([12]),
182+
dayOfMonth: new CronDayOfMonth([31]), // February only has 28/29 days
183+
month: new CronMonth([2]), // February
184+
dayOfWeek: new CronDayOfWeek([1]),
185+
}),
186+
).toThrow('Invalid explicit day of month definition');
187+
});
188+
});
189+
190+
describe('CronFieldCollection.compactField', () => {
191+
test('empty array', () => {
192+
const result = CronFieldCollection.compactField([]);
193+
expect(result).toEqual([]);
194+
});
195+
196+
test('single element array', () => {
197+
const result = CronFieldCollection.compactField([1]);
198+
expect(result).toEqual([{ start: 1, count: 1 }]);
199+
});
200+
201+
test('2 elements array', () => {
202+
const result = CronFieldCollection.compactField([1, 2]);
203+
expect(result).toEqual([
204+
{ start: 1, count: 1 },
205+
{ start: 2, count: 1 },
206+
]);
207+
});
208+
209+
test('2 elements array big step', () => {
210+
const result = CronFieldCollection.compactField([1, 5]);
211+
expect(result).toEqual([
212+
{ start: 1, count: 1 },
213+
{ start: 5, count: 1 },
214+
]);
215+
});
216+
217+
test('3 elements array 1 step', () => {
218+
const result = CronFieldCollection.compactField([1, 2, 3]);
219+
expect(result).toEqual([{ start: 1, end: 3, count: 3, step: 1 }]);
220+
});
221+
222+
test('3 elements array 1 step, dangling extra at end', () => {
223+
const result = CronFieldCollection.compactField([1, 2, 3, 5]);
224+
expect(result).toEqual([
225+
{ start: 1, end: 3, count: 3, step: 1 },
226+
{ start: 5, count: 1 },
227+
]);
228+
});
229+
230+
test('3 elements array 1 step, dangling extra at end and beginning', () => {
231+
const result = CronFieldCollection.compactField([1, 4, 5, 6, 9]);
232+
expect(result).toEqual([
233+
{ start: 1, count: 1 },
234+
{ start: 4, end: 6, count: 3, step: 1 },
235+
{ start: 9, count: 1 },
236+
]);
237+
});
238+
239+
test('2 ranges with dangling in the middle', () => {
240+
const result = CronFieldCollection.compactField([1, 2, 3, 6, 9, 11, 13]);
241+
expect(result).toEqual([
242+
{ start: 1, end: 3, count: 3, step: 1 },
243+
{ start: 6, count: 1 },
244+
{ start: 9, end: 13, count: 3, step: 2 },
245+
]);
246+
});
247+
248+
test('with chars', () => {
249+
const result = CronFieldCollection.compactField(['L', 'W']);
250+
expect(result).toEqual([
251+
{ start: 'L', count: 1 },
252+
{ start: 'W', count: 1 },
253+
]);
254+
});
255+
256+
test('with chars and range', () => {
257+
const result = CronFieldCollection.compactField([1, 'L', 'W']);
258+
expect(result).toEqual([
259+
{ start: 1, count: 1 },
260+
{ start: 'L', count: 1 },
261+
{ start: 'W', count: 1 },
262+
]);
263+
});
264+
265+
test('with chars and range (v2)', () => {
266+
const result = CronFieldCollection.compactField([1, 2, 'L', 'W']);
267+
expect(result).toEqual([
268+
{ start: 1, count: 1 },
269+
{ start: 2, count: 1 },
270+
{ start: 'L', count: 1 },
271+
{ start: 'W', count: 1 },
272+
]);
273+
});
274+
275+
test('with chars and range (v3)', () => {
276+
const result = CronFieldCollection.compactField([1, 2, 3, 'L', 'W']);
277+
expect(result).toEqual([
278+
{ start: 1, end: 3, count: 3, step: 1 },
279+
{ start: 'L', count: 1 },
280+
{ start: 'W', count: 1 },
281+
]);
282+
});
283+
});
284+
5285
describe('from', () => {
6286
let base: CronFieldCollection;
7287

0 commit comments

Comments
 (0)
Please sign in to comment.