screentinker/server/test/scheduled-reboot.test.js
screentinker 501ffb11c1
feat(device-owner): tier foundation + QR provisioning + content-expiry & device enhancements (#168)
Device-owner tier substrate + silent install, end-to-end QR provisioning (Android 12+ compliance, APK-derived checksum, URL pre-seed, zero-touch onboarding, guided a11y screen), content-expiry (#157) with player no-restart deferral, and the device-enhancement batch (#10/#12/#13/#14). Backward-compatible with 1.9.3 clients; all autonomous behaviors opt-in. QA + security review green. Closes #161, #157, #159.
2026-07-12 19:41:07 -05:00

54 lines
2.5 KiB
JavaScript

// #12 scheduled reboot: the pure `rebootDue` decision. Verifies the daily fire, the
// once-per-day guard, the 5-minute catch window (robust to 60s-tick drift), timezone
// evaluation (device-local, not server-local), and off/invalid schedules.
const { test } = require('node:test');
const assert = require('node:assert/strict');
const { rebootDue } = require('../services/scheduler');
// Build a UTC instant. rebootDue evaluates it in the DEVICE's tz.
const at = (iso) => new Date(iso);
test('fires when local clock is at the scheduled minute, not yet fired today', () => {
// 03:00 UTC, schedule 03:00, no tz override -> server/UTC local.
const r = rebootDue('03:00', 'UTC', at('2026-07-11T03:00:00Z'), null);
assert.equal(r.due, true);
assert.equal(r.today, '2026-07-11');
});
test('once-per-day guard: does not re-fire after already firing today', () => {
const r = rebootDue('03:00', 'UTC', at('2026-07-11T03:01:00Z'), '2026-07-11');
assert.equal(r.due, false);
});
test('catch window: still fires up to 4 min late (tick drift), not at +5', () => {
assert.equal(rebootDue('03:00', 'UTC', at('2026-07-11T03:04:00Z'), null).due, true);
assert.equal(rebootDue('03:00', 'UTC', at('2026-07-11T03:05:00Z'), null).due, false);
});
test('not due before the scheduled minute', () => {
assert.equal(rebootDue('03:00', 'UTC', at('2026-07-11T02:59:00Z'), null).due, false);
});
test('evaluated in the device timezone, not the server clock', () => {
// 07:00 UTC == 03:00 America/New_York (EDT, UTC-4 in July). Schedule 03:00 local should fire.
const r = rebootDue('03:00', 'America/New_York', at('2026-07-11T07:00:00Z'), null);
assert.equal(r.due, true);
assert.equal(r.today, '2026-07-11'); // local date matches (still the 11th at 3am EDT)
// At 07:00 UTC the server clock is 07:00 — a 07:00 schedule evaluated in the device zone is
// 03:00 local, NOT 07:00, so it must NOT fire — proving we used the device zone.
assert.equal(rebootDue('07:00', 'America/New_York', at('2026-07-11T07:00:00Z'), null).due, false);
});
test('local-date rollover: a fire just after local midnight stamps the new day', () => {
// 04:30 UTC == 00:30 EDT on 2026-07-11; schedule 00:30 local.
const r = rebootDue('00:30', 'America/New_York', at('2026-07-11T04:30:00Z'), '2026-07-10');
assert.equal(r.due, true);
assert.equal(r.today, '2026-07-11');
});
test('off / invalid schedules are never due', () => {
for (const s of [null, '', undefined, '25:00', '3:00', 'abc', '03:60']) {
assert.equal(rebootDue(s, 'UTC', at('2026-07-11T03:00:00Z'), null).due, false);
}
});