feat(notifications): add settings and migration
This commit is contained in:
@@ -4,6 +4,24 @@ import { createRendererSettings } from "../src/main/renderer-state";
|
||||
import { validateRendererSettingsUpdate } from "../src/main/renderer-settings";
|
||||
|
||||
describe("renderer settings validation", () => {
|
||||
it("accepts every editable notification control while keeping the webhook write-only", () => {
|
||||
const current = { ...defaultSettings(), notifyUrl: "https://notify.example.test/private-hook" };
|
||||
const update = {
|
||||
notifyPackageSuccessMode: "individual",
|
||||
notifyOnRemainingBelow: true,
|
||||
notifyRemainingThresholdGb: 25,
|
||||
notifyOnDownloadStall: true,
|
||||
notifyStallAfterSeconds: 120,
|
||||
notifyStallCooldownMinutes: 15,
|
||||
notifyOnDownloadRecovery: false
|
||||
};
|
||||
const projected = createRendererSettings(current);
|
||||
|
||||
expect(projected).not.toHaveProperty("notifyUrl");
|
||||
expect(projected.notifyUrlConfigured).toBe(true);
|
||||
expect(validateRendererSettingsUpdate(update, current)).toEqual(update);
|
||||
});
|
||||
|
||||
it("accepts a complete settings save when an account status has no optional email", () => {
|
||||
const current = defaultSettings();
|
||||
current.debridAccountStatuses = {
|
||||
|
||||
@@ -40,6 +40,18 @@ const ACCOUNT_FIXTURES: Array<{
|
||||
];
|
||||
|
||||
describe("renderer state serialization", () => {
|
||||
it("projects notification controls with their safe defaults", () => {
|
||||
expect(createRendererState(defaultSettings()).settings).toEqual(expect.objectContaining({
|
||||
notifyPackageSuccessMode: "digest",
|
||||
notifyOnRemainingBelow: false,
|
||||
notifyRemainingThresholdGb: 50,
|
||||
notifyOnDownloadStall: false,
|
||||
notifyStallAfterSeconds: 90,
|
||||
notifyStallCooldownMinutes: 10,
|
||||
notifyOnDownloadRecovery: true
|
||||
}));
|
||||
});
|
||||
|
||||
it("projects distinct Real-Debrid API and Web rows with per-account state", () => {
|
||||
const firstToken = "fixture-renderer-rd-first-1aB2";
|
||||
const secondToken = "fixture-renderer-rd-second-3cD4";
|
||||
|
||||
@@ -22,6 +22,14 @@ async function saveSettingsInMode(mode: SettingsSaveMode, paths: ReturnType<type
|
||||
}
|
||||
}
|
||||
|
||||
function loadSettingsFrom(raw: Record<string, unknown>): AppSettings {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-store-"));
|
||||
tempDirs.push(dir);
|
||||
const paths = createStoragePaths(dir);
|
||||
fs.writeFileSync(paths.configFile, JSON.stringify(raw), "utf8");
|
||||
return loadSettings(paths);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
configureCredentialProtector({
|
||||
isEncryptionAvailable: () => true,
|
||||
@@ -38,6 +46,37 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("settings storage", () => {
|
||||
it("migrates legacy package success notifications without changing their delivery frequency", () => {
|
||||
expect(loadSettingsFrom({ notifyOnPackageCompleted: true }).notifyPackageSuccessMode).toBe("individual");
|
||||
expect(loadSettingsFrom({}).notifyPackageSuccessMode).toBe("digest");
|
||||
expect(loadSettingsFrom({ notifyOnPackageCompleted: true, notifyPackageSuccessMode: "digest" }).notifyPackageSuccessMode).toBe("digest");
|
||||
});
|
||||
|
||||
it("normalizes notification defaults and numeric limits", () => {
|
||||
const defaults = loadSettingsFrom({});
|
||||
const normalized = normalizeSettings({
|
||||
...defaultSettings(),
|
||||
notifyRemainingThresholdGb: 0,
|
||||
notifyStallAfterSeconds: 3_601,
|
||||
notifyStallCooldownMinutes: 1
|
||||
});
|
||||
|
||||
expect(defaults).toEqual(expect.objectContaining({
|
||||
notifyPackageSuccessMode: "digest",
|
||||
notifyOnRemainingBelow: false,
|
||||
notifyRemainingThresholdGb: 50,
|
||||
notifyOnDownloadStall: false,
|
||||
notifyStallAfterSeconds: 90,
|
||||
notifyStallCooldownMinutes: 10,
|
||||
notifyOnDownloadRecovery: true
|
||||
}));
|
||||
expect(normalized).toEqual(expect.objectContaining({
|
||||
notifyRemainingThresholdGb: 1,
|
||||
notifyStallAfterSeconds: 3_600,
|
||||
notifyStallCooldownMinutes: 5
|
||||
}));
|
||||
});
|
||||
|
||||
it("enables package disclosure motion by default and preserves an explicit opt-out", () => {
|
||||
const legacy = { ...defaultSettings() } as Partial<AppSettings>;
|
||||
delete legacy.animatePackageDisclosure;
|
||||
|
||||
@@ -160,9 +160,16 @@ function createSettings(): AppSettings {
|
||||
backupIncludeRemoteDiagnostics: false,
|
||||
notifyUrl: "https://example.test/visual-webhook",
|
||||
notifyMention: "@visual",
|
||||
notifyOnPackageCompleted: true,
|
||||
notifyOnPackageFailed: true,
|
||||
notifyOnRunFinished: true,
|
||||
notifyOnPackageCompleted: true,
|
||||
notifyOnPackageFailed: true,
|
||||
notifyOnRunFinished: true,
|
||||
notifyPackageSuccessMode: "digest",
|
||||
notifyOnRemainingBelow: false,
|
||||
notifyRemainingThresholdGb: 50,
|
||||
notifyOnDownloadStall: false,
|
||||
notifyStallAfterSeconds: 90,
|
||||
notifyStallCooldownMinutes: 10,
|
||||
notifyOnDownloadRecovery: true,
|
||||
totalDownloadedAllTime: 987654321000,
|
||||
totalCompletedFilesAllTime: 842,
|
||||
totalRuntimeAllTimeMs: 172800000,
|
||||
|
||||
Reference in New Issue
Block a user