import { expect, test } from "vitest" import { Cron } from "#Source/cron/index.ts" const internalCreateFutureMinute = (minutesAhead: number): Date => { const date = new Date(Date.now() + minutesAhead * 60_000) date.setSeconds(0, 0) return date } const internalCreatePastMinute = (minutesAgo: number): Date => { const date = new Date(Date.now() - minutesAgo * 60_000) date.setSeconds(0, 0) return date } test("Cron creates schedule models from both cron expressions and one-shot dates", () => { const expressionCron = new Cron({ pattern: "*/5 * * * *" }) const singleShotCron = new Cron({ pattern: internalCreateFutureMinute(5) }) expect(expressionCron).toBeInstanceOf(Cron) expect(singleShotCron).toBeInstanceOf(Cron) }) test("Cron.prevRun returns undefined before a one-shot schedule is reached", () => { const scheduledAt = internalCreateFutureMinute(5) const cron = new Cron({ pattern: scheduledAt }) expect(cron.prevRun()).toBeUndefined() }) test("Cron.prevRuns returns historical one-shot runs that have already happened", () => { const scheduledAt = internalCreatePastMinute(5) const cron = new Cron({ pattern: scheduledAt }) const previousRuns = cron.prevRuns(2) expect(previousRuns).toHaveLength(1) expect(previousRuns[0]?.getTime()).toBe(scheduledAt.getTime()) }) test("Cron.currentRun stays undefined when the model is only queried and not executing a task", () => { const cron = new Cron({ pattern: internalCreateFutureMinute(5) }) expect(cron.currentRun()).toBeUndefined() }) test("Cron.nextRun returns the next upcoming one-shot run", () => { const scheduledAt = internalCreateFutureMinute(5) const cron = new Cron({ pattern: scheduledAt }) expect(cron.nextRun()?.getTime()).toBe(scheduledAt.getTime()) }) test("Cron.nextRuns returns upcoming expression runs in ascending order", () => { const cron = new Cron({ pattern: "*/5 * * * *" }) const nextRuns = cron.nextRuns(2) expect(nextRuns).toHaveLength(2) expect(nextRuns[0]?.getTime()).toBeLessThan(nextRuns[1]?.getTime() ?? 0) expect(cron.match(nextRuns[0]!)).toBe(true) expect(cron.match(nextRuns[1]!)).toBe(true) }) test("Cron.match reflects cron-expression semantics including domAndDow behavior", () => { const yearlyCron = new Cron({ pattern: "0 0 1 1 *" }) const andCron = new Cron({ pattern: "0 0 1 * MON", domAndDow: true }) const orCron = new Cron({ pattern: "0 0 1 * MON", domAndDow: false }) const yearlyHit = new Date(2_026, 0, 1, 0, 0, 0, 0) const yearlyMiss = new Date(2_026, 0, 1, 0, 1, 0, 0) const mondayFirst = new Date(2_026, 5, 1, 0, 0, 0, 0) const mondayNotFirst = new Date(2_026, 5, 8, 0, 0, 0, 0) const firstNotMonday = new Date(2_026, 8, 1, 0, 0, 0, 0) expect(yearlyCron.match(yearlyHit)).toBe(true) expect(yearlyCron.match(yearlyMiss)).toBe(false) expect(andCron.match(mondayFirst)).toBe(true) expect(andCron.match(mondayNotFirst)).toBe(false) expect(andCron.match(firstNotMonday)).toBe(false) expect(orCron.match(mondayFirst)).toBe(true) expect(orCron.match(mondayNotFirst)).toBe(true) expect(orCron.match(firstNotMonday)).toBe(true) })