const test = require('node:test'); const assert = require('node:assert/strict'); const NodeClass = require('../../src/nodeClass'); const { makeNodeStub, makeREDStub } = require('../helpers/factories'); // These tests drive the BaseNodeAdapter public surface. We construct the // full nodeClass and observe the resulting `inst.source.config` (the // validated merged shape) and the source's runtime mode. No private hooks. function makeUiConfig(overrides = {}) { // After the AssetResolver cutover, the editor no longer saves // supplier/category/assetType — those are derived from the model id via // assetResolver.resolveAssetMetadata at runtime. 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 built by these tests park a periodic status-poll timer. We // drive the BaseNodeAdapter close handler after each test to stop it so // node:test exits cleanly — this is the public teardown path Node-RED // itself uses on flow shutdown. const _adapters = []; function buildAdapter(ui) { const node = makeNodeStub(); const RED = makeREDStub(); const inst = new NodeClass(ui, RED, 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('asset identity flows from legacy editor fields through buildDomainConfig', () => { const { inst } = buildAdapter(makeUiConfig({ uuid: 'uuid-from-editor', assetTagNumber: 'TAG-123' })); assert.equal(inst.source.config.asset.uuid, 'uuid-from-editor'); assert.equal(inst.source.config.asset.tagCode, 'tag-123'); assert.equal(inst.source.config.asset.tagNumber, 'tag-123'); }); test('explicit assetUuid/assetTagCode override legacy editor fields', () => { const { inst } = buildAdapter(makeUiConfig({ uuid: 'legacy-uuid', assetUuid: 'explicit-uuid', assetTagNumber: 'legacy-tag', assetTagCode: 'explicit-tag', })); assert.equal(inst.source.config.asset.uuid, 'explicit-uuid'); assert.equal(inst.source.config.asset.tagCode, 'explicit-tag'); }); test('curveUnits propagate through buildDomainConfig, invalid flow unit falls back', () => { const { inst } = buildAdapter(makeUiConfig({ unit: 'not-a-unit', curvePressureUnit: 'mbar', curveFlowUnit: 'm3/h', curvePowerUnit: 'kW', curveControlUnit: '%', })); assert.equal(inst.source.config.general.unit, 'm3/h'); assert.equal(inst.source.config.asset.unit, 'm3/h'); assert.equal(inst.source.config.asset.curveUnits.pressure, 'mbar'); assert.equal(inst.source.config.asset.curveUnits.flow, 'm3/h'); assert.equal(inst.source.config.asset.curveUnits.power, 'kW'); assert.equal(inst.source.config.asset.curveUnits.control, '%'); }); test('logging.enabled flag reaches the domain via configManager.buildConfig', () => { const { inst } = buildAdapter(makeUiConfig({ enableLog: true })); // uiConfig.enableLog flows through configManager.buildConfig and lands // on the validated source config. (logLevel currently doesn't propagate // — known platform behaviour; not exercised here.) assert.equal(inst.source.config.general.logging.enabled, true); }); test('state machine is wired and exposes its public surface', () => { const { inst } = buildAdapter(makeUiConfig()); // The state machine is constructed during configure() and exposes // observable methods used by the rest of the domain + the status badge. assert.equal(typeof inst.source.state.getCurrentState, 'function'); assert.equal(typeof inst.source.state.getCurrentPosition, 'function'); assert.equal(inst.source.state.getCurrentState(), 'idle'); }); test('default mode is honoured on the constructed source', () => { const { inst } = buildAdapter(makeUiConfig()); assert.equal(typeof inst.source.currentMode, 'string'); assert.ok(inst.source.currentMode.length > 0); });