import { describe, expect, it } from 'vitest'; import type { SimSnapshot } from '../contracts'; import { formatReserve, powerState, TIGHT_AT } from './power'; type BW = SimSnapshot['bandwidth']; const bw = (over: Partial = {}): BW => ({ gen: 100, draw: 50, stored: 0, brownout: false, ...over }); describe('powerState', () => { it('is ok with headroom', () => { expect(powerState(bw()).state).toBe('ok'); }); it('goes tight at 85% of generation, before anything is wrong', () => { expect(powerState(bw({ draw: 84 })).state).toBe('ok'); expect(powerState(bw({ draw: TIGHT_AT * 100 })).state).toBe('tight'); expect(powerState(bw({ draw: 99 })).state).toBe('tight'); }); // ---- the round-3 bug, pinned ------------------------------------------------ it('a covered deficit is ON RESERVE, not a brownout', () => { // draw exceeds gen, tanks have charge, and the sim says no brownout. // The old code keyed on `out || ratio > 1` and screamed BROWNOUT at this. const p = powerState(bw({ gen: 100, draw: 134, stored: 1000, brownout: false })); expect(p.state).toBe('reserve'); expect(p.state).not.toBe('brownout'); }); it('reports seconds of reserve left, because stored is bandwidth-SECONDS', () => { // 1000 bandwidth-seconds stored, bleeding 34/sec => ~29s const p = powerState(bw({ gen: 100, draw: 134, stored: 1000, brownout: false })); expect(p.reserveSeconds).toBeCloseTo(1000 / 34, 5); }); it('only the sim gets to say BROWNOUT', () => { // Same covered-deficit numbers, but the sim has raised the flag: believe the flag. expect(powerState(bw({ gen: 100, draw: 134, stored: 1000, brownout: true })).state) .toBe('brownout'); // ...and a brownout at nominal draw is still a brownout. expect(powerState(bw({ gen: 100, draw: 1, brownout: true })).state).toBe('brownout'); }); it('treats an exhausted reserve honestly rather than rounding it up', () => { const p = powerState(bw({ gen: 10, draw: 20, stored: 0, brownout: false })); expect(p.state).toBe('reserve'); expect(p.reserveSeconds).toBe(0); // about to become the sim's problem }); it('handles drawing with no generation at all', () => { const p = powerState(bw({ gen: 0, draw: 4, stored: 40, brownout: false })); expect(p.state).toBe('reserve'); expect(p.ratio).toBe(Infinity); expect(p.reserveSeconds).toBe(10); }); it('is ok on an empty floor — no generation, no draw', () => { const p = powerState(bw({ gen: 0, draw: 0 })); expect(p.state).toBe('ok'); expect(p.ratio).toBe(0); }); it('exactly break-even is not a deficit', () => { expect(powerState(bw({ gen: 100, draw: 100, stored: 5 })).state).toBe('tight'); }); }); describe('formatReserve', () => { it('floors, because a countdown that rounds up lies at the end', () => { expect(formatReserve(29.9)).toBe('29s'); expect(formatReserve(0.4)).toBe('0s'); }); it('caps absurd numbers instead of printing a wall of digits', () => { expect(formatReserve(100000)).toBe('999s'); expect(formatReserve(Infinity)).toBe('∞'); }); });