For all 11 nodes with auto-gen markers: wiki/Home.md sections 5 (topic contract) and 9 (data model) regenerated via npm run wiki:all. New Unit column shows '<measure> (default <unit>)' for declared topics, '—' otherwise. Effect column now uses descriptor.description (P11.2 field) overriding the generic per-prefix fallback. For rotatingMachine + reactor: Phase 10 test rewrites — 3 + 8 files moved off private nodeClass internals (_attachInputHandler, _commands, _pendingExtras, _registerChild, _tick, etc.) to the public BaseNodeAdapter surface (node.handlers.input, node.source.*). +6 / +7 net new tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
103 lines
3.6 KiB
JavaScript
103 lines
3.6 KiB
JavaScript
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 = {}) {
|
|
return {
|
|
unit: 'm3/h', enableLog: false, logLevel: 'error',
|
|
supplier: 'hidrostal', category: 'machine', assetType: 'pump',
|
|
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);
|
|
});
|