diff --git a/src/main/renderer-settings.ts b/src/main/renderer-settings.ts index 4aa517a..ac219b5 100644 --- a/src/main/renderer-settings.ts +++ b/src/main/renderer-settings.ts @@ -30,7 +30,11 @@ function validateJsonValue(value: unknown, depth = 0): void { } const entries = Object.entries(value as Record); if (entries.length > 10_000) invalid(); - entries.forEach(([, entry]) => validateJsonValue(entry, depth + 1)); + entries.forEach(([, entry]) => { + if (entry !== undefined) { + validateJsonValue(entry, depth + 1); + } + }); } function validateTopLevelType(value: unknown, expected: unknown): void { @@ -64,6 +68,9 @@ export function validateRendererSettingsUpdate(value: unknown, current: AppSetti const input = value as Record; const output: Record = {}; for (const [key, entry] of Object.entries(input)) { + if (entry === undefined) { + continue; + } if (DERIVED_KEYS.has(key)) { continue; } diff --git a/tests/renderer-settings.test.ts b/tests/renderer-settings.test.ts new file mode 100644 index 0000000..64aeac8 --- /dev/null +++ b/tests/renderer-settings.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from "vitest"; +import { defaultSettings } from "../src/main/constants"; +import { createRendererSettings } from "../src/main/renderer-state"; +import { validateRendererSettingsUpdate } from "../src/main/renderer-settings"; + +describe("renderer settings validation", () => { + it("accepts a complete settings save when an account status has no optional email", () => { + const current = defaultSettings(); + current.debridAccountStatuses = { + "svc-realdebrid": { + accountId: "svc-realdebrid", + provider: "realdebrid", + label: "Real-Debrid", + maskedLogin: "user", + valid: true, + isPremium: true, + premiumUntilMs: null, + message: "Premium aktiv", + checkedAt: 1_700_000_000_000 + } + }; + const rendererSettings = createRendererSettings(current); + + expect(rendererSettings.debridAccountStatuses["svc-realdebrid"]).toHaveProperty("email", undefined); + const validated = validateRendererSettingsUpdate(rendererSettings, current); + + expect(validated.debridAccountStatuses).toEqual(rendererSettings.debridAccountStatuses); + expect(validated.animatePackageDisclosure).toBe(true); + expect(validated).not.toHaveProperty("configuredProviders"); + expect(validated).not.toHaveProperty("archivePasswordListConfigured"); + expect(validated).not.toHaveProperty("notifyUrlConfigured"); + }); + + it("ignores omitted optional top-level values but still rejects unknown concrete settings", () => { + const current = defaultSettings(); + + expect(validateRendererSettingsUpdate({ columnOrderVersion: undefined }, current)).toEqual({}); + expect(() => validateRendererSettingsUpdate({ obsoleteSetting: true }, current)).toThrow("Settings-Payload ist ungültig"); + }); +});