Files
monster/test/monster.specific.test.js
znetsixe 6b58dd4bd5 upgrades
2026-01-20 20:47:19 +01:00

258 lines
6.8 KiB
JavaScript

const assert = require('assert');
const fs = require('fs');
const path = require('path');
const Monster = require('../src/specificClass');
const { MeasurementContainer } = require('generalFunctions');
function test(name, fn) {
try {
fn();
console.log(`ok - ${name}`);
} catch (err) {
console.error(`not ok - ${name}`);
console.error(err);
process.exitCode = 1;
}
}
function withMockedDate(iso, fn) {
const RealDate = Date;
let now = new RealDate(iso).getTime();
class MockDate extends RealDate {
constructor(...args) {
if (args.length === 0) {
super(now);
} else {
super(...args);
}
}
static now() {
return now;
}
}
global.Date = MockDate;
try {
return fn({
advance(ms) {
now += ms;
}
});
} finally {
global.Date = RealDate;
}
}
function buildConfig(overrides = {}) {
return {
general: {
name: 'Monster Test',
logging: { enabled: false, logLevel: 'error' }
},
asset: {
emptyWeightBucket: 3
},
constraints: {
samplingtime: 1,
minVolume: 5,
maxWeight: 23,
nominalFlowMin: 1,
flowMax: 10
},
...overrides
};
}
function parseMonsternametijdenCsv(filePath) {
const raw = fs.readFileSync(filePath, 'utf8').trim();
const lines = raw.split(/\r?\n/);
const header = lines.shift();
const columns = header.split(',');
return lines
.filter((line) => line && !line.startsWith('-----------'))
.map((line) => {
const parts = [];
let cur = '';
let inQ = false;
for (let i = 0; i < line.length; i++) {
const ch = line[i];
if (ch === '"') {
inQ = !inQ;
continue;
}
if (ch === ',' && !inQ) {
parts.push(cur);
cur = '';
} else {
cur += ch;
}
}
parts.push(cur);
const obj = {};
columns.forEach((col, idx) => {
obj[col] = parts[idx];
});
return obj;
});
}
test('measured + manual flow averages into effective flow', () => {
withMockedDate('2024-10-15T00:00:00Z', ({ advance }) => {
const monster = new Monster(buildConfig());
const child = {
config: {
general: { id: 'child-1', name: 'FlowSensor' },
asset: { type: 'flow' }
},
measurements: new MeasurementContainer({
autoConvert: true,
defaultUnits: { flow: 'm3/h' }
})
};
monster.registerChild(child, 'measurement');
child.measurements
.type('flow')
.variant('measured')
.position('downstream')
.value(60, Date.now(), 'm3/h');
monster.handleInput('input_q', { value: 20, unit: 'm3/h' });
advance(1000);
monster.tick();
assert.strictEqual(monster.q, 40);
});
});
test('invalid flow bounds prevent sampling start', () => {
const monster = new Monster(buildConfig({
constraints: {
samplingtime: 1,
minVolume: 5,
maxWeight: 23,
nominalFlowMin: 10,
flowMax: 5
}
}));
monster.handleInput('i_start', true);
monster.sampling_program();
assert.strictEqual(monster.invalidFlowBounds, true);
assert.strictEqual(monster.running, false);
assert.strictEqual(monster.i_start, false);
});
test('flowCalc uses elapsed time to compute m3PerTick', () => {
withMockedDate('2024-10-15T00:00:00Z', ({ advance }) => {
const monster = new Monster(buildConfig());
monster.q = 36; // m3/h
monster.flowCalc();
assert.strictEqual(monster.m3PerTick, 0);
advance(10000);
monster.flowCalc();
const expected = 0.1; // 36 m3/h -> 0.01 m3/s over 10s
assert.ok(Math.abs(monster.m3PerTick - expected) < 1e-6);
});
});
test('prediction fallback uses nominalFlowMin * sampling_time when rain is stale', () => {
const monster = new Monster(buildConfig());
monster.nominalFlowMin = 4;
monster.flowMax = 10;
monster.rainMaxRef = 8;
monster.sampling_time = 24;
monster.lastRainUpdate = 0;
const pred = monster.get_model_prediction();
assert.strictEqual(pred, 96);
});
test('pulses increment when running with manual flow and zero nominalFlowMin', () => {
withMockedDate('2024-10-15T00:00:00Z', ({ advance }) => {
const monster = new Monster(buildConfig({
constraints: {
samplingtime: 1,
minVolume: 5,
maxWeight: 23,
nominalFlowMin: 0,
flowMax: 6000,
minSampleIntervalSec: 60,
maxRainRef: 10
}
}));
monster.handleInput('input_q', { value: 200, unit: 'm3/h' });
monster.handleInput('i_start', true);
for (let i = 0; i < 80; i++) {
advance(1000);
monster.tick();
}
assert.ok(monster.sumPuls > 0);
assert.ok(monster.bucketVol > 0);
assert.ok(monster.missedSamples > 0);
assert.ok(monster.getSampleCooldownMs() > 0);
});
});
test('rain data aggregation produces totals', () => {
const monster = new Monster(buildConfig());
const rainPath = path.join(__dirname, 'seed_data', 'raindataFormat.json');
const rainData = JSON.parse(fs.readFileSync(rainPath, 'utf8'));
monster.updateRainData(rainData);
assert.ok(Object.keys(monster.aggregatedOutput).length > 0);
assert.ok(monster.sumRain >= 0);
assert.ok(monster.avgRain >= 0);
});
test('monsternametijden schedule sets next date', () => {
withMockedDate('2024-10-15T00:00:00Z', () => {
const monster = new Monster(buildConfig());
const csvPath = path.join(__dirname, 'seed_data', 'monsternametijden.csv');
const rows = parseMonsternametijdenCsv(csvPath);
monster.aquonSampleName = '112100';
monster.updateMonsternametijden(rows);
const nextDate = monster.nextDate instanceof Date
? monster.nextDate.getTime()
: Number(monster.nextDate);
assert.ok(Number.isFinite(nextDate));
assert.ok(nextDate > Date.now());
});
});
test('output includes pulse and flow fields', () => {
const monster = new Monster(buildConfig());
const output = monster.getOutput();
assert.ok(Object.prototype.hasOwnProperty.call(output, 'pulse'));
assert.ok(Object.prototype.hasOwnProperty.call(output, 'q'));
assert.ok(Object.prototype.hasOwnProperty.call(output, 'm3PerPuls'));
assert.ok(Object.prototype.hasOwnProperty.call(output, 'm3PerPulse'));
assert.ok(Object.prototype.hasOwnProperty.call(output, 'pulsesRemaining'));
assert.ok(Object.prototype.hasOwnProperty.call(output, 'pulseFraction'));
assert.ok(Object.prototype.hasOwnProperty.call(output, 'flowToNextPulseM3'));
assert.ok(Object.prototype.hasOwnProperty.call(output, 'timeToNextPulseSec'));
assert.ok(Object.prototype.hasOwnProperty.call(output, 'targetVolumeM3'));
assert.ok(Object.prototype.hasOwnProperty.call(output, 'targetProgressPct'));
assert.ok(Object.prototype.hasOwnProperty.call(output, 'targetDeltaL'));
assert.ok(Object.prototype.hasOwnProperty.call(output, 'targetDeltaM3'));
assert.ok(Object.prototype.hasOwnProperty.call(output, 'predictedRateM3h'));
});