import { test } from "node:test"; import assert from "node:assert/strict"; import { AUDIO_CHECK_COPY_TIMEOUT_MAX_SECONDS, AUDIO_CHECK_INTERVAL_MIN_SECONDS, AUDIO_CHECK_MAX_ROLLBACKS_MAX, CHANNEL_CONFIG_COERCIONS, CHANNEL_CONFIG_FIELD_DOCS, CHANNEL_CONFIG_KEYS, CHANNEL_SLEEP_BETWEEN_DOWNLOADS_MAX_SECONDS, CHANNEL_SYNC_STATE_KEYS, KEEP_LATEST_MAX, SYNC_INTERVAL_MAX_MINUTES, parseChannelConfig, type ChannelConfig, } from "./channelConfig"; import type { z } from "zod"; import { channelConfigObjectSchema, channelConfigSchema, } from "./channelConfigSchema"; const parse = (raw: unknown) => channelConfigSchema.parse(raw); // The zod object's output names exactly ChannelConfig's keys, and fits it — // `handling` aside: its coercion may answer undefined, and it is isChannelShape, // run first, that guarantees it. type Out = z.output; const sameKeys: [keyof Out] extends [keyof ChannelConfig] ? [keyof ChannelConfig] extends [keyof Out] ? true : false : false = true; const fits: Omit extends Omit ? true : false = true; test("one key list: docs = coercions = schema shape, sync-state keys inside it", () => { assert.deepEqual(Object.keys(CHANNEL_CONFIG_COERCIONS), [...CHANNEL_CONFIG_KEYS]); assert.deepEqual(Object.keys(channelConfigObjectSchema.shape), [...CHANNEL_CONFIG_KEYS]); assert.deepEqual(Object.keys(CHANNEL_CONFIG_FIELD_DOCS), [...CHANNEL_CONFIG_KEYS]); assert.equal(CHANNEL_CONFIG_KEYS.length, 33); assert.equal(sameKeys, true); assert.equal(fits, true); for (const k of CHANNEL_SYNC_STATE_KEYS) assert.ok(CHANNEL_CONFIG_KEYS.includes(k), k); }); test("not a channel → null", () => { for (const raw of [null, undefined, 3, "youtube", [], {}, { handling: "x" }, { handling: null }]) { assert.equal(parse(raw), null, JSON.stringify(raw)); assert.equal(parseChannelConfig(raw), null, JSON.stringify(raw)); } }); test("an invalid optional key is OMITTED, not defaulted", () => { const cfg = parse({ handling: "youtube", keepLatest: -1, platform: "nope", postFetcher: " ", audioCheck: { enabled: "yes" }, downloadFilter: { include: " ", rejectedLivestreams: "chat-only" }, ytdlpExtraArgs: ["a", 1], syncIntervalMinutes: "60", })!; assert.deepEqual(cfg, { handling: "youtube" }); for (const k of ["keepLatest", "platform", "postFetcher", "audioCheck", "downloadFilter", "ytdlpExtraArgs", "syncIntervalMinutes"]) { assert.equal(k in cfg, false, k); } }); test("sourceVideoQuality: the two qualities parse, anything else is omitted", () => { for (const q of ["original", "video_720"] as const) { assert.equal(parse({ handling: "youtube", sourceVideoQuality: q })!.sourceVideoQuality, q); assert.equal(parseChannelConfig({ handling: "youtube", sourceVideoQuality: q })!.sourceVideoQuality, q); } for (const junk of ["720", "bestvideo_audio", "", 720, null]) { const cfg = parse({ handling: "youtube", sourceVideoQuality: junk })!; assert.equal("sourceVideoQuality" in cfg, false, JSON.stringify(junk)); } }); test("clamps: 0 is preserved as the disabled sentinel, positives clamp, fractions floor", () => { const cfg = parse({ handling: "transcribe", keepLatest: 0, syncIntervalMinutes: 0, fullSweepIntervalMinutes: 0, sleepBetweenDownloadsSeconds: 0, })!; assert.equal(cfg.keepLatest, 0); assert.equal(cfg.syncIntervalMinutes, 0); assert.equal(cfg.fullSweepIntervalMinutes, 0); assert.equal(cfg.sleepBetweenDownloadsSeconds, 0); const big = parse({ handling: "transcribe", keepLatest: 1e9, syncIntervalMinutes: 0.5, fullSweepIntervalMinutes: 1e9, sleepBetweenDownloadsSeconds: 1e9, audioCheck: { enabled: true, intervalSeconds: 1, maxRollbacks: 1e9, copyTimeoutSeconds: 1e9 }, })!; assert.equal(big.keepLatest, KEEP_LATEST_MAX); assert.equal(big.syncIntervalMinutes, 1); assert.equal(big.fullSweepIntervalMinutes, SYNC_INTERVAL_MAX_MINUTES); assert.equal(big.sleepBetweenDownloadsSeconds, CHANNEL_SLEEP_BETWEEN_DOWNLOADS_MAX_SECONDS); assert.deepEqual(big.audioCheck, { enabled: true, intervalSeconds: AUDIO_CHECK_INTERVAL_MIN_SECONDS, maxRollbacks: AUDIO_CHECK_MAX_ROLLBACKS_MAX, copyTimeoutSeconds: AUDIO_CHECK_COPY_TIMEOUT_MAX_SECONDS, }); assert.equal(parse({ handling: "youtube", keepLatest: 2.9 })!.keepLatest, 2); }); test("trims and normalises", () => { const cfg = parse({ handling: "youtube", socialHandle: " @me ", savedVideosDir: " /x ", dataDir: " /d ", mediaDir: " /m/chan/media ", cookiesFromBrowser: " firefox ", downloadFilter: { include: " a ", exclude: "", includeLivestreams: true, rejectedLivestreams: "skip" }, })!; assert.equal(cfg.socialHandle, "me"); assert.equal(cfg.savedVideosDir, "/x"); assert.equal(cfg.dataDir, "/d"); assert.equal(cfg.mediaDir, "/m/chan/media"); assert.equal(cfg.cookiesFromBrowser, "firefox"); assert.deepEqual(cfg.downloadFilter, { include: "a", includeLivestreams: true }); }); test("excludeFromSync and unknown keys are dropped; every emitted key is declared", () => { const cfg = parse({ handling: "youtube", excludeFromSync: true, bogus: 1, name: "n" })!; assert.deepEqual(cfg, { handling: "youtube", name: "n" }); const full = parse({ handling: "transcribe", sourceKind: "social", name: "n", url: "u", lastSyncedAt: "t", audioCheck: { enabled: false }, })!; for (const k of Object.keys(full)) { assert.ok((CHANNEL_CONFIG_KEYS as readonly string[]).includes(k), k); } }); test("the zod schema and the client-safe parser agree, key order included", () => { const vals: unknown[] = [undefined, null, 0, -1, 2.5, 1e9, "", " x ", "@h", true, false, [], ["a"], {}, { enabled: true, intervalSeconds: 5 }, { include: "a" }]; const pools: Record = { handling: ["youtube", "transcribe"], platform: ["youtube", "rumble", "nope"], cookieMode: ["always", "never", "nope"], sourceVideoQuality: ["original", "video_720", "nope"], }; let seed = 7; const rand = () => ((seed = (seed * 1103515245 + 12345) % 2 ** 31) / 2 ** 31); for (let i = 0; i < 3000; i++) { const raw: Record = {}; for (const k of [...CHANNEL_CONFIG_KEYS, "excludeFromSync", "bogus"]) { if (rand() < 0.5) continue; const pool = pools[k] && rand() < 0.8 ? pools[k] : vals; raw[k] = pool[Math.floor(rand() * pool.length)]; } const a = parseChannelConfig(raw); const b = parse(raw); assert.deepEqual(b, a, JSON.stringify(raw)); assert.deepEqual(Object.keys(b ?? {}), Object.keys(a ?? {})); } });