/** * CC9: the zod field schemas must be a faithful port of the old setSettings * switch — same accepts, same rejects, same coercions (clamp/trim/sanitize). */ import { describe, it, expect } from 'vitest' import { parseSettingsField } from '../src/main/settingsSchema' import { DEFAULT_DOWNLOAD_OPTIONS } from '@shared/ipc' const ok = (v: T): { ok: true; value: T } => ({ ok: true, value: v }) const fail = { ok: false } describe('parseSettingsField (CC9 schema parity)', () => { it('theme: enum only', () => { expect(parseSettingsField('theme', 'dark')).toEqual(ok('dark')) expect(parseSettingsField('theme', 'blue')).toEqual(fail) expect(parseSettingsField('theme', 1)).toEqual(fail) }) it('maxConcurrent: coerces, rounds, clamps to [1,5]', () => { expect(parseSettingsField('maxConcurrent', 3)).toEqual(ok(3)) expect(parseSettingsField('maxConcurrent', '4')).toEqual(ok(4)) expect(parseSettingsField('maxConcurrent', 99)).toEqual(ok(5)) expect(parseSettingsField('maxConcurrent', -2)).toEqual(ok(1)) expect(parseSettingsField('maxConcurrent', 2.6)).toEqual(ok(3)) expect(parseSettingsField('maxConcurrent', 'abc')).toEqual(fail) }) it('aria2cConnections: clamps to [1,16] (L64)', () => { expect(parseSettingsField('aria2cConnections', 64)).toEqual(ok(16)) expect(parseSettingsField('aria2cConnections', 0)).toEqual(ok(1)) }) it('syncTime: 24h HH:MM only (L51)', () => { expect(parseSettingsField('syncTime', '09:30')).toEqual(ok('09:30')) expect(parseSettingsField('syncTime', '9:30')).toEqual(fail) expect(parseSettingsField('syncTime', '24:00')).toEqual(fail) }) it('booleans: strict', () => { expect(parseSettingsField('useAria2c', true)).toEqual(ok(true)) expect(parseSettingsField('useAria2c', 'true')).toEqual(fail) expect(parseSettingsField('enableCustomCommands', false)).toEqual(ok(false)) }) it('ytdlpChannel: allowlist (F1)', () => { expect(parseSettingsField('ytdlpChannel', 'nightly')).toEqual(ok('nightly')) expect(parseSettingsField('ytdlpChannel', 'master')).toEqual(fail) }) it('ytdlpLastUpdateCheck: non-negative finite number, coerced', () => { expect(parseSettingsField('ytdlpLastUpdateCheck', 123)).toEqual(ok(123)) expect(parseSettingsField('ytdlpLastUpdateCheck', -1)).toEqual(fail) expect(parseSettingsField('ytdlpLastUpdateCheck', 'NaN')).toEqual(fail) }) it('defaultTemplateId: string or null', () => { expect(parseSettingsField('defaultTemplateId', null)).toEqual(ok(null)) expect(parseSettingsField('defaultTemplateId', 't1')).toEqual(ok('t1')) expect(parseSettingsField('defaultTemplateId', 7)).toEqual(fail) }) it('downloadOptions: always sanitizes to a full valid object', () => { const r = parseSettingsField('downloadOptions', { embedSubtitles: true, audioFormat: 'nope' }) expect(r.ok).toBe(true) if (r.ok) { expect(r.value.embedSubtitles).toBe(true) expect(r.value.audioFormat).toBe(DEFAULT_DOWNLOAD_OPTIONS.audioFormat) expect(Object.keys(r.value).length).toBeGreaterThanOrEqual( Object.keys(DEFAULT_DOWNLOAD_OPTIONS).length - 3 // optional metadata fields may be undefined ) } }) it('folders: trims; empty clears; relative paths rejected (isSafeOutputDir)', () => { expect(parseSettingsField('videoFolder', ' D:\\Media ')).toEqual(ok('D:\\Media')) expect(parseSettingsField('videoFolder', '')).toEqual(ok('')) expect(parseSettingsField('audioFolder', 'Downloads')).toEqual(fail) expect(parseSettingsField('audioFolder', '..\\elsewhere')).toEqual(fail) }) it('filenameTemplate: trimmed and safety-checked', () => { expect(parseSettingsField('filenameTemplate', ' %(title)s.%(ext)s ')).toEqual( ok('%(title)s.%(ext)s') ) expect(parseSettingsField('filenameTemplate', '../%(title)s.%(ext)s')).toEqual(fail) }) it('rateLimit: yt-dlp format or empty', () => { expect(parseSettingsField('rateLimit', '2.5M')).toEqual(ok('2.5M')) expect(parseSettingsField('rateLimit', ' 500K ')).toEqual(ok('500K')) expect(parseSettingsField('rateLimit', '')).toEqual(ok('')) expect(parseSettingsField('rateLimit', 'fast')).toEqual(fail) }) it('quality presets: allowlisted', () => { expect(parseSettingsField('defaultVideoQuality', '720p')).toEqual(ok('720p')) expect(parseSettingsField('defaultVideoQuality', '4K')).toEqual(fail) expect(parseSettingsField('defaultAudioQuality', 'Best')).toEqual(ok('Best')) }) it('secrets: plain strings pass through (encryption happens in settings.ts)', () => { expect(parseSettingsField('proxy', 'socks5://u:p@h:1')).toEqual(ok('socks5://u:p@h:1')) expect(parseSettingsField('updateToken', ' tok ')).toEqual(ok('tok')) expect(parseSettingsField('proxy', 42)).toEqual(fail) }) it('unknown keys are skipped', () => { expect(parseSettingsField('nope' as never, true)).toEqual(fail) }) })