Files
generalFunctions/test/config-manager.test.js
znetsixe 84a4430266 fix(configManager): deep-merge domainConfig so general.id and asset.model survive
buildConfig() was doing Object.assign(config, domainConfig) — a shallow
merge. When buildDomainConfig returned subsets like {general: {unit}} or
{asset: {curveUnits}}, the assign replaced config.general / config.asset
wholesale, wiping general.id (nodeId), general.name, asset.model, and
asset.supplier that buildConfig had just populated.

Downstream consequences:
- MGC.onRegister('machine') keys by config.general.id; two children with
  the same null id collide and the second registration is rejected.
- rotatingMachine curve-lookup uses asset.model; with model "Unknown" the
  pump curve fails to resolve.

Replace Object.assign with an in-place recursive _deepMerge: arrays and
primitives in src replace dst, plain objects merge key-by-key so siblings
on dst survive.

Two regression tests added — single-config field survival and the MGC
multi-child id-collision case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:06:43 +02:00

76 lines
3.3 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const ConfigManager = require('../src/configs/index.js');
test('buildConfig deep-merges domainConfig so general.id and asset.model survive', () => {
// Regression: previously this used Object.assign (shallow), so a
// buildDomainConfig that returned {general:{unit:'m3/h'}, asset:{curveUnits:...}}
// wiped general.id (→ null via schema default) and asset.model (→ "Unknown"),
// which collapsed MGC child registration (id collision) and broke curve loading.
const manager = new ConfigManager('.');
const uiConfig = {
name: 'PumpA',
model: 'hidrostal-H05K-S03R',
supplier: 'Hidrostal',
category: 'pump',
assetType: 'Centrifugal',
unit: 'm3/h',
};
const domainConfig = {
general: { unit: 'm3/h' },
asset: { curveUnits: { pressure: 'mbar', flow: 'm3/h', power: 'kW', control: '%' } },
};
const cfg = manager.buildConfig('rotatingMachine', uiConfig, 'node-abc-123', domainConfig);
assert.equal(cfg.general.id, 'node-abc-123', 'general.id must come from nodeId');
assert.equal(cfg.general.name, 'PumpA', 'general.name must come from uiConfig.name');
assert.equal(cfg.general.unit, 'm3/h', 'general.unit must come from domainConfig');
assert.equal(cfg.asset.model, 'hidrostal-H05K-S03R', 'asset.model must come from uiConfig.model');
assert.equal(cfg.asset.supplier, 'Hidrostal', 'asset.supplier must survive merge');
assert.deepEqual(cfg.asset.curveUnits, domainConfig.asset.curveUnits,
'asset.curveUnits must come from domainConfig');
});
test('buildConfig: two nodes with same uiConfig keep distinct general.id', () => {
// Regression: MGC.onRegister('machine') keys by config.general.id; if two
// machines collapse to the same id (e.g. null), the second is rejected.
const manager = new ConfigManager('.');
const ui = { model: 'hidrostal-H05K-S03R', supplier: 'Hidrostal', unit: 'm3/h' };
const dom = { general: { unit: 'm3/h' }, asset: { curveUnits: { pressure: 'mbar' } } };
const a = manager.buildConfig('rotatingMachine', ui, 'red-node-A', dom);
const b = manager.buildConfig('rotatingMachine', ui, 'red-node-B', dom);
assert.notEqual(a.general.id, b.general.id);
assert.equal(a.general.id, 'red-node-A');
assert.equal(b.general.id, 'red-node-B');
});
test('can read known config and report existence', () => {
const manager = new ConfigManager('.');
assert.equal(manager.hasConfig('measurement'), true);
const config = manager.getConfig('measurement');
assert.ok(config.functionality);
assert.ok(config.functionality.softwareType);
});
test('getAvailableConfigs includes known names', () => {
const manager = new ConfigManager('.');
const configs = manager.getAvailableConfigs();
assert.ok(configs.includes('measurement'));
assert.ok(configs.includes('rotatingMachine'));
});
test('createEndpoint creates executable JS payload shell', () => {
const manager = new ConfigManager('.');
const script = manager.createEndpoint('measurement');
assert.match(script, /window\.EVOLV\.nodes\.measurement/);
assert.match(script, /config loaded and endpoint created/);
});
test('getConfig throws on missing config', () => {
const manager = new ConfigManager('.');
assert.throws(() => manager.getConfig('definitely-not-real'), /Failed to load config/);
});