const path = require('path'); const ConfigManager = require('../src/configs/index'); describe('ConfigManager', () => { const configDir = path.resolve(__dirname, '../src/configs'); let cm; beforeEach(() => { cm = new ConfigManager(configDir); }); // ── getConfig() ────────────────────────────────────────────────────── describe('getConfig()', () => { it('should load and parse a known JSON config file', () => { const config = cm.getConfig('baseConfig'); expect(config).toBeDefined(); expect(typeof config).toBe('object'); }); it('should return the same content on successive calls', () => { const a = cm.getConfig('baseConfig'); const b = cm.getConfig('baseConfig'); expect(a).toEqual(b); }); it('should throw when the config file does not exist', () => { expect(() => cm.getConfig('nonExistentConfig_xyz')) .toThrow(/Failed to load config/); }); it('should throw a descriptive message including the config name', () => { expect(() => cm.getConfig('missing')) .toThrow("Failed to load config 'missing'"); }); }); // ── hasConfig() ────────────────────────────────────────────────────── describe('hasConfig()', () => { it('should return true for a config that exists', () => { expect(cm.hasConfig('baseConfig')).toBe(true); }); it('should return false for a config that does not exist', () => { expect(cm.hasConfig('doesNotExist_abc')).toBe(false); }); }); // ── getAvailableConfigs() ──────────────────────────────────────────── describe('getAvailableConfigs()', () => { it('should return an array of strings', () => { const configs = cm.getAvailableConfigs(); expect(Array.isArray(configs)).toBe(true); configs.forEach(name => expect(typeof name).toBe('string')); }); it('should include known config names without .json extension', () => { const configs = cm.getAvailableConfigs(); expect(configs).toContain('baseConfig'); expect(configs).toContain('diffuser'); expect(configs).toContain('measurement'); }); it('should not include .json extension in returned names', () => { const configs = cm.getAvailableConfigs(); configs.forEach(name => { expect(name).not.toMatch(/\.json$/); }); }); it('should throw when pointed at a non-existent directory', () => { const bad = new ConfigManager('/tmp/nonexistent_dir_xyz_123'); expect(() => bad.getAvailableConfigs()).toThrow(/Failed to read config directory/); }); }); // ── buildConfig() ──────────────────────────────────────────────────── describe('buildConfig()', () => { it('should return an object with general and functionality sections', () => { const uiConfig = { name: 'TestNode', unit: 'bar', enableLog: true, logLevel: 'debug' }; const result = cm.buildConfig('measurement', uiConfig, 'node-id-1'); expect(result).toHaveProperty('general'); expect(result).toHaveProperty('functionality'); expect(result).toHaveProperty('output'); }); it('should populate general.name from uiConfig.name', () => { const uiConfig = { name: 'MySensor' }; const result = cm.buildConfig('measurement', uiConfig, 'id-1'); expect(result.general.name).toBe('MySensor'); }); it('should default general.name to nodeName when uiConfig.name is empty', () => { const result = cm.buildConfig('measurement', {}, 'id-1'); expect(result.general.name).toBe('measurement'); }); it('should set general.id from the nodeId argument', () => { const result = cm.buildConfig('valve', {}, 'node-42'); expect(result.general.id).toBe('node-42'); }); it('should default unit to unitless', () => { const result = cm.buildConfig('valve', {}, 'id-1'); expect(result.general.unit).toBe('unitless'); }); it('should default logging.enabled to true when enableLog is undefined', () => { const result = cm.buildConfig('valve', {}, 'id-1'); expect(result.general.logging.enabled).toBe(true); }); it('should respect enableLog = false', () => { const result = cm.buildConfig('valve', { enableLog: false }, 'id-1'); expect(result.general.logging.enabled).toBe(false); }); it('should default logLevel to info', () => { const result = cm.buildConfig('valve', {}, 'id-1'); expect(result.general.logging.logLevel).toBe('info'); }); it('should set functionality.softwareType to lowercase nodeName', () => { const result = cm.buildConfig('Valve', {}, 'id-1'); expect(result.functionality.softwareType).toBe('valve'); }); it('should default positionVsParent to atEquipment', () => { const result = cm.buildConfig('valve', {}, 'id-1'); expect(result.functionality.positionVsParent).toBe('atEquipment'); }); it('should set distance when hasDistance is true', () => { const result = cm.buildConfig('valve', { hasDistance: true, distance: 5.5 }, 'id-1'); expect(result.functionality.distance).toBe(5.5); }); it('should set distance to undefined when hasDistance is false', () => { const result = cm.buildConfig('valve', { hasDistance: false, distance: 5.5 }, 'id-1'); expect(result.functionality.distance).toBeUndefined(); }); // ── asset section ────────────────────────────────────────────────── it('should not include asset section when no asset fields provided', () => { const result = cm.buildConfig('valve', {}, 'id-1'); expect(result.asset).toBeUndefined(); }); it('should include asset section when supplier is provided', () => { const result = cm.buildConfig('valve', { supplier: 'Siemens' }, 'id-1'); expect(result.asset).toBeDefined(); expect(result.asset.supplier).toBe('Siemens'); }); it('should populate asset defaults for missing optional fields', () => { const result = cm.buildConfig('valve', { supplier: 'ABB' }, 'id-1'); expect(result.asset.category).toBe('sensor'); expect(result.asset.type).toBe('Unknown'); expect(result.asset.model).toBe('Unknown'); }); // ── domainConfig merge ───────────────────────────────────────────── it('should merge domainConfig sections into the result', () => { const domain = { scaling: { enabled: true, factor: 2 } }; const result = cm.buildConfig('measurement', {}, 'id-1', domain); expect(result.scaling).toEqual({ enabled: true, factor: 2 }); }); it('should handle empty domainConfig gracefully', () => { const result = cm.buildConfig('measurement', {}, 'id-1', {}); expect(result).toHaveProperty('general'); expect(result).toHaveProperty('functionality'); }); it('should default output formats to process and influxdb', () => { const result = cm.buildConfig('measurement', {}, 'id-1'); expect(result.output).toEqual({ process: 'process', dbase: 'influxdb', }); }); it('should allow output format overrides from ui config', () => { const result = cm.buildConfig('measurement', { processOutputFormat: 'json', dbaseOutputFormat: 'csv', }, 'id-1'); expect(result.output).toEqual({ process: 'json', dbase: 'csv', }); }); }); // ── createEndpoint() ───────────────────────────────────────────────── describe('createEndpoint()', () => { it('should return a JavaScript string containing the node name', () => { const script = cm.createEndpoint('baseConfig'); expect(typeof script).toBe('string'); expect(script).toContain('baseConfig'); expect(script).toContain('window.EVOLV'); }); it('should throw for a non-existent config', () => { expect(() => cm.createEndpoint('doesNotExist_xyz')) .toThrow(/Failed to create endpoint/); }); }); // ── getBaseConfig() ────────────────────────────────────────────────── describe('getBaseConfig()', () => { it('should load the baseConfig.json file', () => { const base = cm.getBaseConfig(); expect(base).toBeDefined(); expect(typeof base).toBe('object'); }); }); });