B2.3 + P11.1 + P11.2 + monster schema fix
B2.3 LatestWinsGate fireAndWait:
Added fireAndWait(value, ctx?) returning per-fire settlement promise.
Supersede resolves with frozen sentinel {superseded: true} (no
rejection — callers branch on value without try/catch). Dispatch
errors also resolve (with undefined); error surfaces via gate.lastError.
LatestWinsGate.js 75 → 116 lines. 12/12 tests pass.
P11.1 convert.possibilities(measure):
New helper returning sorted+deduped unit names for a measure.
Cached per measure. Reuses existing convert measures map. Also
exposed convert.measures() listing all known measures.
convert/index.js +21 lines. New test file: 90 lines, 12/12 tests.
P11.2 commandRegistry.units field:
Pre-dispatch normalisation pipeline. descriptor.units = {measure,
default}; commandRegistry extracts msg.payload + msg.unit (3 shapes),
validates against measure, converts to default, falls back + warns
with accepted-list on unknown/wrong-measure. Falls back gracefully
if convert.possibilities is missing. commandRegistry.js 164 → 237.
+7 new tests covering all 4 paths.
monster schema fix (P11.2 sibling):
generalFunctions/src/configs/monster.json was stripping four
legitimate constraint keys (nominalFlowMin, flowMax, maxRainRef,
minSampleIntervalSec). Added them with defaults matching the
legacy nodeClass coercion. Side effect: this also UNBLOCKED the
monster cooldown-guard test (separate ROOT-CAUSE entry below).
CONTRACTS.md §4 + §8 updated. 144/144 basic tests + 206/206 full
generalFunctions tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
90
test/basic/convert.basic.test.js
Normal file
90
test/basic/convert.basic.test.js
Normal file
@@ -0,0 +1,90 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const convert = require('../../src/convert/index.js');
|
||||
|
||||
test('convert.possibilities — exported as a top-level function', () => {
|
||||
assert.equal(typeof convert.possibilities, 'function');
|
||||
});
|
||||
|
||||
test('convert.possibilities(volumeFlowRate) returns common flow units', () => {
|
||||
const units = convert.possibilities('volumeFlowRate');
|
||||
assert.ok(Array.isArray(units));
|
||||
assert.ok(units.length > 0);
|
||||
for (const u of ['m3/s', 'm3/h', 'l/s', 'l/min', 'l/h']) {
|
||||
assert.ok(units.includes(u), `expected '${u}' in volumeFlowRate possibilities`);
|
||||
}
|
||||
});
|
||||
|
||||
test('convert.possibilities(pressure) returns common pressure units', () => {
|
||||
const units = convert.possibilities('pressure');
|
||||
for (const u of ['Pa', 'kPa', 'bar', 'mbar', 'psi']) {
|
||||
assert.ok(units.includes(u), `expected '${u}' in pressure possibilities`);
|
||||
}
|
||||
});
|
||||
|
||||
test('convert.possibilities(power) returns common power units', () => {
|
||||
const units = convert.possibilities('power');
|
||||
for (const u of ['W', 'kW', 'MW']) {
|
||||
assert.ok(units.includes(u), `expected '${u}' in power possibilities`);
|
||||
}
|
||||
});
|
||||
|
||||
test('convert.possibilities(temperature) returns K, C, F', () => {
|
||||
const units = convert.possibilities('temperature');
|
||||
for (const u of ['K', 'C', 'F']) {
|
||||
assert.ok(units.includes(u), `expected '${u}' in temperature possibilities`);
|
||||
}
|
||||
});
|
||||
|
||||
test('convert.possibilities for length / mass / volume return non-empty', () => {
|
||||
assert.ok(convert.possibilities('length').includes('m'));
|
||||
assert.ok(convert.possibilities('mass').includes('kg'));
|
||||
assert.ok(convert.possibilities('volume').includes('l'));
|
||||
});
|
||||
|
||||
test('convert.possibilities(unknown) returns []', () => {
|
||||
assert.deepEqual(convert.possibilities('foo'), []);
|
||||
assert.deepEqual(convert.possibilities('bogus-measure'), []);
|
||||
});
|
||||
|
||||
test('convert.possibilities handles invalid input safely', () => {
|
||||
assert.deepEqual(convert.possibilities(), []);
|
||||
assert.deepEqual(convert.possibilities(null), []);
|
||||
assert.deepEqual(convert.possibilities(''), []);
|
||||
assert.deepEqual(convert.possibilities(42), []);
|
||||
});
|
||||
|
||||
test('convert.possibilities is sorted and deduplicated', () => {
|
||||
const units = convert.possibilities('pressure');
|
||||
const sorted = [...units].sort();
|
||||
assert.deepEqual(units, sorted, 'result should be alphabetically sorted');
|
||||
const set = new Set(units);
|
||||
assert.equal(set.size, units.length, 'result should have no duplicates');
|
||||
});
|
||||
|
||||
test('convert.possibilities returns stable / cached results across calls', () => {
|
||||
const a = convert.possibilities('volumeFlowRate');
|
||||
const b = convert.possibilities('volumeFlowRate');
|
||||
assert.deepEqual(a, b, 'two calls must return equal arrays');
|
||||
// Mutating the returned array must not poison the cache.
|
||||
a.push('SHOULD_NOT_PERSIST');
|
||||
const c = convert.possibilities('volumeFlowRate');
|
||||
assert.ok(!c.includes('SHOULD_NOT_PERSIST'), 'cached array must be defensively copied');
|
||||
assert.deepEqual(c, b);
|
||||
});
|
||||
|
||||
test('convert.measures lists known measure names', () => {
|
||||
const m = convert.measures();
|
||||
assert.ok(Array.isArray(m));
|
||||
for (const name of ['length', 'mass', 'volume', 'pressure', 'power', 'temperature', 'volumeFlowRate']) {
|
||||
assert.ok(m.includes(name), `expected measure '${name}'`);
|
||||
}
|
||||
});
|
||||
|
||||
test('convert factory still works (regression — no breakage of existing API)', () => {
|
||||
const result = convert(1).from('m').to('cm');
|
||||
assert.equal(result, 100);
|
||||
});
|
||||
Reference in New Issue
Block a user