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>
77 lines
2.3 KiB
JavaScript
77 lines
2.3 KiB
JavaScript
'use strict';
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
|
|
function makeTempPath() {
|
|
return path.join(os.tmpdir(), `myprimal-test-config-${process.pid}-${Date.now()}.json`);
|
|
}
|
|
|
|
function freshConfig(tmpPath) {
|
|
process.env.MYPRIMAL_CONFIG_PATH = tmpPath;
|
|
jest.resetModules();
|
|
return require('../src/config');
|
|
}
|
|
|
|
afterEach(() => {
|
|
delete process.env.MYPRIMAL_CONFIG_PATH;
|
|
delete process.env.POLL_FAST;
|
|
});
|
|
|
|
describe('config', () => {
|
|
test('loadConfig uses env defaults when no file', () => {
|
|
process.env.POLL_FAST = '45';
|
|
const tmp = makeTempPath();
|
|
const { loadConfig, getConfig } = freshConfig(tmp);
|
|
loadConfig();
|
|
expect(getConfig().poll.fast).toBe(45);
|
|
});
|
|
|
|
test('patchConfig deep merges and persists', () => {
|
|
const tmp = makeTempPath();
|
|
const { loadConfig, getConfig, patchConfig } = freshConfig(tmp);
|
|
loadConfig();
|
|
patchConfig({ poll: { fast: 20 } });
|
|
expect(getConfig().poll.fast).toBe(20);
|
|
expect(getConfig().poll.normal).toBeGreaterThan(0);
|
|
|
|
const saved = JSON.parse(fs.readFileSync(tmp, 'utf8'));
|
|
expect(saved.poll.fast).toBe(20);
|
|
fs.unlinkSync(tmp);
|
|
});
|
|
|
|
test('patchConfig emits change event', done => {
|
|
const tmp = makeTempPath();
|
|
const { loadConfig, patchConfig, on } = freshConfig(tmp);
|
|
loadConfig();
|
|
on('change', cfg => {
|
|
if (cfg.poll.slow === 1800) {
|
|
fs.existsSync(tmp) && fs.unlinkSync(tmp);
|
|
done();
|
|
}
|
|
});
|
|
patchConfig({ poll: { slow: 1800 } });
|
|
});
|
|
|
|
test('overrides preserved across patches', () => {
|
|
const tmp = makeTempPath();
|
|
const { loadConfig, getConfig, patchConfig } = freshConfig(tmp);
|
|
loadConfig();
|
|
patchConfig({ poll: { overrides: { 'lrps_0565c0/ph': 30 } } });
|
|
patchConfig({ poll: { fast: 10 } });
|
|
expect(getConfig().poll.overrides['lrps_0565c0/ph']).toBe(30);
|
|
expect(getConfig().poll.fast).toBe(10);
|
|
fs.unlinkSync(tmp);
|
|
});
|
|
|
|
test('loadConfig reads persisted values', () => {
|
|
const tmp = makeTempPath();
|
|
fs.writeFileSync(tmp, JSON.stringify({ poll: { fast: 99, normal: 500, slow: 1000, overrides: {} } }));
|
|
const { loadConfig, getConfig } = freshConfig(tmp);
|
|
loadConfig();
|
|
expect(getConfig().poll.fast).toBe(99);
|
|
expect(getConfig().poll.normal).toBe(500);
|
|
fs.unlinkSync(tmp);
|
|
});
|
|
});
|