Files
measurement/test/basic/commands.basic.test.js

169 lines
6.2 KiB
JavaScript
Raw Normal View History

// Basic tests for the measurement commands registry.
// Run with: node --test test/basic/commands.basic.test.js
'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const { createRegistry } = require('generalFunctions');
const commands = require('../../src/commands');
// --- helpers ---------------------------------------------------------------
function makeLogger() {
const calls = { warn: [], error: [], info: [], debug: [] };
return {
calls,
warn: (m) => calls.warn.push(String(m)),
error: (m) => calls.error.push(String(m)),
info: (m) => calls.info.push(String(m)),
debug: (m) => calls.debug.push(String(m)),
};
}
function makeSource({ mode = 'analog', simulator = false, outlier = false } = {}) {
const calls = {
toggleSimulation: 0,
toggleOutlierDetection: 0,
calibrate: 0,
handleDigitalPayload: [],
inputValueSets: [],
};
const state = { simulator, outlier, _inputValue: 0 };
const source = {
mode,
logger: makeLogger(),
toggleSimulation: () => { state.simulator = !state.simulator; calls.toggleSimulation += 1; },
toggleOutlierDetection: () => { state.outlier = !state.outlier; calls.toggleOutlierDetection += 1; },
calibrate: () => { calls.calibrate += 1; },
handleDigitalPayload: (p) => { calls.handleDigitalPayload.push(p); return { ok: true }; },
get inputValue() { return state._inputValue; },
set inputValue(v) { state._inputValue = v; calls.inputValueSets.push(v); },
};
return { source, calls, state };
}
function makeCtx({ logger = makeLogger() } = {}) {
return { logger, RED: { nodes: { getNode: () => undefined } }, node: {}, send: () => {} };
}
function makeRegistry(logger) {
return createRegistry(commands, { logger });
}
// --- tests -----------------------------------------------------------------
test('canonical topics dispatch to the right handler', async () => {
const { source, calls, state } = makeSource();
const reg = makeRegistry(makeLogger());
await reg.dispatch({ topic: 'set.simulator' }, source, makeCtx());
assert.equal(calls.toggleSimulation, 1);
assert.equal(state.simulator, true);
await reg.dispatch({ topic: 'set.outlier-detection' }, source, makeCtx());
assert.equal(calls.toggleOutlierDetection, 1);
assert.equal(state.outlier, true);
await reg.dispatch({ topic: 'cmd.calibrate' }, source, makeCtx());
assert.equal(calls.calibrate, 1);
});
test('aliases dispatch to the same handler and log a one-time deprecation', async () => {
const { source, calls } = makeSource();
const ctxLogger = makeLogger();
const reg = makeRegistry(ctxLogger);
for (const alias of ['simulator', 'outlierDetection', 'calibrate', 'measurement']) {
await reg.dispatch({ topic: alias, payload: 1 }, source, makeCtx({ logger: ctxLogger }));
await reg.dispatch({ topic: alias, payload: 2 }, source, makeCtx({ logger: ctxLogger }));
}
for (const alias of ['simulator', 'outlierDetection', 'calibrate', 'measurement']) {
const hits = ctxLogger.calls.warn.filter((m) => m.includes(`'${alias}' is deprecated`));
assert.equal(hits.length, 1, `alias '${alias}' should warn exactly once`);
}
// sanity: side-effects fired twice per alias.
assert.equal(calls.toggleSimulation, 2);
assert.equal(calls.toggleOutlierDetection, 2);
assert.equal(calls.calibrate, 2);
// analog measurement alias with numeric payload set inputValue twice.
assert.deepEqual(calls.inputValueSets, [1, 2]);
});
test('data.measurement analog with numeric payload sets source.inputValue', async () => {
const { source, calls } = makeSource({ mode: 'analog' });
const reg = makeRegistry(makeLogger());
await reg.dispatch({ topic: 'data.measurement', payload: 42 }, source, makeCtx());
await reg.dispatch({ topic: 'data.measurement', payload: '3.5' }, source, makeCtx());
assert.deepEqual(calls.inputValueSets, [42, 3.5]);
});
test('data.measurement analog with object payload logs helpful switch-mode warn', async () => {
const { source, calls } = makeSource({ mode: 'analog' });
const ctxLogger = makeLogger();
const reg = makeRegistry(ctxLogger);
await reg.dispatch(
{ topic: 'data.measurement', payload: { temperature: 21.5, humidity: 45 } },
source,
makeCtx({ logger: ctxLogger })
);
assert.equal(calls.inputValueSets.length, 0);
assert.equal(calls.handleDigitalPayload.length, 0);
assert.ok(
ctxLogger.calls.warn.some((m) => m.includes('analog mode') && m.includes('digital')),
`expected helpful switch-to-digital warn, got: ${JSON.stringify(ctxLogger.calls.warn)}`
);
});
test('data.measurement digital with object payload calls handleDigitalPayload', async () => {
const { source, calls } = makeSource({ mode: 'digital' });
const reg = makeRegistry(makeLogger());
const payload = { tempA: 21.5, tempB: 19.8 };
await reg.dispatch({ topic: 'data.measurement', payload }, source, makeCtx());
assert.equal(calls.handleDigitalPayload.length, 1);
assert.deepEqual(calls.handleDigitalPayload[0], payload);
assert.equal(calls.inputValueSets.length, 0);
});
test('data.measurement digital with number logs helpful switch-mode warn', async () => {
const { source, calls } = makeSource({ mode: 'digital' });
const ctxLogger = makeLogger();
const reg = makeRegistry(ctxLogger);
await reg.dispatch(
{ topic: 'data.measurement', payload: 7 },
source,
makeCtx({ logger: ctxLogger })
);
assert.equal(calls.handleDigitalPayload.length, 0);
assert.equal(calls.inputValueSets.length, 0);
assert.ok(
ctxLogger.calls.warn.some((m) => m.includes('digital mode') && m.includes('analog')),
`expected helpful switch-to-analog warn, got: ${JSON.stringify(ctxLogger.calls.warn)}`
);
});
test('set.simulator toggles even with no payload (idempotent flip)', async () => {
const { source, calls, state } = makeSource({ simulator: false });
const reg = makeRegistry(makeLogger());
await reg.dispatch({ topic: 'set.simulator' }, source, makeCtx());
assert.equal(state.simulator, true);
await reg.dispatch({ topic: 'set.simulator' }, source, makeCtx());
assert.equal(state.simulator, false);
await reg.dispatch({ topic: 'set.simulator' }, source, makeCtx());
assert.equal(state.simulator, true);
assert.equal(calls.toggleSimulation, 3);
});