/** * config.ts 단위 테스트 * - getDefaultConfig()가 올바른 기본값을 반환하는지 확인 * - 설정 로드/저장은 파일 I/O가 포함되므로 통합 테스트에 가까움 */ import { describe, it, expect, vi, afterEach } from 'vitest'; import fs from 'fs'; import os from 'os'; import path from 'path'; import inquirer from 'inquirer'; import { checkAndRunMigrations, CURRENT_CONFIG_VERSION } from '../src/migration'; import { getDefaultConfig, loadConfig, saveConfig, SKT_TERMS_URLS, SKT_TERMS_VERSION, validateRequiredFields, } from '../src/config'; afterEach(() => { vi.restoreAllMocks(); }); describe('getDefaultConfig', () => { it('should return valid default config with required fields', () => { const config = getDefaultConfig(); expect(config._config_version).toBe(4); expect(config.credentials).toBeDefined(); expect(config.terms).toEqual({ provider: 'skt', accepted: false, accepted_at: '', version: SKT_TERMS_VERSION, urls: SKT_TERMS_URLS, }); expect(config.plan.speed_mbps).toBe(1000); expect(config.schedule.timezone).toBe('Asia/Seoul'); expect(config.headless).toBe(true); }); it('should have schedule with multi-attempt defaults', () => { const config = getDefaultConfig(); expect(config.schedule.max_attempts).toBeGreaterThan(1); expect(config.schedule.retry_interval_minutes).toBeGreaterThan(0); expect(config.schedule.stop_on_complaint_success).toBe(true); }); }); describe('validateRequiredFields', () => { it('should require SKT terms acceptance', () => { const config = getDefaultConfig(); config.credentials.id = 'user@example.com'; config.credentials.password = 'password'; config.phone = '01012345678'; expect(validateRequiredFields(config)).toContain( 'terms (SKT/SK브로드밴드 공식 이용약관 동의)', ); }); it('should reject invalid SKT terms acceptance timestamps', () => { const config = getDefaultConfig(); config.credentials.id = 'user@example.com'; config.credentials.password = 'password'; config.phone = '01012345678'; config.terms = { provider: 'skt', accepted: true, accepted_at: 'not-a-date', version: SKT_TERMS_VERSION, urls: SKT_TERMS_URLS, }; expect(validateRequiredFields(config)).toContain( 'terms (SKT/SK브로드밴드 공식 이용약관 동의)', ); }); it('should reject normalized or underspecified timestamp strings', () => { const config = getDefaultConfig(); config.credentials.id = 'user@example.com'; config.credentials.password = 'password'; config.phone = '01012345678'; config.terms = { provider: 'skt', accepted: true, accepted_at: '1', version: SKT_TERMS_VERSION, urls: SKT_TERMS_URLS, }; expect(validateRequiredFields(config)).toContain( 'terms (SKT/SK브로드밴드 공식 이용약관 동의)', ); config.terms.accepted_at = '2026-02-31T00:00:00.000Z'; expect(validateRequiredFields(config)).toContain( 'terms (SKT/SK브로드밴드 공식 이용약관 동의)', ); }); it('should accept config with current SKT terms metadata', () => { const config = getDefaultConfig(); config.credentials.id = 'user@example.com'; config.credentials.password = 'password'; config.phone = '01012345678'; config.terms = { provider: 'skt', accepted: true, accepted_at: '2026-03-30T00:00:00.000Z', version: SKT_TERMS_VERSION, urls: SKT_TERMS_URLS, }; expect(validateRequiredFields(config)).toEqual([]); }); }); describe('loadConfig and saveConfig terms handling', () => { it('should default missing terms in old configs safely', () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'skt-config-')); const configPath = path.join(dir, 'config.yaml'); fs.writeFileSync(configPath, [ '_config_version: 3', 'credentials:', ' id: "user@example.com"', ' password: "password"', 'phone: "01012345678"', ].join('\n'), 'utf8'); const config = loadConfig(configPath); expect(config.terms.accepted).toBe(false); expect(config.terms.version).toBe(SKT_TERMS_VERSION); expect(config.terms.urls).toEqual(SKT_TERMS_URLS); }); it('should preserve accepted current terms when saving and loading', () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'skt-config-')); const configPath = path.join(dir, 'config.yaml'); const config = getDefaultConfig(); config.terms = { provider: 'skt', accepted: true, accepted_at: '2026-03-30T00:00:00.000Z', version: SKT_TERMS_VERSION, urls: SKT_TERMS_URLS, }; saveConfig(config, configPath); const loaded = loadConfig(configPath); expect(loaded.terms).toEqual(config.terms); }); }); describe('terms migration', () => { it('should record SKT terms acceptance when migrating v3 configs to v4', async () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'skt-config-')); const configPath = path.join(dir, 'config.yaml'); const config = getDefaultConfig(); config._config_version = 3; config.credentials.id = 'user@example.com'; config.credentials.password = 'password'; config.phone = '01012345678'; vi.spyOn(inquirer, 'prompt').mockResolvedValue({ apply: true }); const migrated = await checkAndRunMigrations(config, configPath, { interactive: true }); expect(migrated._config_version).toBe(CURRENT_CONFIG_VERSION); expect(migrated.terms).toMatchObject({ provider: 'skt', accepted: true, version: SKT_TERMS_VERSION, urls: SKT_TERMS_URLS, }); expect(new Date(migrated.terms.accepted_at).toISOString()).toBe(migrated.terms.accepted_at); expect(loadConfig(configPath).terms.accepted).toBe(true); }); });