diff --git a/changelog.d/fixes/11718-peak-hour-overnight-days.md b/changelog.d/fixes/11718-peak-hour-overnight-days.md new file mode 100644 index 0000000000..296d27c3e4 --- /dev/null +++ b/changelog.d/fixes/11718-peak-hour-overnight-days.md @@ -0,0 +1 @@ +- **fix(providers):** Keep overnight peak-hour protection active after midnight according to the window's configured start day ([#11718](https://github.com/diegosouzapw/OmniRoute/pull/11718)) — thanks @pacocartones diff --git a/src/lib/providers/peakHourProtection.ts b/src/lib/providers/peakHourProtection.ts index 08bf6248dd..5957cc340e 100644 --- a/src/lib/providers/peakHourProtection.ts +++ b/src/lib/providers/peakHourProtection.ts @@ -109,11 +109,13 @@ function isDayAllowed(window: PeakHourWindow, date: Date): boolean { } function windowActiveAt(window: PeakHourWindow, date: Date): boolean { - if (!isDayAllowed(window, date)) return false; const start = parseUtcTimeMinutes(window.startUtc); const end = parseUtcTimeMinutes(window.endUtc); if (start === null || end === null || start === end) return false; const now = date.getUTCHours() * 60 + date.getUTCMinutes(); + const effectiveDay = + start > end && now < end ? new Date(date.getTime() - MINUTES_PER_DAY * 60_000) : date; + if (!isDayAllowed(window, effectiveDay)) return false; return start < end ? now >= start && now < end : now >= start || now < end; } diff --git a/tests/unit/peak-hour-protection.test.ts b/tests/unit/peak-hour-protection.test.ts index 0b58ef6479..fc474edaae 100644 --- a/tests/unit/peak-hour-protection.test.ts +++ b/tests/unit/peak-hour-protection.test.ts @@ -55,6 +55,73 @@ test("peak-hour protection honors weekdays and end boundary", () => { ); }); +test("overnight windows apply their start day after midnight", () => { + const mondayWindow = { + peakHourProtection: { + enabled: true, + mode: "block", + windows: [{ days: ["mon"], startUtc: "22:00", endUtc: "02:00" }], + }, + }; + + const mondayNight = evaluatePeakHourProtection( + mondayWindow, + new Date("2026-08-24T23:00:00.000Z") + ); + assert.equal(mondayNight.active, true); + assert.equal(mondayNight.retryAfter, "2026-08-25T02:00:00.000Z"); + + const tuesdayEarly = evaluatePeakHourProtection( + mondayWindow, + new Date("2026-08-25T01:00:00.000Z") + ); + assert.equal(tuesdayEarly.active, true); + assert.equal(tuesdayEarly.retryAfter, "2026-08-25T02:00:00.000Z"); + + assert.deepEqual(evaluatePeakHourProtection(mondayWindow, new Date("2026-08-25T02:00:00.000Z")), { + active: false, + }); + assert.deepEqual(evaluatePeakHourProtection(mondayWindow, new Date("2026-08-25T23:00:00.000Z")), { + active: false, + }); + + const tuesdayWindow = { + peakHourProtection: { + enabled: true, + mode: "block", + windows: [{ days: ["tue"], startUtc: "22:00", endUtc: "02:00" }], + }, + }; + assert.deepEqual( + evaluatePeakHourProtection(tuesdayWindow, new Date("2026-08-25T01:00:00.000Z")), + { active: false } + ); + + const sundayWindow = { + peakHourProtection: { + enabled: true, + mode: "block", + windows: [{ days: ["sun"], startUtc: "22:00", endUtc: "02:00" }], + }, + }; + assert.equal( + evaluatePeakHourProtection(sundayWindow, new Date("2026-08-24T01:00:00.000Z")).active, + true + ); + + const dailyWindow = { + peakHourProtection: { + enabled: true, + mode: "block", + windows: [{ startUtc: "22:00", endUtc: "02:00" }], + }, + }; + assert.equal( + evaluatePeakHourProtection(dailyWindow, new Date("2026-08-25T01:00:00.000Z")).active, + true + ); +}); + test("peak-hour protection supports daily Z.ai-style windows", () => { const state = evaluatePeakHourProtection( {