import { describe, it, expect, beforeEach, afterEach } from 'vitest'; describe('config', () => { const originalEnv = process.env; beforeEach(() => { process.env = { ...originalEnv }; }); afterEach(() => { process.env = originalEnv; }); it('returns defaults when env vars are not set', async () => { process.env.JWT_SECRET = 'test-secret'; delete process.env.PORT; delete process.env.HOST; delete process.env.DB_PATH; delete process.env.UPLOAD_DIR; delete process.env.LOG_FILE; delete process.env.MAX_FILE_SIZE; delete process.env.BASE_URL; delete process.env.COOKIE_SECURE; delete process.env.TRUST_PROXY; delete process.env.LOCKOUT_THRESHOLD; delete process.env.LOCKOUT_BASE_SECONDS; delete process.env.LOCKOUT_MAX_SECONDS; delete process.env.LOGIN_MIN_RESPONSE_MS; delete process.env.LOGIN_RATE_LIMIT_MAX; delete process.env.LOGIN_RATE_LIMIT_WINDOW_SECONDS; const { loadConfig } = await import('../../src/config.ts'); const config = loadConfig(); expect(config.port).toBe(3000); expect(config.host).toBe('0.0.0.0'); expect(config.dbPath).toBe('./data/nanodrop.db'); expect(config.uploadDir).toBe('./data/uploads'); expect(config.logFile).toBe('./data/nanodrop.log'); expect(config.maxFileSize).toBe(104857600); expect(config.baseUrl).toBe('http://localhost:3000'); expect(config.cookieSecure).toBe(false); expect(config.trustProxy).toBe(false); expect(config.lockoutThreshold).toBe(5); expect(config.lockoutBaseSeconds).toBe(30); expect(config.lockoutMaxSeconds).toBe(3600); expect(config.loginMinResponseMs).toBe(350); expect(config.loginRateLimitMax).toBe(10); expect(config.loginRateLimitWindowSeconds).toBe(60); }); it('reads values from env vars', async () => { process.env.JWT_SECRET = 'my-secret'; process.env.PORT = '4000'; process.env.HOST = '127.0.0.1'; process.env.COOKIE_SECURE = 'true'; process.env.TRUST_PROXY = 'true'; process.env.MAX_FILE_SIZE = '52428800'; const { loadConfig } = await import('../../src/config.ts'); const config = loadConfig(); expect(config.port).toBe(4000); expect(config.host).toBe('127.0.0.1'); expect(config.jwtSecret).toBe('my-secret'); expect(config.cookieSecure).toBe(true); expect(config.trustProxy).toBe(true); expect(config.maxFileSize).toBe(52428800); }); it('reads lockout and rate-limit values from env vars', async () => { process.env.JWT_SECRET = 'my-secret'; process.env.LOCKOUT_THRESHOLD = '3'; process.env.LOCKOUT_BASE_SECONDS = '15'; process.env.LOCKOUT_MAX_SECONDS = '900'; process.env.LOGIN_MIN_RESPONSE_MS = '50'; process.env.LOGIN_RATE_LIMIT_MAX = '20'; process.env.LOGIN_RATE_LIMIT_WINDOW_SECONDS = '120'; const { loadConfig } = await import('../../src/config.ts'); const config = loadConfig(); expect(config.lockoutThreshold).toBe(3); expect(config.lockoutBaseSeconds).toBe(15); expect(config.lockoutMaxSeconds).toBe(900); expect(config.loginMinResponseMs).toBe(50); expect(config.loginRateLimitMax).toBe(20); expect(config.loginRateLimitWindowSeconds).toBe(120); }); it('throws when JWT_SECRET is missing', async () => { delete process.env.JWT_SECRET; const { loadConfig } = await import('../../src/config.ts'); expect(() => loadConfig()).toThrow('JWT_SECRET'); }); });