/** * Tests for platform/env.js * Uses Node.js built-in test runner (node --test) */ import { test, describe, beforeEach } from 'node:test'; import assert from 'node:assert'; import { initEnv, getConfig, getConfigSync, setConfig, getConfigDict, isDevelopment, isProduction, isServiceWorkerEnabledSync, resolveServiceWorkerEnabled, CONFIG_KEYS } from '../src/platform/env.js'; import { getProvider } from '../src/platform/storage.js'; describe('env.js', () => { beforeEach(() => { // Reset config before each test initEnv({}); }); describe('initEnv', () => { test('should initialize config with provided values', () => { const appConfig = { name: 'TestApp', displayName: 'Test Application', brandLogo: '/logo.png', ui_shell: 'DashboardShell', storage: { backend: 'indexedDB' }, api: { baseURL: '/api/v1' }, modules: ['core', 'dummy'] }; initEnv(appConfig); const config = getConfigDict(); assert.strictEqual(config.APP_NAME, 'TestApp'); assert.strictEqual(config.APP_DISPLAY_NAME, 'Test Application'); assert.strictEqual(config.BRAND_LOGO, '/logo.png'); assert.strictEqual(config.UI_SHELL, 'DashboardShell'); assert.strictEqual(config.STORAGE_BACKEND, 'indexedDB'); assert.strictEqual(config.API_BASE_URL, '/api/v1'); assert.deepStrictEqual(config.MODULES, ['core', 'dummy']); }); test('should use defaults when values are missing', () => { const appConfig = { name: 'TestApp' }; initEnv(appConfig); const config = getConfigDict(); assert.strictEqual(config.APP_NAME, 'TestApp'); assert.strictEqual(config.APP_DISPLAY_NAME, 'TestApp'); // Falls back to name assert.strictEqual(config.BRAND_LOGO, '/favicon.svg'); // Default assert.strictEqual(config.UI_SHELL, 'EmptyShell'); // Default assert.strictEqual(config.STORAGE_BACKEND, 'localStorage'); // Default assert.strictEqual(config.API_BASE_URL, '/api'); // Default assert.deepStrictEqual(config.MODULES, []); // Default }); }); describe('getConfig', () => { beforeEach(() => { initEnv({ name: 'TestApp', displayName: 'Test Display Name' }); }); test('should return config value from dictionary', async () => { const value = await getConfig(CONFIG_KEYS.APP_NAME); assert.strictEqual(value, 'TestApp'); }); test('should return altValue when key not found', async () => { // Note: getConfig checks import.meta.env which may not be available in Node // This test verifies the fallback behavior const value = await getConfig('NON_EXISTENT_KEY', 'default'); assert.strictEqual(value, 'default'); }); test('should return null when key not found and no altValue', async () => { // Note: getConfig checks import.meta.env which may not be available in Node // This test verifies the fallback behavior const value = await getConfig('NON_EXISTENT_KEY'); // May return null or undefined depending on import.meta.env availability assert.ok(value === null || value === undefined); }); test('locked keys ignore persisted storage overrides', async () => { initEnv({ name: 'TestApp', api: { base_url: '/api/profile' }, modules: ['rt', 'game'] }); const configStorage = getProvider('kv', 'config'); await configStorage.set(CONFIG_KEYS.API_BASE_URL, '/api/stale'); await configStorage.set(CONFIG_KEYS.MODULES, ['stale']); assert.strictEqual(await getConfig(CONFIG_KEYS.API_BASE_URL), '/api/profile'); assert.deepStrictEqual(await getConfig(CONFIG_KEYS.MODULES), ['rt', 'game']); }); }); describe('getConfigSync', () => { test('should return in-memory config without storage reads', () => { initEnv({ name: 'SyncApp', api: { base_url: '/api/sync' } }); assert.strictEqual(getConfigSync(CONFIG_KEYS.APP_NAME), 'SyncApp'); assert.strictEqual(getConfigSync(CONFIG_KEYS.API_BASE_URL), '/api/sync'); assert.strictEqual(getConfigSync('MISSING', 'fallback'), 'fallback'); }); }); describe('setConfig', () => { beforeEach(() => { initEnv({ name: 'TestApp' }); }); test('should update existing config key in dictionary', async () => { await setConfig(CONFIG_KEYS.APP_NAME, 'UpdatedApp'); const value = await getConfig(CONFIG_KEYS.APP_NAME); assert.strictEqual(value, 'UpdatedApp'); }); test('should handle new keys (may attempt storage)', async () => { // Since it's a new key, it should attempt to store in storage // We just verify it doesn't throw await assert.doesNotReject(async () => { await setConfig('custom.key', 'customValue'); }); }); }); describe('getConfigDict', () => { test('should return a copy of config dictionary', () => { initEnv({ name: 'TestApp', displayName: 'Test Display' }); const config1 = getConfigDict(); const config2 = getConfigDict(); // Should be equal but not the same object (copy) assert.deepStrictEqual(config1, config2); assert.notStrictEqual(config1, config2); }); test('should return config with defaults when initialized with empty object', () => { initEnv({}); const config = getConfigDict(); // initEnv sets defaults even with empty object assert.ok('APP_NAME' in config); assert.ok('BRAND_LOGO' in config); assert.strictEqual(config.BRAND_LOGO, '/favicon.svg'); }); }); describe('CONFIG_KEYS', () => { test('should export all expected config keys', () => { assert.ok('APP_NAME' in CONFIG_KEYS); assert.ok('APP_DISPLAY_NAME' in CONFIG_KEYS); assert.ok('BRAND_LOGO' in CONFIG_KEYS); assert.ok('UI_SHELL' in CONFIG_KEYS); assert.ok('STORAGE_BACKEND' in CONFIG_KEYS); assert.ok('API_BASE_URL' in CONFIG_KEYS); assert.ok('MODULES' in CONFIG_KEYS); assert.ok('SERVICE_WORKER_ENABLED' in CONFIG_KEYS); }); }); describe('service worker profile flag', () => { test('resolveServiceWorkerEnabled defaults to true', () => { assert.strictEqual(resolveServiceWorkerEnabled({}), true); }); test('resolveServiceWorkerEnabled reads service_worker.enabled', () => { assert.strictEqual(resolveServiceWorkerEnabled({ service_worker: { enabled: false } }), false); assert.strictEqual(resolveServiceWorkerEnabled({ pwa: { service_worker: { enabled: false } } }), false); }); test('initEnv seeds SERVICE_WORKER_ENABLED into config', () => { initEnv({ service_worker: { enabled: false } }); assert.strictEqual(isServiceWorkerEnabledSync(), false); assert.strictEqual(getConfigDict()[CONFIG_KEYS.SERVICE_WORKER_ENABLED], false); }); }); describe('isDevelopment and isProduction', () => { test('should be functions', () => { assert.strictEqual(typeof isDevelopment, 'function'); assert.strictEqual(typeof isProduction, 'function'); }); }); });