const test = require('node:test'); const assert = require('node:assert/strict'); const OutputUtils = require('../src/helper/outputUtils.js'); const config = { functionality: { softwareType: 'measurement', role: 'sensor' }, general: { id: 'abc', unit: 'mbar' }, asset: { uuid: 'u1', tagCode: 't1', geoLocation: { lat: 51.6, lon: 4.7 }, category: 'measurement', type: 'pressure', model: 'M1', }, }; test('process format emits message with changed fields only', () => { const out = new OutputUtils(); const first = out.formatMsg({ a: 1, b: 2 }, config, 'process'); assert.equal(first.topic, 'measurement_abc'); assert.deepEqual(first.payload, { a: 1, b: 2 }); const second = out.formatMsg({ a: 1, b: 2 }, config, 'process'); assert.equal(second, null); const third = out.formatMsg({ a: 1, b: 3, c: { x: 1 } }, config, 'process'); assert.deepEqual(third.payload, { b: 3, c: JSON.stringify({ x: 1 }) }); }); test('alwaysEmit fields bypass delta compression (re-emitted while unchanged)', () => { const out = new OutputUtils({ alwaysEmit: ['ctrl'] }); const first = out.formatMsg({ ctrl: 40, flow: 12 }, config, 'influxdb'); assert.deepEqual(first.payload.fields, { ctrl: 40, flow: 12 }); // flow unchanged → dropped; ctrl unchanged but forced → still emitted. const second = out.formatMsg({ ctrl: 40, flow: 12 }, config, 'influxdb'); assert.deepEqual(second.payload.fields, { ctrl: 40 }); // ctrl changed → emitted with its new value. const third = out.formatMsg({ ctrl: 41, flow: 12 }, config, 'influxdb'); assert.deepEqual(third.payload.fields, { ctrl: 41 }); }); test('alwaysEmit is per-format and does not force a missing/undefined field', () => { const out = new OutputUtils({ alwaysEmit: ['ctrl'] }); // ctrl absent from the output → nothing to force; with no other change the // message is suppressed as usual. out.formatMsg({ flow: 5 }, config, 'influxdb'); assert.equal(out.formatMsg({ flow: 5 }, config, 'influxdb'), null); }); test('default OutputUtils keeps pure delta compression (no alwaysEmit)', () => { const out = new OutputUtils(); out.formatMsg({ ctrl: 40 }, config, 'influxdb'); assert.equal(out.formatMsg({ ctrl: 40 }, config, 'influxdb'), null); }); test('influx format flattens tags and stringifies tag values', () => { const out = new OutputUtils(); const msg = out.formatMsg({ value: 10 }, config, 'influxdb'); assert.equal(msg.topic, 'measurement_abc'); assert.equal(msg.payload.measurement, 'measurement_abc'); assert.equal(msg.payload.tags.geoLocation_lat, '51.6'); assert.equal(msg.payload.tags.geoLocation_lon, '4.7'); assert.equal(msg.payload.tags.tagcode, 't1'); assert.ok(msg.payload.timestamp instanceof Date); }); test('influx format omits tags whose config value is unset', () => { const out = new OutputUtils(); // No asset block at all: uuid/tagcode/geoLocation/category/type/model are // all undefined and must NOT appear as `="undefined"` tags. const sparse = { functionality: { softwareType: 'measurement' }, general: { id: 'abc' }, }; const msg = out.formatMsg({ value: 10 }, sparse, 'influxdb'); for (const t of ['geoLocation', 'category', 'type', 'model', 'uuid', 'tagcode', 'unit', 'role']) { assert.ok(!(t in msg.payload.tags), `tag "${t}" should be omitted when unset, got "${msg.payload.tags[t]}"`); } // Tags that DO have values still come through. assert.equal(msg.payload.tags.id, 'abc'); assert.equal(msg.payload.tags.softwareType, 'measurement'); // Nothing should stringify to the literal "undefined". for (const v of Object.values(msg.payload.tags)) { assert.notEqual(v, 'undefined'); } }); test('influx format drops empty-string tag values too', () => { const out = new OutputUtils(); const cfg = { functionality: { softwareType: 'pump', role: '' }, general: { id: 'p1' }, asset: { category: '', model: 'M9' }, }; const msg = out.formatMsg({ value: 1 }, cfg, 'influxdb'); assert.ok(!('role' in msg.payload.tags)); assert.ok(!('category' in msg.payload.tags)); assert.equal(msg.payload.tags.model, 'M9'); });