Files
insforge--insforge/backend/tests/unit/schedule.service.sub-minute-cadence.test.ts
wehub-resource-sync 3a28426bf4
Lint and Format Check / lint-and-format (push) Failing after 0s
Check Migrations / Check for duplicate migration numbers (push) Failing after 1s
CI Pre-merge Check / CI Pre-merge Check (push) Failing after 2m17s
chore: import upstream snapshot with attribution
2026-07-13 12:23:40 +08:00

131 lines
4.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect } from 'vitest';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const currentDir = path.dirname(fileURLToPath(import.meta.url));
const servicePath = path.resolve(currentDir, '../../src/services/schedules/schedule.service.ts');
const sharedSchemaPath = path.resolve(
currentDir,
'../../../packages/shared-schemas/src/schedules-api.schema.ts'
);
describe('Sub-minute cadence support: validator', () => {
const serviceSrc = fs.readFileSync(servicePath, 'utf8');
const sharedSrc = fs.readFileSync(sharedSchemaPath, 'utf8');
it('service defines an INTERVAL_RE that only accepts seconds', () => {
// Must declare an INTERVAL_RE (or equivalent) constant
expect(serviceSrc).toMatch(/INTERVAL_RE\s*=\s*\//);
// The regex must mention seconds and must NOT include minutes/hours alternations
expect(serviceSrc).toMatch(/INTERVAL_RE[\s\S]*?seconds\?/);
expect(serviceSrc).not.toMatch(/INTERVAL_RE[\s\S]*?minutes\?/);
expect(serviceSrc).not.toMatch(/INTERVAL_RE[\s\S]*?hours\?/);
// And be case-insensitive (i flag)
expect(serviceSrc).toMatch(/INTERVAL_RE[\s\S]*?\/i\b/);
});
it('service validateCronExpression takes the interval branch first', () => {
// The validator should match the interval regex and early-return before falling to 5-field check.
// Sequence: validateCronExpression(...) -> ... -> INTERVAL_RE -> ... -> return; -> ... -> fields.length
expect(serviceSrc).toMatch(
/validateCronExpression\b[\s\S]+?INTERVAL_RE[\s\S]+?return\s*;[\s\S]+?fields\.length/
);
});
it('service validator error message mentions both 5-field cron and interval examples', () => {
expect(serviceSrc).toMatch(/seconds/i);
expect(serviceSrc).toMatch(/\* \* \* \* \*|5 fields/);
});
it('service computeNextRunForSchedule has an interval branch using INTERVAL_RE', () => {
expect(serviceSrc).toMatch(/computeNextRunForSchedule\b[\s\S]+?INTERVAL_RE[\s\S]+?getTime\(\)/);
});
it('service interval next-run uses the seconds multiplier (1_000 ms)', () => {
expect(serviceSrc).toMatch(/\b1[_]?000\b/);
});
it('shared-schemas cron validator accepts only sub-minute seconds form', () => {
// The refine() must accept "30 seconds" but reject minutes/hours.
expect(sharedSrc).toMatch(/seconds\?/i);
expect(sharedSrc).not.toMatch(/minutes\?\s*\|/i);
expect(sharedSrc).not.toMatch(/\|\s*hours\?/i);
});
it('shared-schemas error message mentions sub-minute / 159 seconds', () => {
expect(sharedSrc).toMatch(/seconds|interval/i);
expect(sharedSrc).toMatch(/sub-minute|1[\s\S]{0,3}59/i);
});
});
// Behavioural sanity checks on the regex shape. These don't import the service
// (which needs DatabaseManager); instead, re-derive the same pattern and assert
// it accepts the inputs we care about and rejects the ones we don't.
//
// The service-side regex stays permissive on digits (\d+) so the service can
// throw a precise "159 seconds" error for out-of-range values; the
// shared-schemas regex bounds the digits inline because it only returns a
// boolean.
describe('Sub-minute cadence regex behaviour (service)', () => {
const SERVICE_INTERVAL_RE = /^\s*(\d+)\s+seconds?\s*$/i;
it.each([
['1 second', '1'],
['2 seconds', '2'],
['30 seconds', '30'],
['59 seconds', '59'],
[' 30 SECONDS ', '30'],
])('accepts %j as sub-minute interval', (input, n) => {
const m = input.match(SERVICE_INTERVAL_RE);
expect(m).not.toBeNull();
expect(m?.[1]).toBe(n);
});
it.each([
'* * * * *',
'*/5 * * * *',
'0 9 * * 1-5',
'',
'foo bar',
'2',
'2 days',
'2 weeks',
'2.5 seconds',
'-1 seconds',
'1 minute',
'5 minutes',
'1 hour',
'12 hours',
'* * * * * *',
])('rejects %j', (input) => {
expect(input.match(SERVICE_INTERVAL_RE)).toBeNull();
});
});
describe('Sub-minute cadence regex behaviour (shared-schemas, bounded 159)', () => {
const SCHEMA_INTERVAL_RE = /^\s*([1-9]|[1-5]\d)\s+seconds?\s*$/i;
it.each(['1 second', '2 seconds', '30 seconds', '59 seconds', ' 30 SECONDS '])(
'accepts %j',
(input) => {
expect(input.match(SCHEMA_INTERVAL_RE)).not.toBeNull();
}
);
it.each([
'0 seconds',
'60 seconds',
'90 seconds',
'120 seconds',
'1 minute',
'5 minutes',
'1 hour',
'12 hours',
'2.5 seconds',
'-1 seconds',
])('rejects %j', (input) => {
expect(input.match(SCHEMA_INTERVAL_RE)).toBeNull();
});
});