import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; import { loadConfig } from './config.js'; const temporaryRoots = []; function validConfig(overrides = {}) { return { mongodb: { uri: 'mongodb://172.16.0.7:27017', db: 'basket_utils' }, server: { host: '0.0.0.0', port: 3000, mode: 'development', allowedOrigins: ['http://localhost:5173'], secureCookie: false }, bootstrap: { password: '' }, qa: { db: 'basket_utils_qa', password: '' }, ...overrides, }; } async function writeConfig(source) { const root = await mkdtemp(path.join(os.tmpdir(), 'basket-utils-config-')); temporaryRoots.push(root); await writeFile(path.join(root, '.config.json'), source); return root; } afterEach(async () => { await Promise.all(temporaryRoots.splice(0).map((root) => rm(root, { recursive: true, force: true }))); }); describe('JSON configuration', () => { it('loads relative to the project root and accepts a UTF-8 BOM', async () => { const root = await writeConfig(`\uFEFF${JSON.stringify(validConfig())}`); const config = await loadConfig({ projectRoot: root }); expect(config.mongodb.db).toBe('basket_utils'); expect(config.server.port).toBe(3000); }); it('reports missing and malformed files without exposing source values', async () => { const missingRoot = await mkdtemp(path.join(os.tmpdir(), 'basket-utils-config-')); temporaryRoots.push(missingRoot); await expect(loadConfig({ projectRoot: missingRoot })).rejects.toThrow(/not found.*\.config\.json\.sample/i); const secret = 'config-only-secret'; const malformedRoot = await writeConfig(`{"bootstrap":{"password":"${secret}"},`); await expect(loadConfig({ projectRoot: malformedRoot })).rejects.toThrow(/Invalid JSON.*check its JSON syntax/i); await expect(loadConfig({ projectRoot: malformedRoot })).rejects.not.toThrow(secret); }); it('rejects invalid port types with a field-specific message', async () => { const root = await writeConfig(JSON.stringify(validConfig({ server: { ...validConfig().server, port: '3000' } }))); await expect(loadConfig({ projectRoot: root })).rejects.toThrow(/server\.port.*integer/i); }); it('forces secure cookies and removes private HTTP origins in production', async () => { const root = await writeConfig(JSON.stringify(validConfig({ server: { ...validConfig().server, mode: 'production', secureCookie: false, allowedOrigins: ['http://localhost:5173', 'http://192.168.5.10:5173', 'https://coach.example.com'], } }))); const config = await loadConfig({ projectRoot: root }); expect(config.server.secureCookie).toBe(true); expect(config.server.allowedOrigins).toEqual(['https://coach.example.com']); }); it('keeps the sample schema and empty sample passwords', async () => { const sample = JSON.parse(await readFile(path.resolve(import.meta.dirname, '..', '.config.json.sample'), 'utf8')); expect(sample.mongodb).toEqual({ uri: 'mongodb://172.16.0.7:27017', db: 'basket_utils' }); expect(sample.server).toMatchObject({ host: '0.0.0.0', port: 3000, mode: 'development', secureCookie: false }); expect(sample.server.allowedOrigins).toEqual(['http://localhost:5173', 'http://127.0.0.1:5173', 'http://192.168.5.10:5173']); expect(sample.bootstrap.password).toBe(''); expect(sample.qa).toEqual({ db: 'basket_utils_qa', password: '' }); }); });