941abd98d5
Dynamic MySolem/MyIndygo IoT bridge with MQTT, Home Assistant autodiscovery, and Homebridge EasyMQTT compatibility. - Device discovery via MySolem API on startup (all modules/inputs/outputs) - Configurable poll engine (fast/normal/slow buckets, per-input overrides) - MQTT retained state publishing + HA autodiscovery payloads - Unified manual command routing (linesControl vs sendManualModuleCommand) - Runtime config via MQTT $config/set, persisted to config.json - Expression override system (API-provided > hardcoded > passthrough) - Minimal REST API: /health, /state, /control, /rediscover - Docker deployment (Dockerfile + docker-compose.yml) - 38 unit tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
83 lines
2.5 KiB
JavaScript
83 lines
2.5 KiB
JavaScript
'use strict';
|
|
const { normalizeValue, toMetricName } = require('../src/normalize');
|
|
|
|
describe('normalizeValue', () => {
|
|
test('type 6 (pH): raw / 100', () => {
|
|
expect(normalizeValue(735, 6)).toBeCloseTo(7.35);
|
|
});
|
|
|
|
test('type 133 (water temp): raw / 100', () => {
|
|
expect(normalizeValue(2450, 133)).toBeCloseTo(24.5);
|
|
});
|
|
|
|
test('type 8 (pressure): (1.5 * raw - 500) / 1000', () => {
|
|
// raw = 800 → (1.5 * 800 - 500) / 1000 = (1200 - 500) / 1000 = 0.7
|
|
expect(normalizeValue(800, 8)).toBeCloseTo(0.7);
|
|
});
|
|
|
|
test('type 5 (temperature): passthrough', () => {
|
|
expect(normalizeValue(22, 5)).toBe(22);
|
|
});
|
|
|
|
test('type 12 (moisture): passthrough', () => {
|
|
expect(normalizeValue(54, 12)).toBe(54);
|
|
});
|
|
|
|
test('type 2 (binary): passthrough', () => {
|
|
expect(normalizeValue(1, 2)).toBe(1);
|
|
expect(normalizeValue(0, 2)).toBe(0);
|
|
});
|
|
|
|
test('null raw value returns null', () => {
|
|
expect(normalizeValue(null, 6)).toBeNull();
|
|
expect(normalizeValue(undefined, 12)).toBeNull();
|
|
});
|
|
|
|
test('unknown type code: passthrough', () => {
|
|
expect(normalizeValue(42, 999)).toBe(42);
|
|
});
|
|
});
|
|
|
|
describe('normalizeValue with apiExpression', () => {
|
|
test('apiExpression overrides hardcoded type expression', () => {
|
|
// type 133 hardcoded: v/100 — override with v/10
|
|
expect(normalizeValue(190, 133, 'value/10')).toBeCloseTo(19.0);
|
|
});
|
|
|
|
test('apiExpression used when typeDef has no expression', () => {
|
|
expect(normalizeValue(250, 5, 'value/10')).toBeCloseTo(25.0);
|
|
});
|
|
|
|
test('unsafe expression rejected, falls back to type default', () => {
|
|
// type 133 hardcoded: v/100
|
|
expect(normalizeValue(2450, 133, 'require("fs")')).toBeCloseTo(24.5);
|
|
});
|
|
|
|
test('null apiExpression falls back to type expression', () => {
|
|
expect(normalizeValue(735, 6, null)).toBeCloseTo(7.35);
|
|
});
|
|
|
|
test('complex expression (pressure)', () => {
|
|
expect(normalizeValue(800, 8, '(1.5*value-500)/1000')).toBeCloseTo(0.7);
|
|
});
|
|
});
|
|
|
|
describe('toMetricName', () => {
|
|
test('lowercases and replaces spaces', () => {
|
|
expect(toMetricName('Plumbago Humidity', 0)).toBe('plumbago_humidity');
|
|
});
|
|
|
|
test('strips non-alphanumeric except underscore', () => {
|
|
expect(toMetricName('pH (pool)', 0)).toBe('ph_pool');
|
|
});
|
|
|
|
test('falls back to input_{index} when name empty', () => {
|
|
expect(toMetricName('', 3)).toBe('input_3');
|
|
expect(toMetricName(null, 1)).toBe('input_1');
|
|
});
|
|
|
|
test('handles already clean names', () => {
|
|
expect(toMetricName('temperature', 0)).toBe('temperature');
|
|
});
|
|
});
|