import { describe, it, expect } from 'vitest' import { loadConfig, DEFAULT_CP_URL, DEFAULT_TUNNEL_ZONE, DEFAULT_PANEL_BIND_PORT, DEFAULT_CAPABILITY_SIGN_KEY_PATH, } from '../src/config.js' const base = { SESSION_SECRET: 'a-sufficiently-long-secret-value', BASE_DOMAIN: 'terminal.yaojia.wang', OPERATOR_ACCOUNT_ID: 'acct-1', } describe('loadConfig', () => { it('applies defaults for optional fields', () => { const cfg = loadConfig({ ...base } as NodeJS.ProcessEnv) expect(cfg.cpUrl).toBe(DEFAULT_CP_URL) expect(cfg.tunnelZone).toBe(DEFAULT_TUNNEL_ZONE) expect(cfg.panelBindPort).toBe(DEFAULT_PANEL_BIND_PORT) expect(cfg.capabilitySignKeyPath).toBe(DEFAULT_CAPABILITY_SIGN_KEY_PATH) expect(cfg.panelPassword).toBeUndefined() }) it('reads all provided values and strips trailing slash from CP_URL', () => { const cfg = loadConfig({ ...base, PANEL_PASSWORD: 'pw', CP_URL: 'http://127.0.0.1:9000/', TUNNEL_ZONE: 'z.example', PANEL_BIND_PORT: '9999', } as NodeJS.ProcessEnv) expect(cfg.panelPassword).toBe('pw') expect(cfg.cpUrl).toBe('http://127.0.0.1:9000') expect(cfg.tunnelZone).toBe('z.example') expect(cfg.panelBindPort).toBe(9999) }) it('throws when SESSION_SECRET is missing (fail-closed)', () => { const { SESSION_SECRET: _omit, ...rest } = base expect(() => loadConfig(rest as NodeJS.ProcessEnv)).toThrow() }) it('throws when SESSION_SECRET is too short', () => { expect(() => loadConfig({ ...base, SESSION_SECRET: 'short' } as NodeJS.ProcessEnv)).toThrow() }) it('throws when BASE_DOMAIN is missing', () => { const { BASE_DOMAIN: _omit, ...rest } = base expect(() => loadConfig(rest as NodeJS.ProcessEnv)).toThrow() }) it('throws when OPERATOR_ACCOUNT_ID is missing', () => { const { OPERATOR_ACCOUNT_ID: _omit, ...rest } = base expect(() => loadConfig(rest as NodeJS.ProcessEnv)).toThrow() }) it('throws when PANEL_BIND_PORT is out of range', () => { expect(() => loadConfig({ ...base, PANEL_BIND_PORT: '70000' } as NodeJS.ProcessEnv)).toThrow() }) it('accepts loopback CP_URL hosts (127.0.0.0/8, ::1, localhost)', () => { for (const url of ['http://127.0.0.1:8080', 'http://127.9.9.9:1', 'http://[::1]:8080', 'http://localhost:8080']) { expect(loadConfig({ ...base, CP_URL: url } as NodeJS.ProcessEnv).cpUrl).toBe(url) } }) it('throws when CP_URL host is not loopback (anti-SSRF, fail-closed)', () => { for (const url of ['http://evil.example.com:8080', 'http://169.254.169.254/', 'http://10.0.0.5:8080', 'http://8.8.8.8']) { expect(() => loadConfig({ ...base, CP_URL: url } as NodeJS.ProcessEnv)).toThrow() } }) })