// Tests for config deepMerge logic import { describe, it, expect } from 'vitest'; import type { DispatcherConfig } from '../src/types.js'; // ── inline copy of deepMerge (same logic as config.ts) ─────────────────────── function deepMerge(base: T, override: Partial): T { const result = { ...base }; for (const key of Object.keys(override) as (keyof T)[]) { const overrideVal = override[key]; const baseVal = base[key]; if ( overrideVal !== null && typeof overrideVal === 'object' && !Array.isArray(overrideVal) && baseVal !== null && typeof baseVal === 'object' && !Array.isArray(baseVal) ) { result[key] = deepMerge(baseVal as object, overrideVal as object) as T[keyof T]; } else if (overrideVal !== undefined) { result[key] = overrideVal as T[keyof T]; } } return result; } // ── helpers ─────────────────────────────────────────────────────────────────── const DEFAULTS: DispatcherConfig = { ograph: { endpoint: 'https://ograph.example.com', token: undefined, projections: [], }, oc: { statusEndpoint: 'http://localhost:18789/status', statusToken: 'default-token', minAvailable: 2, }, intervals: { watcherIdle: 30_000, watcherActive: 5_000, schedulerIdle: 60_000, schedulerActive: 5_000, cooldownAfterPush: 60_000, }, }; // ── tests ───────────────────────────────────────────────────────────────────── describe('config deepMerge logic', () => { it('returns defaults unchanged when no overrides supplied', () => { const result = deepMerge(DEFAULTS, {}); expect(result).toEqual(DEFAULTS); }); it('overrides a scalar field without touching siblings', () => { const result = deepMerge(DEFAULTS, { ograph: { endpoint: 'https://custom.example.com', token: undefined, projections: [] }, }); expect(result.ograph.endpoint).toBe('https://custom.example.com'); expect(result.oc.statusToken).toBe('default-token'); // unchanged }); it('deep-merges nested objects — partial override preserves untouched sibling keys', () => { const result = deepMerge(DEFAULTS, { oc: { statusToken: 'my-token' } as DispatcherConfig['oc'], }); expect(result.oc.statusToken).toBe('my-token'); expect(result.oc.statusEndpoint).toBe(DEFAULTS.oc.statusEndpoint); // preserved expect(result.oc.minAvailable).toBe(2); // preserved }); it('env var override merges on top of file config', () => { const fileConfig: Partial = { ograph: { endpoint: 'https://file-endpoint.example.com', token: undefined, projections: ['p1'] }, }; let config = deepMerge(DEFAULTS, fileConfig); // Simulate env var override (as done in loadConfig) config.ograph.endpoint = 'https://env-endpoint.example.com'; expect(config.ograph.endpoint).toBe('https://env-endpoint.example.com'); expect(config.ograph.projections).toEqual(['p1']); // file value preserved expect(config.oc.minAvailable).toBe(2); // default preserved }); it('does not overwrite a field when override value is undefined', () => { const result = deepMerge(DEFAULTS, { ograph: { endpoint: undefined as unknown as string, token: undefined, projections: [] }, }); // endpoint should keep the default since override is undefined expect(result.ograph.endpoint).toBe(DEFAULTS.ograph.endpoint); }); });