'use strict'; const test = require('node:test'); const assert = require('node:assert/strict'); const Machine = require('../../src/specificClass'); const { buildQHCurve } = require('../../src/display/workingCurves'); const { makeMachineConfig, makeStateConfig } = require('../helpers/factories'); async function makeRunningMachine() { const cfg = makeMachineConfig({ general: { id: 'rm-qh', name: 'qh-test', unit: 'm3/h', logging: { enabled: false, logLevel: 'error' } }, asset: { supplier: 'hidrostal', category: 'pump', type: 'Centrifugal', model: 'hidrostal-H05K-S03R', unit: 'm3/h', curveUnits: { pressure: 'mbar', flow: 'm3/h', power: 'kW', control: '%' }, }, }); const m = new Machine(cfg, makeStateConfig()); await m.handleInput('parent', 'execSequence', 'startup'); m.updateMeasuredPressure(0, 'upstream', { unit: 'mbar', timestamp: Date.now(), childName: 'pt-up' }); m.updateMeasuredPressure(1500, 'downstream', { unit: 'mbar', timestamp: Date.now(), childName: 'pt-down' }); await m.handleInput('parent', 'execMovement', 60); return m; } test('buildQHCurve returns one (Q, H) point per pressure slice in envelope', async () => { const m = await makeRunningMachine(); const r = buildQHCurve(m, 60); assert.ok(!r.error, `should not error, got ${r.error}`); assert.ok(Array.isArray(r.points) && r.points.length > 0, 'must return points array'); for (const pt of r.points) { assert.ok(Number.isFinite(pt.Q), `Q must be finite, got ${pt.Q}`); assert.ok(Number.isFinite(pt.H), `H must be finite, got ${pt.H}`); assert.ok(pt.Q > 0, `Q must be > 0, got ${pt.Q}`); assert.ok(pt.H > 0, `H must be > 0, got ${pt.H}`); } // Centrifugal pump: as head rises (higher pressure slice), flow drops. // Verify monotone non-increasing Q across rising H. const sortedByH = [...r.points].sort((a, b) => a.H - b.H); for (let i = 1; i < sortedByH.length; i++) { assert.ok( sortedByH[i].Q <= sortedByH[i - 1].Q * 1.01 + 1e-6, `flow should be non-increasing as head rises: ${JSON.stringify(sortedByH)}`, ); } }); test('buildQHCurve does not mutate predictor state', async () => { const m = await makeRunningMachine(); const beforeF = m.predictFlow.fDimension; const beforeX = m.predictFlow.currentX; const beforeOutputY = m.predictFlow.outputY; buildQHCurve(m, 60); assert.equal(m.predictFlow.fDimension, beforeF, 'fDimension must be restored'); assert.equal(m.predictFlow.currentX, beforeX, 'currentX must be restored'); assert.ok( Math.abs(m.predictFlow.outputY - beforeOutputY) < 1e-9, `outputY must be restored, before=${beforeOutputY} after=${m.predictFlow.outputY}`, ); }); test('buildQHCurve handles no-curve gracefully', () => { const r = buildQHCurve({ hasCurve: false }, 50); assert.ok(r.error, 'must report error'); assert.deepEqual(r.points, []); }); test('buildQHCurve uses current ctrl when none provided', async () => { const m = await makeRunningMachine(); const r = buildQHCurve(m); assert.equal(r.ctrlPct, m.predictFlow.currentX, `ctrlPct should default to current x, got ${r.ctrlPct} vs ${m.predictFlow.currentX}`); });