updates to rotating machine struct
This commit is contained in:
31
test/basic/constructor.basic.test.js
Normal file
31
test/basic/constructor.basic.test.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const Machine = require('../../src/specificClass');
|
||||
const { makeMachineConfig, makeStateConfig } = require('../helpers/factories');
|
||||
|
||||
test('constructor initializes with valid curve model', () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig());
|
||||
assert.equal(machine.hasCurve, true);
|
||||
assert.ok(machine.predictFlow);
|
||||
assert.ok(machine.predictPower);
|
||||
assert.ok(machine.predictCtrl);
|
||||
|
||||
const out = machine.getOutput();
|
||||
assert.ok('state' in out);
|
||||
assert.ok('mode' in out);
|
||||
assert.ok('ctrl' in out);
|
||||
});
|
||||
|
||||
test('constructor handles missing curve model without throwing', () => {
|
||||
const cfg = makeMachineConfig({ asset: { supplier: 'x', category: 'machine', type: 'pump', model: 'not-existing-model', unit: 'm3/h' } });
|
||||
const machine = new Machine(cfg, makeStateConfig());
|
||||
|
||||
assert.equal(machine.hasCurve, false);
|
||||
assert.equal(machine.predictFlow, null);
|
||||
assert.equal(machine.predictPower, null);
|
||||
assert.equal(machine.predictCtrl, null);
|
||||
|
||||
const out = machine.getOutput();
|
||||
assert.ok('state' in out);
|
||||
});
|
||||
35
test/basic/mode-and-input.basic.test.js
Normal file
35
test/basic/mode-and-input.basic.test.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const Machine = require('../../src/specificClass');
|
||||
const { makeMachineConfig, makeStateConfig } = require('../helpers/factories');
|
||||
|
||||
test('setMode changes mode only for allowed values', () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig());
|
||||
const original = machine.currentMode;
|
||||
|
||||
machine.setMode('virtualControl');
|
||||
assert.equal(machine.currentMode, 'virtualControl');
|
||||
|
||||
machine.setMode('invalid-mode');
|
||||
assert.equal(machine.currentMode, 'virtualControl');
|
||||
assert.notEqual(machine.currentMode, original);
|
||||
});
|
||||
|
||||
test('handleInput rejects non-string action safely', async () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig());
|
||||
await assert.doesNotReject(async () => {
|
||||
await machine.handleInput('GUI', 123, null);
|
||||
});
|
||||
});
|
||||
|
||||
test('handleInput ignores disallowed source/action combination', async () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig());
|
||||
machine.setMode('fysicalControl');
|
||||
|
||||
const before = machine.state.getCurrentState();
|
||||
await machine.handleInput('GUI', 'execSequence', 'startup');
|
||||
const after = machine.state.getCurrentState();
|
||||
|
||||
assert.equal(before, after);
|
||||
});
|
||||
31
test/edge/error-paths.edge.test.js
Normal file
31
test/edge/error-paths.edge.test.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const Machine = require('../../src/specificClass');
|
||||
const NodeClass = require('../../src/nodeClass');
|
||||
const { makeMachineConfig, makeStateConfig, makeNodeStub } = require('../helpers/factories');
|
||||
|
||||
test('setpoint rejects negative inputs without throwing', async () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig({ state: { current: 'operational' } }));
|
||||
await assert.doesNotReject(async () => {
|
||||
await machine.setpoint(-1);
|
||||
});
|
||||
});
|
||||
|
||||
test('nodeClass _updateNodeStatus returns error status on internal failure', () => {
|
||||
const inst = Object.create(NodeClass.prototype);
|
||||
const node = makeNodeStub();
|
||||
inst.node = node;
|
||||
inst.source = {
|
||||
currentMode: 'auto',
|
||||
state: {
|
||||
getCurrentState() {
|
||||
throw new Error('boom');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const status = inst._updateNodeStatus();
|
||||
assert.equal(status.text, 'Status Error');
|
||||
assert.equal(node._errors.length, 1);
|
||||
});
|
||||
60
test/edge/nodeClass-routing.edge.test.js
Normal file
60
test/edge/nodeClass-routing.edge.test.js
Normal file
@@ -0,0 +1,60 @@
|
||||
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 };
|
||||
},
|
||||
updateMeasuredPressure(value, position) {
|
||||
calls.push(['updateMeasuredPressure', value, position]);
|
||||
},
|
||||
updateMeasuredFlow(value, position) {
|
||||
calls.push(['updateMeasuredFlow', value, position]);
|
||||
},
|
||||
updateMeasuredTemperature(value, position) {
|
||||
calls.push(['updateMeasuredTemperature', value, position]);
|
||||
},
|
||||
};
|
||||
|
||||
inst._attachInputHandler();
|
||||
const onInput = node._handlers.input;
|
||||
|
||||
onInput({ topic: 'setMode', payload: 'auto' }, () => {}, () => {});
|
||||
onInput({ topic: 'execSequence', payload: { source: 'GUI', action: 'execSequence', parameter: 'startup' } }, () => {}, () => {});
|
||||
onInput({ topic: 'registerChild', payload: 'child1', positionVsParent: 'downstream' }, () => {}, () => {});
|
||||
onInput({ topic: 'simulateMeasurement', payload: { type: 'pressure', position: 'upstream', value: 250, unit: 'mbar' } }, () => {}, () => {});
|
||||
|
||||
assert.deepEqual(calls[0], ['setMode', 'auto']);
|
||||
assert.deepEqual(calls[1], ['handleInput', 'GUI', 'execSequence', 'startup']);
|
||||
assert.deepEqual(calls[2], ['registerChild', { id: 'child-source' }, 'downstream']);
|
||||
assert.deepEqual(calls[3], ['updateMeasuredPressure', 250, 'upstream']);
|
||||
});
|
||||
110
test/helpers/factories.js
Normal file
110
test/helpers/factories.js
Normal file
@@ -0,0 +1,110 @@
|
||||
const { MeasurementContainer } = require('generalFunctions');
|
||||
|
||||
function makeMachineConfig(overrides = {}) {
|
||||
return {
|
||||
general: {
|
||||
id: 'rm-test-1',
|
||||
name: 'rotating-machine-test',
|
||||
unit: 'm3/h',
|
||||
logging: { enabled: false, logLevel: 'error' },
|
||||
},
|
||||
functionality: {
|
||||
positionVsParent: 'atEquipment',
|
||||
},
|
||||
asset: {
|
||||
supplier: 'hidrostal',
|
||||
category: 'machine',
|
||||
type: 'pump',
|
||||
model: 'hidrostal-H05K-S03R',
|
||||
unit: 'm3/h',
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeStateConfig(overrides = {}) {
|
||||
return {
|
||||
general: {
|
||||
logging: { enabled: false, logLevel: 'error' },
|
||||
},
|
||||
state: {
|
||||
current: 'idle',
|
||||
},
|
||||
movement: {
|
||||
mode: 'staticspeed',
|
||||
speed: 1000,
|
||||
maxSpeed: 1000,
|
||||
interval: 10,
|
||||
},
|
||||
time: {
|
||||
starting: 0,
|
||||
warmingup: 0,
|
||||
stopping: 0,
|
||||
coolingdown: 0,
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeChildMeasurement({ id = 'child-1', name = 'PT-1', positionVsParent = 'downstream', type = 'pressure', unit = 'mbar' } = {}) {
|
||||
const measurements = new MeasurementContainer({
|
||||
autoConvert: true,
|
||||
defaultUnits: {
|
||||
pressure: 'mbar',
|
||||
flow: 'm3/h',
|
||||
temperature: 'C',
|
||||
power: 'kW',
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
config: {
|
||||
general: { id, name },
|
||||
functionality: { positionVsParent },
|
||||
asset: { type, unit },
|
||||
},
|
||||
measurements,
|
||||
};
|
||||
}
|
||||
|
||||
function makeNodeStub() {
|
||||
const handlers = {};
|
||||
const sent = [];
|
||||
const statuses = [];
|
||||
const errors = [];
|
||||
const warns = [];
|
||||
return {
|
||||
id: 'node-1',
|
||||
source: null,
|
||||
send(msg) { sent.push(msg); },
|
||||
status(s) { statuses.push(s); },
|
||||
error(e) { errors.push(e); },
|
||||
warn(w) { warns.push(w); },
|
||||
on(event, cb) { handlers[event] = cb; },
|
||||
_handlers: handlers,
|
||||
_sent: sent,
|
||||
_statuses: statuses,
|
||||
_errors: errors,
|
||||
_warns: warns,
|
||||
};
|
||||
}
|
||||
|
||||
function makeREDStub(nodeMap = {}) {
|
||||
return {
|
||||
nodes: {
|
||||
getNode(id) {
|
||||
return nodeMap[id] || null;
|
||||
},
|
||||
createNode() {},
|
||||
registerType() {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
makeMachineConfig,
|
||||
makeStateConfig,
|
||||
makeChildMeasurement,
|
||||
makeNodeStub,
|
||||
makeREDStub,
|
||||
};
|
||||
22
test/integration/coolprop.integration.test.js
Normal file
22
test/integration/coolprop.integration.test.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const Machine = require('../../src/specificClass');
|
||||
const { makeMachineConfig, makeStateConfig } = require('../helpers/factories');
|
||||
|
||||
test('calcEfficiency runs through coolprop path without mocks', () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig({ state: { current: 'operational' } }));
|
||||
|
||||
machine.measurements.type('pressure').variant('measured').position('downstream').value(1200, Date.now(), 'mbar');
|
||||
machine.measurements.type('pressure').variant('measured').position('upstream').value(800, Date.now(), 'mbar');
|
||||
machine.measurements.type('flow').variant('predicted').position('atEquipment').value(120, Date.now(), 'm3/h');
|
||||
machine.measurements.type('power').variant('predicted').position('atEquipment').value(12, Date.now(), 'kW');
|
||||
|
||||
assert.doesNotThrow(() => {
|
||||
machine.calcEfficiency(12, 120, 'predicted');
|
||||
});
|
||||
|
||||
const eff = machine.measurements.type('efficiency').variant('predicted').position('atEquipment').getCurrentValue();
|
||||
assert.equal(typeof eff, 'number');
|
||||
assert.ok(eff > 0);
|
||||
});
|
||||
27
test/integration/registration.integration.test.js
Normal file
27
test/integration/registration.integration.test.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const Machine = require('../../src/specificClass');
|
||||
const { makeMachineConfig, makeStateConfig, makeChildMeasurement } = require('../helpers/factories');
|
||||
|
||||
test('registerChild listens to measurement events and stores measured pressure', async () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig());
|
||||
const child = makeChildMeasurement({ positionVsParent: 'downstream', type: 'pressure', unit: 'mbar' });
|
||||
|
||||
machine.registerChild(child, 'measurement');
|
||||
|
||||
child.measurements
|
||||
.type('pressure')
|
||||
.variant('measured')
|
||||
.position('downstream')
|
||||
.value(123, Date.now(), 'mbar');
|
||||
|
||||
const stored = machine.measurements
|
||||
.type('pressure')
|
||||
.variant('measured')
|
||||
.position('downstream')
|
||||
.getCurrentValue('mbar');
|
||||
|
||||
assert.equal(typeof stored, 'number');
|
||||
assert.equal(Math.round(stored), 123);
|
||||
});
|
||||
22
test/integration/sequences.integration.test.js
Normal file
22
test/integration/sequences.integration.test.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const Machine = require('../../src/specificClass');
|
||||
const { makeMachineConfig, makeStateConfig } = require('../helpers/factories');
|
||||
|
||||
test('execSequence startup reaches operational with zero transition times', async () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig());
|
||||
|
||||
await machine.handleInput('parent', 'execSequence', 'startup');
|
||||
|
||||
assert.equal(machine.state.getCurrentState(), 'operational');
|
||||
});
|
||||
|
||||
test('execMovement updates controller position in operational state', async () => {
|
||||
const machine = new Machine(makeMachineConfig(), makeStateConfig({ state: { current: 'operational' } }));
|
||||
|
||||
await machine.handleInput('parent', 'execMovement', 10);
|
||||
|
||||
const pos = machine.state.getCurrentPosition();
|
||||
assert.ok(pos >= 9.9 && pos <= 10);
|
||||
});
|
||||
Reference in New Issue
Block a user