const test = require('node:test'); const assert = require('node:assert/strict'); const NodeClass = require('../../src/nodeClass'); const { makeNodeStub, makeREDStub } = require('../helpers/factories'); test('input handler routes topics to source methods', () => { const inst = Object.create(NodeClass.prototype); const node = makeNodeStub(); const calls = []; inst.node = node; inst.RED = makeREDStub({ child1: { source: { id: 'child-source' }, }, }); inst.source = { childRegistrationUtils: { registerChild(childSource, pos) { calls.push(['registerChild', childSource, pos]); }, }, setMode(mode) { calls.push(['setMode', mode]); }, handleInput(source, action, parameter) { calls.push(['handleInput', source, action, parameter]); }, showWorkingCurves() { return { ok: true }; }, showCoG() { return { cog: 1 }; }, updateSimulatedMeasurement(type, position, value) { calls.push(['updateSimulatedMeasurement', type, position, value]); }, updateMeasuredPressure(value, position) { calls.push(['updateMeasuredPressure', value, position]); }, 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(); const onInput = node._handlers.input; 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], ['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', () => { const inst = Object.create(NodeClass.prototype); const node = makeNodeStub(); inst.node = node; inst.source = { currentMode: 'virtualControl', state: { getCurrentState() { return 'operational'; }, getCurrentPosition() { return 50; }, }, getPressureInitializationStatus() { return { initialized: false, hasUpstream: false, hasDownstream: false, hasDifferential: false }; }, measurements: { type() { return { variant() { return { position() { return { getCurrentValue() { return 0; } }; }, }; }, }; }, }, }; const status = inst._updateNodeStatus(); const statusAgain = inst._updateNodeStatus(); assert.equal(status.fill, 'yellow'); assert.equal(status.shape, 'ring'); assert.match(status.text, /pressure not initialized/i); assert.equal(statusAgain.fill, 'yellow'); assert.equal(node._warns.length, 1); assert.match(String(node._warns[0]), /Pressure input is not initialized/i); }); test('showWorkingCurves and CoG route reply messages to process output index', () => { const inst = Object.create(NodeClass.prototype); const node = makeNodeStub(); inst.node = node; inst.RED = makeREDStub(); inst.source = { childRegistrationUtils: { registerChild() {} }, setMode() {}, handleInput() {}, showWorkingCurves() { return { curve: [1, 2, 3] }; }, showCoG() { return { cog: 0.77 }; }, }; inst._attachInputHandler(); const onInput = node._handlers.input; const sent = []; const send = (out) => sent.push(out); onInput({ topic: 'showWorkingCurves', payload: { request: true } }, send, () => {}); onInput({ topic: 'CoG', payload: { request: true } }, send, () => {}); assert.equal(sent.length, 2); assert.equal(Array.isArray(sent[0]), true); assert.equal(sent[0].length, 3); assert.equal(sent[0][0].topic, 'showWorkingCurves'); assert.equal(sent[0][1], null); assert.equal(sent[0][2], null); assert.equal(sent[1][0].topic, 'showCoG'); });