fix(notifications): validate success mode settings

This commit is contained in:
Sucukdeluxe
2026-08-22 03:51:47 +02:00
parent 754231d15c
commit 161e111c2f
3 changed files with 37 additions and 12 deletions
+3
View File
@@ -82,6 +82,9 @@ export function validateRendererSettingsUpdate(value: unknown, current: AppSetti
if (!(key in safe)) {
invalid();
}
if (key === "notifyPackageSuccessMode" && entry !== "digest" && entry !== "individual") {
invalid();
}
validateTopLevelType(entry, safe[key]);
validateJsonValue(entry);
output[key] = entry;
+8
View File
@@ -22,6 +22,14 @@ describe("renderer settings validation", () => {
expect(validateRendererSettingsUpdate(update, current)).toEqual(update);
});
it("rejects unsupported package success modes at the IPC boundary", () => {
const current = defaultSettings();
expect(validateRendererSettingsUpdate({ notifyPackageSuccessMode: "digest" }, current)).toEqual({ notifyPackageSuccessMode: "digest" });
expect(validateRendererSettingsUpdate({ notifyPackageSuccessMode: "individual" }, current)).toEqual({ notifyPackageSuccessMode: "individual" });
expect(() => validateRendererSettingsUpdate({ notifyPackageSuccessMode: "batched" }, current)).toThrow("Settings-Payload ist ungültig");
});
it("accepts a complete settings save when an account status has no optional email", () => {
const current = defaultSettings();
current.debridAccountStatuses = {
+26 -12
View File
@@ -52,14 +52,17 @@ describe("settings storage", () => {
expect(loadSettingsFrom({ notifyOnPackageCompleted: true, notifyPackageSuccessMode: "digest" }).notifyPackageSuccessMode).toBe("digest");
});
it("normalizes notification defaults and numeric limits", () => {
it("preserves an explicit individual success mode when legacy package notifications are disabled", () => {
expect(loadSettingsFrom({ notifyOnPackageCompleted: false, notifyPackageSuccessMode: "individual" }).notifyPackageSuccessMode).toBe("individual");
});
it("falls back from an invalid persisted success mode according to the legacy package toggle", () => {
expect(loadSettingsFrom({ notifyOnPackageCompleted: true, notifyPackageSuccessMode: "invalid" }).notifyPackageSuccessMode).toBe("individual");
expect(loadSettingsFrom({ notifyOnPackageCompleted: false, notifyPackageSuccessMode: "invalid" }).notifyPackageSuccessMode).toBe("digest");
});
it("loads notification defaults for new settings", () => {
const defaults = loadSettingsFrom({});
const normalized = normalizeSettings({
...defaultSettings(),
notifyRemainingThresholdGb: 0,
notifyStallAfterSeconds: 3_601,
notifyStallCooldownMinutes: 1
});
expect(defaults).toEqual(expect.objectContaining({
notifyPackageSuccessMode: "digest",
@@ -70,11 +73,22 @@ describe("settings storage", () => {
notifyStallCooldownMinutes: 10,
notifyOnDownloadRecovery: true
}));
expect(normalized).toEqual(expect.objectContaining({
notifyRemainingThresholdGb: 1,
notifyStallAfterSeconds: 3_600,
notifyStallCooldownMinutes: 5
}));
});
it.each([
["notifyRemainingThresholdGb", 0, 1],
["notifyRemainingThresholdGb", 100_001, 100_000],
["notifyRemainingThresholdGb", "invalid", 50],
["notifyStallAfterSeconds", 59, 60],
["notifyStallAfterSeconds", 3_601, 3_600],
["notifyStallAfterSeconds", "invalid", 90],
["notifyStallCooldownMinutes", 4, 5],
["notifyStallCooldownMinutes", 1_441, 1_440],
["notifyStallCooldownMinutes", "invalid", 10]
] as const)("normalizes %s from %s to %s", (key, input, expected) => {
const normalized = normalizeSettings({ ...defaultSettings(), [key]: input } as AppSettings);
expect(normalized[key]).toBe(expected);
});
it("enables package disclosure motion by default and preserves an explicit opt-out", () => {