'use strict'; const { test } = require('node:test'); const assert = require('node:assert/strict'); const StateManager = require('../../src/state/stateManager'); // Minimal config that satisfies the stateManager constructor's expectations. // Real configs come from configs/.json; we hand-roll one here so the // test doesn't drag the whole node-config plumbing in for a 30-line getter. function makeConfig(initial = 'idle', times = { idle: 0, warmingup: 5 }) { return { state: { current: initial, available: ['idle', 'warmingup', 'operational'], descriptions: { idle: 'off', warmingup: 'warming', operational: 'running' }, allowedTransitions: { idle: new Set(['warmingup']), warmingup: new Set(['operational']), operational: new Set(['idle']), }, activeStates: new Set(['operational']), }, time: times, }; } const noopLogger = { debug() {}, info() {}, warn() {}, error() {} }; test('getRemainingTransitionS returns 0 for untimed initial state', () => { const sm = new StateManager(makeConfig('idle'), noopLogger); assert.equal(sm.getRemainingTransitionS(), 0); }); test('getRemainingTransitionS returns ≈full duration just after entering a timed state', async () => { const sm = new StateManager(makeConfig('idle', { idle: 0, warmingup: 5 }), noopLogger); // Force-enter "warmingup" via the constructor's state machinery: simulate // by manually setting fields the way transitionTo would. sm.currentState = 'warmingup'; sm.stateEnteredAt = Date.now(); const remaining = sm.getRemainingTransitionS(); assert.ok(remaining > 4.9 && remaining <= 5.0, `expected ~5s, got ${remaining}`); }); test('getRemainingTransitionS decays with elapsed time', async () => { const sm = new StateManager(makeConfig('idle', { idle: 0, warmingup: 5 }), noopLogger); sm.currentState = 'warmingup'; sm.stateEnteredAt = Date.now() - 2000; // pretend we entered 2s ago const remaining = sm.getRemainingTransitionS(); assert.ok(remaining > 2.9 && remaining <= 3.0, `expected ~3s, got ${remaining}`); }); test('getRemainingTransitionS clamps to 0 once duration has elapsed', () => { const sm = new StateManager(makeConfig('idle', { idle: 0, warmingup: 5 }), noopLogger); sm.currentState = 'warmingup'; sm.stateEnteredAt = Date.now() - 60_000; // a minute ago, way past 5s assert.equal(sm.getRemainingTransitionS(), 0); }); test('transitionTo refreshes stateEnteredAt on the immediate branch', async () => { const sm = new StateManager(makeConfig('idle', { idle: 0 }), noopLogger); const before = sm.stateEnteredAt; await new Promise((r) => setTimeout(r, 10)); await sm.transitionTo('warmingup'); assert.ok(sm.stateEnteredAt > before, 'stateEnteredAt should advance on transition'); }); test('transitionTo refreshes stateEnteredAt on the timed branch', async () => { // Tiny duration so the test stays fast. const sm = new StateManager(makeConfig('idle', { idle: 0.05, warmingup: 0 }), noopLogger); const before = sm.stateEnteredAt; await new Promise((r) => setTimeout(r, 10)); await sm.transitionTo('warmingup'); assert.ok(sm.stateEnteredAt > before, 'stateEnteredAt should advance after timed transition'); // And remaining should now be 0 (we're in warmingup, but warmingup duration is 0). assert.equal(sm.getRemainingTransitionS(), 0); });