From 9e0e3e3859e02ee2fe94c7cb1734c219603b5ccb Mon Sep 17 00:00:00 2001 From: znetsixe <73483679+znetsixe@users.noreply.github.com> Date: Thu, 19 Feb 2026 17:37:21 +0100 Subject: [PATCH] before functional changes by codex --- examples/README.md | 21 +++ examples/basic.flow.json | 111 ++++++++++++++ examples/edge.flow.json | 120 +++++++++++++++ examples/integration.flow.json | 142 ++++++++++++++++++ package.json | 2 +- test/README.md | 21 +++ test/basic/.gitkeep | 0 test/basic/nodeclass-routing.basic.test.js | 54 +++++++ test/basic/scaling-and-output.basic.test.js | 25 +++ test/basic/specific-constructor.basic.test.js | 16 ++ test/edge/.gitkeep | 0 test/edge/invalid-payload.edge.test.js | 28 ++++ test/edge/outlier-toggle.edge.test.js | 12 ++ test/helpers/.gitkeep | 0 test/helpers/factories.js | 111 ++++++++++++++ test/integration/.gitkeep | 0 .../examples-flows.integration.test.js | 48 ++++++ .../measurement-event.integration.test.js | 37 +++++ 18 files changed, 747 insertions(+), 1 deletion(-) create mode 100644 examples/README.md create mode 100644 examples/basic.flow.json create mode 100644 examples/edge.flow.json create mode 100644 examples/integration.flow.json create mode 100644 test/README.md create mode 100644 test/basic/.gitkeep create mode 100644 test/basic/nodeclass-routing.basic.test.js create mode 100644 test/basic/scaling-and-output.basic.test.js create mode 100644 test/basic/specific-constructor.basic.test.js create mode 100644 test/edge/.gitkeep create mode 100644 test/edge/invalid-payload.edge.test.js create mode 100644 test/edge/outlier-toggle.edge.test.js create mode 100644 test/helpers/.gitkeep create mode 100644 test/helpers/factories.js create mode 100644 test/integration/.gitkeep create mode 100644 test/integration/examples-flows.integration.test.js create mode 100644 test/integration/measurement-event.integration.test.js diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..a20044e --- /dev/null +++ b/examples/README.md @@ -0,0 +1,21 @@ +# Measurement Example Flows + +These flows are import-ready Node-RED examples for the `measurement` node. + +## Files +- `basic.flow.json` +Purpose: basic measurement injection and output inspection. + +- `integration.flow.json` +Purpose: parent/child registration and periodic measurement updates. + +- `edge.flow.json` +Purpose: invalid/edge payload driving for robustness checks. + +## Requirements +- EVOLV `measurement` node available in Node-RED. + +## Import +1. Open Node-RED import. +2. Import one `*.flow.json` file. +3. Deploy and inspect debug output. diff --git a/examples/basic.flow.json b/examples/basic.flow.json new file mode 100644 index 0000000..87c8d84 --- /dev/null +++ b/examples/basic.flow.json @@ -0,0 +1,111 @@ +[ + { + "id": "m_tab_basic_1", + "type": "tab", + "label": "Measurement Basic", + "disabled": false, + "info": "Basic measurement flow" + }, + { + "id": "m_basic_node", + "type": "measurement", + "z": "m_tab_basic_1", + "name": "M Basic", + "scaling": true, + "i_min": 0, + "i_max": 100, + "i_offset": 0, + "o_min": 0, + "o_max": 10, + "simulator": false, + "smooth_method": "mean", + "count": 5, + "uuid": "", + "supplier": "vendor", + "category": "sensor", + "assetType": "pressure", + "model": "PT-1", + "unit": "bar", + "assetTagNumber": "PT-001", + "enableLog": false, + "logLevel": "error", + "positionVsParent": "atEquipment", + "positionIcon": "", + "hasDistance": false, + "distance": 0, + "distanceUnit": "m", + "distanceDescription": "", + "x": 510, + "y": 220, + "wires": [["m_basic_dbg_process"],["m_basic_dbg_influx"],["m_basic_dbg_parent"]] + }, + { + "id": "m_basic_inject_measurement", + "type": "inject", + "z": "m_tab_basic_1", + "name": "measurement 42", + "props": [{"p": "topic", "vt": "str"},{"p": "payload", "vt": "num"}], + "topic": "measurement", + "payload": "42", + "payloadType": "num", + "x": 170, + "y": 220, + "wires": [["m_basic_node"]] + }, + { + "id": "m_basic_inject_calibrate", + "type": "inject", + "z": "m_tab_basic_1", + "name": "calibrate", + "props": [{"p": "topic", "vt": "str"}], + "topic": "calibrate", + "x": 140, + "y": 170, + "wires": [["m_basic_node"]] + }, + { + "id": "m_basic_dbg_process", + "type": "debug", + "z": "m_tab_basic_1", + "name": "M process", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "x": 750, + "y": 180, + "wires": [] + }, + { + "id": "m_basic_dbg_influx", + "type": "debug", + "z": "m_tab_basic_1", + "name": "M influx", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "x": 740, + "y": 220, + "wires": [] + }, + { + "id": "m_basic_dbg_parent", + "type": "debug", + "z": "m_tab_basic_1", + "name": "M parent", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "x": 740, + "y": 260, + "wires": [] + } +] diff --git a/examples/edge.flow.json b/examples/edge.flow.json new file mode 100644 index 0000000..e126c86 --- /dev/null +++ b/examples/edge.flow.json @@ -0,0 +1,120 @@ +[ + { + "id": "m_tab_edge_1", + "type": "tab", + "label": "Measurement Edge", + "disabled": false, + "info": "Edge-case measurement flow" + }, + { + "id": "m_edge_node", + "type": "measurement", + "z": "m_tab_edge_1", + "name": "M Edge", + "scaling": true, + "i_min": 0, + "i_max": 100, + "i_offset": 0, + "o_min": 0, + "o_max": 10, + "simulator": false, + "smooth_method": "mean", + "count": 5, + "supplier": "vendor", + "category": "sensor", + "assetType": "pressure", + "model": "PT-E", + "unit": "bar", + "positionVsParent": "atEquipment", + "hasDistance": false, + "distance": 0, + "distanceUnit": "m", + "enableLog": false, + "logLevel": "error", + "x": 510, + "y": 220, + "wires": [["m_edge_dbg_process"],["m_edge_dbg_influx"],["m_edge_dbg_parent"]] + }, + { + "id": "m_edge_bad_payload", + "type": "inject", + "z": "m_tab_edge_1", + "name": "measurement bad payload", + "props": [{"p": "topic", "vt": "str"},{"p": "payload", "vt": "str"}], + "topic": "measurement", + "payload": "not-a-number", + "payloadType": "str", + "x": 170, + "y": 170, + "wires": [["m_edge_node"]] + }, + { + "id": "m_edge_toggle_outlier", + "type": "inject", + "z": "m_tab_edge_1", + "name": "toggle outlier", + "props": [{"p": "topic", "vt": "str"}], + "topic": "outlierDetection", + "x": 140, + "y": 220, + "wires": [["m_edge_node"]] + }, + { + "id": "m_edge_unknown_topic", + "type": "inject", + "z": "m_tab_edge_1", + "name": "unknown topic", + "props": [{"p": "topic", "vt": "str"},{"p": "payload", "vt": "num"}], + "topic": "doesNotExist", + "payload": "1", + "payloadType": "num", + "x": 150, + "y": 270, + "wires": [["m_edge_node"]] + }, + { + "id": "m_edge_dbg_process", + "type": "debug", + "z": "m_tab_edge_1", + "name": "M edge process", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "x": 750, + "y": 180, + "wires": [] + }, + { + "id": "m_edge_dbg_influx", + "type": "debug", + "z": "m_tab_edge_1", + "name": "M edge influx", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "x": 740, + "y": 220, + "wires": [] + }, + { + "id": "m_edge_dbg_parent", + "type": "debug", + "z": "m_tab_edge_1", + "name": "M edge parent", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "x": 740, + "y": 260, + "wires": [] + } +] diff --git a/examples/integration.flow.json b/examples/integration.flow.json new file mode 100644 index 0000000..7997bb5 --- /dev/null +++ b/examples/integration.flow.json @@ -0,0 +1,142 @@ +[ + { + "id": "m_tab_int_1", + "type": "tab", + "label": "Measurement Integration", + "disabled": false, + "info": "Integration-oriented measurement flow" + }, + { + "id": "m_int_parent", + "type": "measurement", + "z": "m_tab_int_1", + "name": "M Parent", + "scaling": true, + "i_min": 0, + "i_max": 100, + "i_offset": 0, + "o_min": 0, + "o_max": 10, + "simulator": false, + "smooth_method": "mean", + "count": 5, + "supplier": "vendor", + "category": "sensor", + "assetType": "pressure", + "model": "PT-P", + "unit": "bar", + "positionVsParent": "atEquipment", + "hasDistance": false, + "distance": 0, + "distanceUnit": "m", + "enableLog": false, + "logLevel": "error", + "x": 560, + "y": 220, + "wires": [["m_int_dbg_process"],["m_int_dbg_influx"],["m_int_dbg_parent"]] + }, + { + "id": "m_int_child", + "type": "measurement", + "z": "m_tab_int_1", + "name": "M Child", + "scaling": true, + "i_min": 0, + "i_max": 100, + "i_offset": 0, + "o_min": 0, + "o_max": 10, + "simulator": false, + "smooth_method": "none", + "count": 3, + "supplier": "vendor", + "category": "sensor", + "assetType": "pressure", + "model": "PT-C", + "unit": "bar", + "positionVsParent": "upstream", + "hasDistance": true, + "distance": 5, + "distanceUnit": "m", + "enableLog": false, + "logLevel": "error", + "x": 560, + "y": 360, + "wires": [[],[],[]] + }, + { + "id": "m_int_register_child", + "type": "inject", + "z": "m_tab_int_1", + "name": "register child", + "props": [ + {"p": "topic", "vt": "str"}, + {"p": "payload", "vt": "str"}, + {"p": "positionVsParent", "v": "upstream", "vt": "str"} + ], + "topic": "registerChild", + "payload": "m_int_child", + "payloadType": "str", + "x": 150, + "y": 180, + "wires": [["m_int_parent"]] + }, + { + "id": "m_int_measurement", + "type": "inject", + "z": "m_tab_int_1", + "name": "measurement 55", + "props": [{"p": "topic", "vt": "str"},{"p": "payload", "vt": "num"}], + "topic": "measurement", + "payload": "55", + "payloadType": "num", + "x": 150, + "y": 240, + "wires": [["m_int_parent"]] + }, + { + "id": "m_int_dbg_process", + "type": "debug", + "z": "m_tab_int_1", + "name": "M int process", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "x": 810, + "y": 180, + "wires": [] + }, + { + "id": "m_int_dbg_influx", + "type": "debug", + "z": "m_tab_int_1", + "name": "M int influx", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "x": 800, + "y": 220, + "wires": [] + }, + { + "id": "m_int_dbg_parent", + "type": "debug", + "z": "m_tab_int_1", + "name": "M int parent", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "x": 800, + "y": 260, + "wires": [] + } +] diff --git a/package.json b/package.json index 724d18d..56ca664 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Control module measurement", "main": "measurement.js", "scripts": { - "test": "node measurement.js" + "test": "node --test test/basic/*.test.js test/integration/*.test.js test/edge/*.test.js" }, "repository": { "type": "git", diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..e23d833 --- /dev/null +++ b/test/README.md @@ -0,0 +1,21 @@ +# Measurement Test Suite Layout + +This folder follows EVOLV standard node test structure. + +## Required folders +- `basic/` +- `integration/` +- `edge/` +- `helpers/` + +## Baseline files +- `basic/specific-constructor.basic.test.js` +- `basic/scaling-and-output.basic.test.js` +- `basic/nodeclass-routing.basic.test.js` +- `integration/examples-flows.integration.test.js` +- `integration/measurement-event.integration.test.js` +- `edge/invalid-payload.edge.test.js` +- `edge/outlier-toggle.edge.test.js` + +Authoritative mapping for coverage intent lives in: +- `.agents/function-anchors/measurement/EVIDENCE-measurement-tests.md` diff --git a/test/basic/.gitkeep b/test/basic/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/basic/nodeclass-routing.basic.test.js b/test/basic/nodeclass-routing.basic.test.js new file mode 100644 index 0000000..416b87f --- /dev/null +++ b/test/basic/nodeclass-routing.basic.test.js @@ -0,0 +1,54 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); + +const NodeClass = require('../../src/nodeClass'); +const { makeNodeStub, makeREDStub } = require('../helpers/factories'); + +test('_attachInputHandler routes known topics to source methods', () => { + const inst = Object.create(NodeClass.prototype); + const node = makeNodeStub(); + const calls = []; + + inst.node = node; + inst.RED = makeREDStub(); + inst.source = { + toggleSimulation() { calls.push('simulator'); }, + toggleOutlierDetection() { calls.push('outlierDetection'); }, + calibrate() { calls.push('calibrate'); }, + set inputValue(v) { calls.push(['measurement', v]); }, + }; + + inst._attachInputHandler(); + const onInput = node._handlers.input; + + onInput({ topic: 'simulator' }, () => {}, () => {}); + onInput({ topic: 'outlierDetection' }, () => {}, () => {}); + onInput({ topic: 'calibrate' }, () => {}, () => {}); + onInput({ topic: 'measurement', payload: 12.3 }, () => {}, () => {}); + + assert.deepEqual(calls[0], 'simulator'); + assert.deepEqual(calls[1], 'outlierDetection'); + assert.deepEqual(calls[2], 'calibrate'); + assert.deepEqual(calls[3], ['measurement', 12.3]); +}); + +test('_registerChild emits delayed registerChild message on output 2', () => { + const inst = Object.create(NodeClass.prototype); + const node = makeNodeStub(); + + inst.node = node; + inst.config = { functionality: { positionVsParent: 'upstream', distance: 5 } }; + + const originalSetTimeout = global.setTimeout; + global.setTimeout = (fn) => { fn(); return 1; }; + try { + inst._registerChild(); + } finally { + global.setTimeout = originalSetTimeout; + } + + assert.equal(node._sent.length, 1); + assert.equal(node._sent[0][2].topic, 'registerChild'); + assert.equal(node._sent[0][2].positionVsParent, 'upstream'); + assert.equal(node._sent[0][2].distance, 5); +}); diff --git a/test/basic/scaling-and-output.basic.test.js b/test/basic/scaling-and-output.basic.test.js new file mode 100644 index 0000000..4ace152 --- /dev/null +++ b/test/basic/scaling-and-output.basic.test.js @@ -0,0 +1,25 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); + +const { makeMeasurementInstance } = require('../helpers/factories'); + +test('calculateInput applies scaling and updates bounded output', () => { + const m = makeMeasurementInstance(); + + m.calculateInput(50); + const out = m.getOutput(); + + assert.equal(out.mAbs >= 0 && out.mAbs <= 10, true); + assert.equal(out.mPercent >= 0 && out.mPercent <= 100, true); +}); + +test('out-of-range input is constrained to abs range', () => { + const m = makeMeasurementInstance({ + smoothing: { smoothWindow: 1, smoothMethod: 'none' }, + }); + + m.calculateInput(10000); + const out = m.getOutput(); + + assert.equal(out.mAbs, 10); +}); diff --git a/test/basic/specific-constructor.basic.test.js b/test/basic/specific-constructor.basic.test.js new file mode 100644 index 0000000..a0043d7 --- /dev/null +++ b/test/basic/specific-constructor.basic.test.js @@ -0,0 +1,16 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); + +const { makeMeasurementInstance } = require('../helpers/factories'); + +test('Measurement constructor initializes key defaults and ranges', () => { + const m = makeMeasurementInstance(); + + assert.equal(m.inputValue, 0); + assert.equal(m.outputAbs, 0); + assert.equal(m.outputPercent, 0); + assert.equal(Array.isArray(m.storedValues), true); + assert.equal(typeof m.measurements, 'object'); + assert.equal(m.inputRange, 100); + assert.equal(m.processRange, 10); +}); diff --git a/test/edge/.gitkeep b/test/edge/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/edge/invalid-payload.edge.test.js b/test/edge/invalid-payload.edge.test.js new file mode 100644 index 0000000..4938f11 --- /dev/null +++ b/test/edge/invalid-payload.edge.test.js @@ -0,0 +1,28 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); + +const NodeClass = require('../../src/nodeClass'); +const { makeNodeStub, makeREDStub } = require('../helpers/factories'); + +test('measurement topic ignores non-number payloads (current behavior)', () => { + const inst = Object.create(NodeClass.prototype); + const node = makeNodeStub(); + const calls = []; + + inst.node = node; + inst.RED = makeREDStub(); + inst.source = { + set inputValue(v) { calls.push(v); }, + toggleSimulation() {}, + toggleOutlierDetection() {}, + calibrate() {}, + }; + + inst._attachInputHandler(); + const onInput = node._handlers.input; + + onInput({ topic: 'measurement', payload: '42' }, () => {}, () => {}); + onInput({ topic: 'measurement', payload: { value: 42 } }, () => {}, () => {}); + + assert.equal(calls.length, 0); +}); diff --git a/test/edge/outlier-toggle.edge.test.js b/test/edge/outlier-toggle.edge.test.js new file mode 100644 index 0000000..47a3f57 --- /dev/null +++ b/test/edge/outlier-toggle.edge.test.js @@ -0,0 +1,12 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); + +const { makeMeasurementInstance } = require('../helpers/factories'); + +test('toggleOutlierDetection currently converts config object to boolean (known gap)', () => { + const m = makeMeasurementInstance(); + + assert.equal(typeof m.config.outlierDetection, 'object'); + m.toggleOutlierDetection(); + assert.equal(typeof m.config.outlierDetection, 'boolean'); +}); diff --git a/test/helpers/.gitkeep b/test/helpers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/helpers/factories.js b/test/helpers/factories.js new file mode 100644 index 0000000..a3522d2 --- /dev/null +++ b/test/helpers/factories.js @@ -0,0 +1,111 @@ +const Measurement = require('../../src/specificClass'); + +function makeUiConfig(overrides = {}) { + return { + unit: 'bar', + enableLog: false, + logLevel: 'error', + supplier: 'vendor', + category: 'sensor', + assetType: 'pressure', + model: 'PT-1', + scaling: true, + i_min: 0, + i_max: 100, + o_min: 0, + o_max: 10, + i_offset: 0, + count: 5, + smooth_method: 'mean', + simulator: false, + positionVsParent: 'atEquipment', + hasDistance: false, + distance: 0, + ...overrides, + }; +} + +function makeMeasurementConfig(overrides = {}) { + return { + general: { + id: 'm-test-1', + name: 'measurement-test', + unit: 'bar', + logging: { enabled: false, logLevel: 'error' }, + }, + asset: { + uuid: '', + tagCode: '', + tagNumber: 'PT-001', + supplier: 'vendor', + category: 'sensor', + type: 'pressure', + model: 'PT-1', + unit: 'bar', + }, + scaling: { + enabled: true, + inputMin: 0, + inputMax: 100, + absMin: 0, + absMax: 10, + offset: 0, + }, + smoothing: { + smoothWindow: 5, + smoothMethod: 'mean', + }, + simulation: { + enabled: false, + }, + functionality: { + positionVsParent: 'atEquipment', + distance: undefined, + }, + ...overrides, + }; +} + +function makeNodeStub() { + const handlers = {}; + const sent = []; + const status = []; + const warns = []; + return { + id: 'm-node-1', + source: null, + on(event, cb) { handlers[event] = cb; }, + send(msg) { sent.push(msg); }, + status(s) { status.push(s); }, + warn(w) { warns.push(w); }, + _handlers: handlers, + _sent: sent, + _status: status, + _warns: warns, + }; +} + +function makeREDStub(nodeMap = {}) { + return { + nodes: { + getNode(id) { + return nodeMap[id] || null; + }, + createNode() {}, + registerType() {}, + }, + httpAdmin: { get() {}, post() {} }, + }; +} + +function makeMeasurementInstance(overrides = {}) { + return new Measurement(makeMeasurementConfig(overrides)); +} + +module.exports = { + makeUiConfig, + makeMeasurementConfig, + makeNodeStub, + makeREDStub, + makeMeasurementInstance, +}; diff --git a/test/integration/.gitkeep b/test/integration/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/integration/examples-flows.integration.test.js b/test/integration/examples-flows.integration.test.js new file mode 100644 index 0000000..8133247 --- /dev/null +++ b/test/integration/examples-flows.integration.test.js @@ -0,0 +1,48 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const EXAMPLES_DIR = path.resolve(__dirname, '../../examples'); + +function readFlow(file) { + const full = path.join(EXAMPLES_DIR, file); + const parsed = JSON.parse(fs.readFileSync(full, 'utf8')); + assert.equal(Array.isArray(parsed), true); + return parsed; +} + +function nodesByType(flow, type) { + return flow.filter((n) => n && n.type === type); +} + +function injectByTopic(flow, topic) { + return flow.filter((n) => n && n.type === 'inject' && n.topic === topic); +} + +test('examples package contains required files', () => { + for (const name of ['README.md', 'basic.flow.json', 'integration.flow.json', 'edge.flow.json']) { + assert.equal(fs.existsSync(path.join(EXAMPLES_DIR, name)), true, `${name} missing`); + } +}); + +test('basic flow has measurement node and baseline injects', () => { + const flow = readFlow('basic.flow.json'); + assert.equal(nodesByType(flow, 'measurement').length >= 1, true); + assert.equal(injectByTopic(flow, 'measurement').length >= 1, true); + assert.equal(injectByTopic(flow, 'calibrate').length >= 1, true); +}); + +test('integration flow has two measurement nodes and registerChild example', () => { + const flow = readFlow('integration.flow.json'); + assert.equal(nodesByType(flow, 'measurement').length >= 2, true); + assert.equal(injectByTopic(flow, 'registerChild').length >= 1, true); + assert.equal(injectByTopic(flow, 'measurement').length >= 1, true); +}); + +test('edge flow contains edge-driving injects', () => { + const flow = readFlow('edge.flow.json'); + assert.equal(injectByTopic(flow, 'measurement').length >= 1, true); + assert.equal(injectByTopic(flow, 'outlierDetection').length >= 1, true); + assert.equal(injectByTopic(flow, 'doesNotExist').length >= 1, true); +}); diff --git a/test/integration/measurement-event.integration.test.js b/test/integration/measurement-event.integration.test.js new file mode 100644 index 0000000..9ed0916 --- /dev/null +++ b/test/integration/measurement-event.integration.test.js @@ -0,0 +1,37 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); + +const { makeMeasurementInstance } = require('../helpers/factories'); + +test('updateOutputAbs emits measurement event with configured type/position', async () => { + const m = makeMeasurementInstance({ + asset: { + uuid: '', + tagCode: '', + tagNumber: 'PT-001', + supplier: 'vendor', + category: 'sensor', + type: 'pressure', + model: 'PT-1', + unit: 'bar', + }, + functionality: { + positionVsParent: 'upstream', + distance: undefined, + }, + smoothing: { + smoothWindow: 1, + smoothMethod: 'none', + }, + }); + + const event = await new Promise((resolve) => { + m.measurements.emitter.once('pressure.measured.upstream', resolve); + m.calculateInput(30); + }); + + assert.equal(event.type, 'pressure'); + assert.equal(event.variant, 'measured'); + assert.equal(event.position, 'upstream'); + assert.equal(typeof event.value, 'number'); +});