fix: production hardening — safety fixes, prediction accuracy, test coverage
Safety: - Async input handler: await all handleInput() calls, prevents unhandled rejections - Fix emergencyStop case mismatch: "emergencyStop" → "emergencystop" matching config - Implement showCoG() method (was routing to undefined) - Null guards on 6 methods for missing curve data - Editor menu polling timeout (5s max) - Listener cleanup on node close (child measurements + state emitter) - Tick loop race condition: track startup timeout, clear on close Prediction accuracy: - Remove efficiency rounding that destroyed signal in canonical units - Fix calcEfficiency variant: hydraulic power reads from correct variant - Guard efficiency calculations against negative/zero values - Division-by-zero protection in calcRelativeDistanceFromPeak - Curve data anomaly detection (cross-pressure median-y ratio check) - calcEfficiencyCurve O(n²) → O(n) with running min - updateCurve bootstraps predictors when they were null Tests: 43 new tests (76 total) covering emergency stop, shutdown/maintenance sequences, efficiency/CoG, movement lifecycle, output format, null guards, and listener cleanup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
72
test/integration/shutdown-sequence.integration.test.js
Normal file
72
test/integration/shutdown-sequence.integration.test.js
Normal file
@@ -0,0 +1,72 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const Machine = require('../../src/specificClass');
|
||||
const { makeMachineConfig, makeStateConfig } = require('../helpers/factories');
|
||||
|
||||
test('shutdown sequence from operational reaches idle', async () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig());
|
||||
|
||||
await machine.handleInput('parent', 'execSequence', 'startup');
|
||||
assert.equal(machine.state.getCurrentState(), 'operational');
|
||||
|
||||
await machine.handleInput('parent', 'execSequence', 'shutdown');
|
||||
assert.equal(machine.state.getCurrentState(), 'idle');
|
||||
});
|
||||
|
||||
test('shutdown from operational ramps down position before stopping', async () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig());
|
||||
|
||||
await machine.handleInput('parent', 'execSequence', 'startup');
|
||||
await machine.handleInput('parent', 'execMovement', 50);
|
||||
|
||||
const posBefore = machine.state.getCurrentPosition();
|
||||
assert.ok(posBefore > 0, 'Machine should be at non-zero position');
|
||||
|
||||
await machine.handleInput('parent', 'execSequence', 'shutdown');
|
||||
|
||||
const posAfter = machine.state.getCurrentPosition();
|
||||
assert.ok(posAfter <= posBefore, 'Position should have decreased after shutdown');
|
||||
assert.equal(machine.state.getCurrentState(), 'idle');
|
||||
});
|
||||
|
||||
test('shutdown clears predicted flow and power', async () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig());
|
||||
|
||||
await machine.handleInput('parent', 'execSequence', 'startup');
|
||||
machine.updateMeasuredPressure(1000, 'downstream', { timestamp: Date.now(), unit: 'mbar', childName: 'pt' });
|
||||
await machine.handleInput('parent', 'execMovement', 50);
|
||||
|
||||
await machine.handleInput('parent', 'execSequence', 'shutdown');
|
||||
|
||||
const flow = machine.measurements.type('flow').variant('predicted').position('downstream').getCurrentValue();
|
||||
const power = machine.measurements.type('power').variant('predicted').position('atEquipment').getCurrentValue();
|
||||
assert.equal(flow, 0, 'Flow should be zero after shutdown');
|
||||
assert.equal(power, 0, 'Power should be zero after shutdown');
|
||||
});
|
||||
|
||||
test('entermaintenance sequence from operational reaches maintenance state', async () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig());
|
||||
|
||||
await machine.handleInput('parent', 'execSequence', 'startup');
|
||||
assert.equal(machine.state.getCurrentState(), 'operational');
|
||||
|
||||
await machine.handleInput('parent', 'enterMaintenance', 'entermaintenance');
|
||||
assert.equal(machine.state.getCurrentState(), 'maintenance');
|
||||
});
|
||||
|
||||
test('exitmaintenance requires mode with exitmaintenance action allowed', async () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig());
|
||||
|
||||
// Use auto mode (has execsequence + entermaintenance) to reach maintenance
|
||||
await machine.handleInput('parent', 'execSequence', 'startup');
|
||||
assert.equal(machine.state.getCurrentState(), 'operational');
|
||||
|
||||
await machine.handleInput('parent', 'enterMaintenance', 'entermaintenance');
|
||||
assert.equal(machine.state.getCurrentState(), 'maintenance');
|
||||
|
||||
// Switch to fysicalControl which allows exitmaintenance
|
||||
machine.setMode('fysicalControl');
|
||||
await machine.handleInput('fysical', 'exitMaintenance', 'exitmaintenance');
|
||||
assert.equal(machine.state.getCurrentState(), 'idle');
|
||||
});
|
||||
Reference in New Issue
Block a user