const test = require('node:test'); const assert = require('node:assert/strict'); const Machine = require('../../src/specificClass'); const NodeClass = require('../../src/nodeClass'); const { makeMachineConfig, makeStateConfig, makeNodeStub, makeREDStub } = require('../helpers/factories'); function makeUiConfig(overrides = {}) { // Post-AssetResolver: editor saves only model + unit + uuid/tagCode. return { unit: 'm3/h', enableLog: false, logLevel: 'error', model: 'hidrostal-H05K-S03R', curvePressureUnit: 'mbar', curveFlowUnit: 'm3/h', curvePowerUnit: 'kW', curveControlUnit: '%', positionVsParent: 'atEquipment', speed: 1, movementMode: 'staticspeed', startup: 0, warmup: 0, shutdown: 0, cooldown: 0, ...overrides, }; } // Adapters park a periodic status-poll timer. Drive the BaseNodeAdapter // close handler after each test to stop it — the public teardown path // used by Node-RED itself on flow shutdown. const _adapters = []; function buildAdapter(ui = makeUiConfig()) { const node = makeNodeStub(); const inst = new NodeClass(ui, makeREDStub(), node, 'rotatingMachine'); _adapters.push(node); return { inst, node }; } test.afterEach(() => { while (_adapters.length) { const node = _adapters.pop(); try { node._handlers.close?.(() => {}); } catch (_) { /* best effort */ } } }); test('setpoint rejects negative inputs without throwing', async () => { const machine = new Machine(makeMachineConfig(), makeStateConfig({ state: { current: 'operational' } })); await assert.doesNotReject(async () => { await machine.setpoint(-1); }); }); test('setpoint is constrained to safe movement/curve bounds', async () => { const machine = new Machine(makeMachineConfig(), makeStateConfig({ state: { current: 'operational' } })); const requested = []; machine.state.moveTo = async (target) => { requested.push(target); }; const stateMin = machine.state.movementManager.minPosition; const stateMax = machine.state.movementManager.maxPosition; const curveMin = machine.predictFlow.currentFxyXMin; const curveMax = machine.predictFlow.currentFxyXMax; const min = Math.max(stateMin, curveMin); const max = Math.min(stateMax, curveMax); await machine.setpoint(min - 100); await machine.setpoint(max + 100); assert.equal(requested.length, 2); assert.equal(requested[0], min); assert.equal(requested[1], max); }); test('source.getStatusBadge returns error status on internal failure', () => { // Build the full adapter, then force the source's state.getCurrentState // to throw — the public getStatusBadge() must catch and return an // error badge without propagating. const { inst } = buildAdapter(); const errors = []; inst.source.logger.error = (m) => errors.push(m); inst.source.state.getCurrentState = () => { throw new Error('boom'); }; const status = inst.source.getStatusBadge(); assert.match(status.text, /Status Error/); assert.equal(status.fill, 'red'); assert.equal(errors.length, 1); }); test('measurement handlers reject incompatible units', () => { const machine = new Machine(makeMachineConfig(), makeStateConfig({ state: { current: 'operational' } })); assert.equal(machine.isUnitValidForType('flow', 'm3/h'), true); assert.equal(machine.isUnitValidForType('flow', 'mbar'), false); machine.updateMeasuredFlow(100, 'downstream', { timestamp: Date.now(), unit: 'mbar', childName: 'bad-ft', }); const measuredFlow = machine.measurements .type('flow') .variant('measured') .position('downstream') .getCurrentValue(); assert.equal(measuredFlow, null); });