'use strict'; // Phase 10 rewrite: drives only the public BaseNodeAdapter surface. // The pre-refactor _attachInputHandler private switch is gone — input // dispatch goes through the commands registry that BaseNodeAdapter builds // at construction. Tests fire msgs through `node.handlers.input` and // observe via `node.sends`, `inst.source.engine.*`, and per-fire calls // captured on a child stub registered through `RED.nodes.getNode(id)`. const test = require('node:test'); const assert = require('node:assert/strict'); const nodeClass = require('../../src/nodeClass'); const { makeUiConfig } = require('../helpers/factories'); function makeNode(id = 'reactor-1') { const sends = []; const statuses = []; const handlers = {}; return { id, sends, statuses, handlers, send(arr) { sends.push(arr); }, status(b) { statuses.push(b); }, on(ev, fn) { handlers[ev] = fn; }, warn() {}, error() {}, }; } function makeRED(nodeMap = {}) { return { nodes: { getNode: (id) => nodeMap[id] || null } }; } function closeNode(node) { if (node.handlers.close) node.handlers.close(() => {}); } test('legacy alias topics drive engine setters and updateState', async () => { const childSource = { id: 'child-source-A', config: { general: { id: 'child-source-A' }, functionality: { softwareType: 'measurement', positionVsParent: 'upstream' }, asset: { type: 'temperature' } }, }; const node = makeNode(); const RED = makeRED({ childA: { source: childSource } }); const inst = new nodeClass(makeUiConfig(), RED, node, 'reactor'); try { let doneCount = 0; const done = () => { doneCount += 1; }; // data.clock alias → updateState(timestamp). Capture currentTime // before/after to verify the engine advanced. const t0 = inst.source.engine.currentTime; await node.handlers.input({ topic: 'clock', timestamp: t0 + 1 }, () => {}, done); // Fluent alias → engine setInfluent setter. await node.handlers.input( { topic: 'Fluent', payload: { inlet: 0, F: 7, C: [1,2,3,4,5,6,7,8,9,10,11,12,13] } }, () => {}, done, ); assert.equal(inst.source.engine.Fs[0], 7); assert.deepEqual(inst.source.engine.Cs_in[0], [1,2,3,4,5,6,7,8,9,10,11,12,13]); // OTR alias → engine setOTR setter. await node.handlers.input({ topic: 'OTR', payload: 3.5 }, () => {}, done); assert.equal(inst.source.engine.OTR, 3.5); // Temperature alias → engine setTemperature setter. await node.handlers.input({ topic: 'Temperature', payload: 18.2 }, () => {}, done); assert.equal(inst.source.engine.temperature, 18.2); // Dispersion alias — CSTR engine does not own a setDispersion setter // (only PFR does); the Reactor wrapper guards on engine type and the // dispatch should silently return without throwing. await node.handlers.input({ topic: 'Dispersion', payload: 0.2 }, () => {}, done); // registerChild alias → registers via childRegistrationUtils. // The handler resolves the child via RED.nodes.getNode(payload).source. await node.handlers.input( { topic: 'registerChild', payload: 'childA', positionVsParent: 'upstream' }, () => {}, done, ); assert.equal(doneCount, 6); } finally { closeNode(node); } }); test('canonical topics are accepted (data.fluent, data.otr, data.temperature)', async () => { const node = makeNode(); const inst = new nodeClass(makeUiConfig(), makeRED(), node, 'reactor'); try { let done = 0; await node.handlers.input( { topic: 'data.fluent', payload: { inlet: 0, F: 11, C: [0,0,0,0,0,0,0,0,0,0,0,0,0] } }, () => {}, () => { done += 1; }, ); assert.equal(inst.source.engine.Fs[0], 11); await node.handlers.input({ topic: 'data.otr', payload: 4.2 }, () => {}, () => { done += 1; }); assert.equal(inst.source.engine.OTR, 4.2); await node.handlers.input({ topic: 'data.temperature', payload: 19.7 }, () => {}, () => { done += 1; }); assert.equal(inst.source.engine.temperature, 19.7); assert.equal(done, 3); } finally { closeNode(node); } });