fix(notifications): validate success mode settings
This commit is contained in:
@@ -82,6 +82,9 @@ export function validateRendererSettingsUpdate(value: unknown, current: AppSetti
|
|||||||
if (!(key in safe)) {
|
if (!(key in safe)) {
|
||||||
invalid();
|
invalid();
|
||||||
}
|
}
|
||||||
|
if (key === "notifyPackageSuccessMode" && entry !== "digest" && entry !== "individual") {
|
||||||
|
invalid();
|
||||||
|
}
|
||||||
validateTopLevelType(entry, safe[key]);
|
validateTopLevelType(entry, safe[key]);
|
||||||
validateJsonValue(entry);
|
validateJsonValue(entry);
|
||||||
output[key] = entry;
|
output[key] = entry;
|
||||||
|
|||||||
@@ -22,6 +22,14 @@ describe("renderer settings validation", () => {
|
|||||||
expect(validateRendererSettingsUpdate(update, current)).toEqual(update);
|
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", () => {
|
it("accepts a complete settings save when an account status has no optional email", () => {
|
||||||
const current = defaultSettings();
|
const current = defaultSettings();
|
||||||
current.debridAccountStatuses = {
|
current.debridAccountStatuses = {
|
||||||
|
|||||||
+26
-12
@@ -52,15 +52,18 @@ describe("settings storage", () => {
|
|||||||
expect(loadSettingsFrom({ notifyOnPackageCompleted: true, notifyPackageSuccessMode: "digest" }).notifyPackageSuccessMode).toBe("digest");
|
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", () => {
|
||||||
const defaults = loadSettingsFrom({});
|
expect(loadSettingsFrom({ notifyOnPackageCompleted: false, notifyPackageSuccessMode: "individual" }).notifyPackageSuccessMode).toBe("individual");
|
||||||
const normalized = normalizeSettings({
|
|
||||||
...defaultSettings(),
|
|
||||||
notifyRemainingThresholdGb: 0,
|
|
||||||
notifyStallAfterSeconds: 3_601,
|
|
||||||
notifyStallCooldownMinutes: 1
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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({});
|
||||||
|
|
||||||
expect(defaults).toEqual(expect.objectContaining({
|
expect(defaults).toEqual(expect.objectContaining({
|
||||||
notifyPackageSuccessMode: "digest",
|
notifyPackageSuccessMode: "digest",
|
||||||
notifyOnRemainingBelow: false,
|
notifyOnRemainingBelow: false,
|
||||||
@@ -70,11 +73,22 @@ describe("settings storage", () => {
|
|||||||
notifyStallCooldownMinutes: 10,
|
notifyStallCooldownMinutes: 10,
|
||||||
notifyOnDownloadRecovery: true
|
notifyOnDownloadRecovery: true
|
||||||
}));
|
}));
|
||||||
expect(normalized).toEqual(expect.objectContaining({
|
});
|
||||||
notifyRemainingThresholdGb: 1,
|
|
||||||
notifyStallAfterSeconds: 3_600,
|
it.each([
|
||||||
notifyStallCooldownMinutes: 5
|
["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", () => {
|
it("enables package disclosure motion by default and preserves an explicit opt-out", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user