From 5980b48d60d8f23e46c081676956fd39a6295044 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Sat, 15 Aug 2026 00:18:01 +0200 Subject: [PATCH] fix(settings): accept omitted optional values after upgrades Restore the v2.0.35 renderer settings validation behavior that was lost when the download path returned to the v2.0.28 baseline. Ignore undefined optional values at top level and inside structured settings while continuing to reject unknown concrete keys and invalid types. Cover a full settings save containing a Real-Debrid account status without the optional email field, plus obsolete optional-field handling. --- src/main/renderer-settings.ts | 9 +++++++- tests/renderer-settings.test.ts | 40 +++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 tests/renderer-settings.test.ts 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"); + }); +});