- Docker infrastructure (Dockerfile, docker-compose.yml, genieacs-sim) - AdonisJS v7 app with Inertia.js + Vue 3 - MongoDB integration - CWMP/TR-069 state machine engine - Configuration engine with compliance tracking - Branding: ADAM ACS (favicon, navbar, locales) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
193 lines
6.3 KiB
TypeScript
193 lines
6.3 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import fc from 'fast-check'
|
|
import { isWithinSchedule } from '../../app/utils/schedule_window.js'
|
|
import type { ScheduleWindow } from '../../app/domain/configuration/configuration.js'
|
|
|
|
/**
|
|
* Property 12: Schedule window evaluation
|
|
* Validates: Requirements 8.1, 8.3
|
|
*
|
|
* Verifies midnight-spanning logic and day-of-week filtering using
|
|
* randomly generated schedules and timestamps.
|
|
*/
|
|
describe('Property 12: Schedule window evaluation', () => {
|
|
// --- Arbitraries ---
|
|
|
|
/** Generate a valid hour (0-23) */
|
|
const arbHour = fc.integer({ min: 0, max: 23 })
|
|
|
|
/** Generate a valid day-of-week subset (0=Sunday ... 6=Saturday) */
|
|
const arbDaysOfWeek = fc.subarray([0, 1, 2, 3, 4, 5, 6], { minLength: 1 })
|
|
|
|
/** Generate a Date within a reasonable range */
|
|
const arbDate = fc.date({
|
|
min: new Date('2020-01-01T00:00:00Z'),
|
|
max: new Date('2030-12-31T23:59:59Z'),
|
|
})
|
|
|
|
/** Generate a non-midnight-spanning schedule (startHour < endHour) */
|
|
const arbNormalSchedule: fc.Arbitrary<ScheduleWindow> = fc
|
|
.tuple(arbHour, arbHour, arbDaysOfWeek)
|
|
.filter(([s, e]) => s < e)
|
|
.map(([startHour, endHour, daysOfWeek]) => ({
|
|
startHour,
|
|
endHour,
|
|
daysOfWeek,
|
|
timezone: 'UTC',
|
|
}))
|
|
|
|
/** Generate a midnight-spanning schedule (startHour > endHour) */
|
|
const arbMidnightSchedule: fc.Arbitrary<ScheduleWindow> = fc
|
|
.tuple(arbHour, arbHour, arbDaysOfWeek)
|
|
.filter(([s, e]) => s > e)
|
|
.map(([startHour, endHour, daysOfWeek]) => ({
|
|
startHour,
|
|
endHour,
|
|
daysOfWeek,
|
|
timezone: 'UTC',
|
|
}))
|
|
|
|
/** Generate any valid schedule (startHour !== endHour) */
|
|
const arbSchedule: fc.Arbitrary<ScheduleWindow> = fc
|
|
.tuple(arbHour, arbHour, arbDaysOfWeek)
|
|
.filter(([s, e]) => s !== e)
|
|
.map(([startHour, endHour, daysOfWeek]) => ({
|
|
startHour,
|
|
endHour,
|
|
daysOfWeek,
|
|
timezone: 'UTC',
|
|
}))
|
|
|
|
/**
|
|
* Helper: create a UTC Date at a specific hour on a specific day-of-week.
|
|
* Uses a known reference week (2025-01-05 is Sunday).
|
|
*/
|
|
function makeDateAtHourAndDay(hour: number, dayOfWeek: number): Date {
|
|
// 2025-01-05 is a Sunday (dayOfWeek=0)
|
|
const baseDate = new Date('2025-01-05T00:00:00Z')
|
|
baseDate.setUTCDate(baseDate.getUTCDate() + dayOfWeek)
|
|
baseDate.setUTCHours(hour, 0, 0, 0)
|
|
return baseDate
|
|
}
|
|
|
|
// --- Property 1: undefined schedule always returns true ---
|
|
|
|
it('isWithinSchedule(undefined, anyDate) always returns true', () => {
|
|
fc.assert(
|
|
fc.property(arbDate, (date) => {
|
|
expect(isWithinSchedule(undefined, date)).toBe(true)
|
|
})
|
|
)
|
|
})
|
|
|
|
// --- Property 2: Non-midnight-spanning window boundary behavior ---
|
|
|
|
/**
|
|
* **Validates: Requirements 8.1, 8.3**
|
|
*
|
|
* For a non-midnight-spanning window (startHour < endHour):
|
|
* - A time at startHour is within the window
|
|
* - A time at endHour is NOT within the window (exclusive end)
|
|
*/
|
|
it('non-midnight-spanning: startHour is within, endHour is NOT within', () => {
|
|
fc.assert(
|
|
fc.property(arbNormalSchedule, (schedule) => {
|
|
// Pick a day that IS in daysOfWeek
|
|
const day = schedule.daysOfWeek[0]
|
|
|
|
const atStart = makeDateAtHourAndDay(schedule.startHour, day)
|
|
const atEnd = makeDateAtHourAndDay(schedule.endHour, day)
|
|
|
|
expect(isWithinSchedule(schedule, atStart)).toBe(true)
|
|
expect(isWithinSchedule(schedule, atEnd)).toBe(false)
|
|
})
|
|
)
|
|
})
|
|
|
|
// --- Property 3: Midnight-spanning window behavior ---
|
|
|
|
/**
|
|
* **Validates: Requirements 8.1, 8.3**
|
|
*
|
|
* For a midnight-spanning window (startHour > endHour):
|
|
* - A time at startHour is within the window
|
|
* - A time between endHour and startHour (exclusive) is NOT within the window
|
|
*/
|
|
it('midnight-spanning: startHour is within, hour between endHour and startHour is NOT within', () => {
|
|
fc.assert(
|
|
fc.property(
|
|
arbMidnightSchedule,
|
|
fc.integer({ min: 0, max: 23 }),
|
|
(schedule, randomHour) => {
|
|
// Pick a day that IS in daysOfWeek
|
|
const day = schedule.daysOfWeek[0]
|
|
|
|
// startHour should be within
|
|
const atStart = makeDateAtHourAndDay(schedule.startHour, day)
|
|
expect(isWithinSchedule(schedule, atStart)).toBe(true)
|
|
|
|
// An hour strictly between endHour and startHour should be outside
|
|
// For midnight-spanning: outside range is [endHour, startHour)
|
|
const outsideHour = schedule.endHour + (randomHour % (schedule.startHour - schedule.endHour))
|
|
if (outsideHour >= schedule.endHour && outsideHour < schedule.startHour) {
|
|
const outsideDate = makeDateAtHourAndDay(outsideHour, day)
|
|
expect(isWithinSchedule(schedule, outsideDate)).toBe(false)
|
|
}
|
|
}
|
|
)
|
|
)
|
|
})
|
|
|
|
// --- Property 4: Day-of-week filtering ---
|
|
|
|
/**
|
|
* **Validates: Requirements 8.1, 8.3**
|
|
*
|
|
* If the day is NOT in daysOfWeek, returns false regardless of hour.
|
|
*/
|
|
it('day-of-week filtering: excluded day always returns false', () => {
|
|
fc.assert(
|
|
fc.property(arbSchedule, arbHour, (schedule, hour) => {
|
|
const allDays = [0, 1, 2, 3, 4, 5, 6]
|
|
const excludedDays = allDays.filter((d) => !schedule.daysOfWeek.includes(d))
|
|
|
|
// Only test if there are excluded days
|
|
if (excludedDays.length > 0) {
|
|
const excludedDay = excludedDays[0]
|
|
const date = makeDateAtHourAndDay(hour, excludedDay)
|
|
expect(isWithinSchedule(schedule, date)).toBe(false)
|
|
}
|
|
})
|
|
)
|
|
})
|
|
|
|
// --- Property 5: Included day + within hour range returns true ---
|
|
|
|
/**
|
|
* **Validates: Requirements 8.1, 8.3**
|
|
*
|
|
* If daysOfWeek includes the current day and hour is within range, returns true.
|
|
*/
|
|
it('included day + hour within range returns true', () => {
|
|
fc.assert(
|
|
fc.property(arbSchedule, (schedule) => {
|
|
const day = schedule.daysOfWeek[0]
|
|
const { startHour, endHour } = schedule
|
|
|
|
// Pick an hour that is definitely within the window
|
|
let withinHour: number
|
|
if (startHour < endHour) {
|
|
// Normal window: any hour in [startHour, endHour)
|
|
withinHour = startHour
|
|
} else {
|
|
// Midnight-spanning: startHour is always within
|
|
withinHour = startHour
|
|
}
|
|
|
|
const date = makeDateAtHourAndDay(withinHour, day)
|
|
expect(isWithinSchedule(schedule, date)).toBe(true)
|
|
})
|
|
)
|
|
})
|
|
})
|