This commit is contained in:
znetsixe
2026-03-11 11:13:26 +01:00
parent 33f3c2ef61
commit 6b2a8239f2
16 changed files with 2850 additions and 146 deletions

View File

@@ -12,6 +12,28 @@ test('setpoint rejects negative inputs without throwing', async () => {
});
});
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('nodeClass _updateNodeStatus returns error status on internal failure', () => {
const inst = Object.create(NodeClass.prototype);
const node = makeNodeStub();
@@ -29,3 +51,24 @@ test('nodeClass _updateNodeStatus returns error status on internal failure', ()
assert.equal(status.text, 'Status Error');
assert.equal(node._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);
});

View File

@@ -43,9 +43,15 @@ test('input handler routes topics to source methods', () => {
updateMeasuredFlow(value, position) {
calls.push(['updateMeasuredFlow', value, position]);
},
updateMeasuredPower(value, position) {
calls.push(['updateMeasuredPower', value, position]);
},
updateMeasuredTemperature(value, position) {
calls.push(['updateMeasuredTemperature', value, position]);
},
isUnitValidForType() {
return true;
},
};
inst._attachInputHandler();
@@ -53,13 +59,53 @@ test('input handler routes topics to source methods', () => {
onInput({ topic: 'setMode', payload: 'auto' }, () => {}, () => {});
onInput({ topic: 'execSequence', payload: { source: 'GUI', action: 'execSequence', parameter: 'startup' } }, () => {}, () => {});
onInput({ topic: 'flowMovement', payload: { source: 'GUI', action: 'flowMovement', setpoint: 123 } }, () => {}, () => {});
onInput({ topic: 'emergencystop', payload: { source: 'GUI', action: 'emergencystop' } }, () => {}, () => {});
onInput({ topic: 'registerChild', payload: 'child1', positionVsParent: 'downstream' }, () => {}, () => {});
onInput({ topic: 'simulateMeasurement', payload: { type: 'pressure', position: 'upstream', value: 250, unit: 'mbar' } }, () => {}, () => {});
onInput({ topic: 'simulateMeasurement', payload: { type: 'power', position: 'atEquipment', value: 7.5, unit: 'kW' } }, () => {}, () => {});
assert.deepEqual(calls[0], ['setMode', 'auto']);
assert.deepEqual(calls[1], ['handleInput', 'GUI', 'execSequence', 'startup']);
assert.deepEqual(calls[2], ['registerChild', { id: 'child-source' }, 'downstream']);
assert.deepEqual(calls[3], ['updateSimulatedMeasurement', 'pressure', 'upstream', 250]);
assert.deepEqual(calls[2], ['handleInput', 'GUI', 'flowMovement', 123]);
assert.deepEqual(calls[3], ['handleInput', 'GUI', 'emergencystop', undefined]);
assert.deepEqual(calls[4], ['registerChild', { id: 'child-source' }, 'downstream']);
assert.deepEqual(calls[5], ['updateSimulatedMeasurement', 'pressure', 'upstream', 250]);
assert.deepEqual(calls[6], ['updateMeasuredPower', 7.5, 'atEquipment']);
});
test('simulateMeasurement warns and ignores invalid payloads', () => {
const inst = Object.create(NodeClass.prototype);
const node = makeNodeStub();
const calls = [];
inst.node = node;
inst.RED = makeREDStub();
inst.source = {
childRegistrationUtils: { registerChild() {} },
setMode() {},
handleInput() {},
showWorkingCurves() { return {}; },
showCoG() { return {}; },
updateSimulatedMeasurement() { calls.push('updateSimulatedMeasurement'); },
updateMeasuredPressure() { calls.push('updateMeasuredPressure'); },
updateMeasuredFlow() { calls.push('updateMeasuredFlow'); },
updateMeasuredPower() { calls.push('updateMeasuredPower'); },
updateMeasuredTemperature() { calls.push('updateMeasuredTemperature'); },
};
inst._attachInputHandler();
const onInput = node._handlers.input;
onInput({ topic: 'simulateMeasurement', payload: { type: 'pressure', position: 'upstream', value: 'not-a-number' } }, () => {}, () => {});
onInput({ topic: 'simulateMeasurement', payload: { type: 'flow', position: 'upstream', value: 12 } }, () => {}, () => {});
onInput({ topic: 'simulateMeasurement', payload: { type: 'unknown', position: 'upstream', value: 12, unit: 'm3/h' } }, () => {}, () => {});
assert.equal(calls.length, 0);
assert.equal(node._warns.length, 3);
assert.match(String(node._warns[0]), /finite number/i);
assert.match(String(node._warns[1]), /payload\.unit is required/i);
assert.match(String(node._warns[2]), /unsupported simulatemeasurement type/i);
});
test('status shows warning when pressure inputs are not initialized', () => {