import { describe, expect, it } from 'vitest'; import { deviceHealth, humaniseSeconds, OVERDUE_FACTOR, type DeviceSnapshot } from './health'; const NOW = new Date('2026-09-22T12:00:00Z'); function snapshot(overrides: Partial = {}): DeviceSnapshot { return { lastSeenAt: new Date('2026-09-22T11:50:00Z'), lastRefreshRate: 600, batteryVoltage: null, percentCharged: null, rssi: null, ...overrides, }; } describe('deviceHealth', () => { it('reports a device that has never called', () => { const health = deviceHealth(snapshot({ lastSeenAt: null }), NOW); expect(health.neverSeen).toBe(true); expect(health.nextWakeAt).toBeNull(); expect(health.isOverdue).toBe(false); }); it('predicts the next wake from the interval the device was given', () => { const health = deviceHealth(snapshot(), NOW); expect(health.nextWakeAt?.toISOString()).toBe('2026-09-22T12:00:00.000Z'); expect(health.secondsUntilWake).toBe(0); }); it('counts down to the next wake', () => { const health = deviceHealth(snapshot({ lastSeenAt: new Date('2026-09-22T11:55:00Z') }), NOW); expect(health.secondsUntilWake).toBe(300); expect(health.isOverdue).toBe(false); }); it('tolerates a late wake before calling it overdue', () => { // Firmware wake-ups drift; an alert that cries wolf every cycle is unread. const justLate = new Date(NOW.getTime() - 600 * 1.2 * 1000); expect(deviceHealth(snapshot({ lastSeenAt: justLate }), NOW).isOverdue).toBe(false); }); it('calls a device overdue once it misses by half its interval again', () => { const wellLate = new Date(NOW.getTime() - 600 * (OVERDUE_FACTOR + 0.1) * 1000); const health = deviceHealth(snapshot({ lastSeenAt: wellLate }), NOW); expect(health.isOverdue).toBe(true); expect(health.secondsUntilWake).toBeLessThan(0); }); it('says nothing about lateness with no interval to go on', () => { const health = deviceHealth(snapshot({ lastRefreshRate: null }), NOW); expect(health.nextWakeAt).toBeNull(); expect(health.isOverdue).toBe(false); }); it('ignores a nonsensical interval', () => { expect(deviceHealth(snapshot({ lastRefreshRate: 0 }), NOW).nextWakeAt).toBeNull(); }); it('prefers the reported battery percentage to the voltage', () => { const health = deviceHealth(snapshot({ percentCharged: 42, batteryVoltage: 4.1 }), NOW); expect(health.batteryPercent).toBe(42); }); it('derives a rough percentage from the voltage when that is all there is', () => { expect(deviceHealth(snapshot({ batteryVoltage: 4.1 }), NOW).batteryPercent).toBe(100); expect(deviceHealth(snapshot({ batteryVoltage: 3.3 }), NOW).batteryPercent).toBe(0); expect(deviceHealth(snapshot({ batteryVoltage: 3.7 }), NOW).batteryPercent).toBe(50); }); it('clamps a voltage outside the usable range', () => { expect(deviceHealth(snapshot({ batteryVoltage: 4.5 }), NOW).batteryPercent).toBe(100); expect(deviceHealth(snapshot({ batteryVoltage: 2.9 }), NOW).batteryPercent).toBe(0); }); it('reports nothing about a battery it was never told about', () => { expect(deviceHealth(snapshot(), NOW).batteryPercent).toBeNull(); }); it('grades the wifi signal', () => { expect(deviceHealth(snapshot({ rssi: -55 }), NOW).signal).toBe('good'); expect(deviceHealth(snapshot({ rssi: -72 }), NOW).signal).toBe('fair'); expect(deviceHealth(snapshot({ rssi: -88 }), NOW).signal).toBe('poor'); expect(deviceHealth(snapshot(), NOW).signal).toBeNull(); }); }); describe('humaniseSeconds', () => { it('looks forward and backward', () => { expect(humaniseSeconds(300)).toBe('dans 5 minutes'); expect(humaniseSeconds(-300)).toBe('il y a 5 minutes'); }); it('shortens anything under a minute', () => { expect(humaniseSeconds(30)).toBe('dans moins d’une minute'); expect(humaniseSeconds(0)).toBe('dans moins d’une minute'); }); it('switches unit as the distance grows', () => { expect(humaniseSeconds(3600)).toBe('dans 1 heure'); expect(humaniseSeconds(7200)).toBe('dans 2 heures'); expect(humaniseSeconds(172_800)).toBe('dans 2 jours'); }); it('keeps the singular', () => { expect(humaniseSeconds(60)).toBe('dans 1 minute'); }); });