Files
Multi-Debrid-Downloader/tests/renderer-state.test.ts
Sucukdeluxe 1b7caba2eb feat: add Deepbrid, daily scheduling, and notification center
Add encrypted Deepbrid API accounts with account validation, provider routing, fallback, usage tracking, safe error handling, and verified 1Fichier downloads. Restore persistent recurring daily starts with local-calendar deduplication and legacy schedule compatibility. Add durable Discord package, run, remaining-volume, stall, and recovery notifications with privacy-safe telemetry and disk-failure recovery.
2026-08-24 07:37:55 +02:00

196 lines
10 KiB
TypeScript

import { describe, expect, it } from "vitest";
import crypto from "node:crypto";
import { defaultSettings } from "../src/main/constants";
import { createRendererState } from "../src/main/renderer-state";
import { getDebridLinkApiKeyId } from "../src/shared/debrid-link-keys";
import { getMegaDebridAccountId } from "../src/shared/mega-debrid-accounts";
import { serializeRealDebridApiAccounts } from "../src/shared/real-debrid-accounts";
import type { AppSettings, RendererAccountKind } from "../src/shared/types";
const SECRETS = {
token: "fixture-rd-token-7vQ2",
megaPassword: "fixture-mega-password-8kM3",
bestToken: "fixture-best-token-5xL9",
allDebridToken: "fixture-ad-token-4pR6",
ddownloadPassword: "fixture-dd-password-2tN8",
oneFichierApiKey: "fixture-onefichier-key-6cW1",
debridLinkApiKey: "fixture-debridlink-key-9hS4",
linkSnappyPassword: "fixture-linksnappy-password-3mB7",
archivePassword: "fixture-archive-password-1jD5",
notifyUrl: "https://notify.example.test/hooks/fixture-notify-secret-0fA2"
} as const;
const ACCOUNT_FIXTURES: Array<{
kind: RendererAccountKind;
secret: string;
settings: Partial<AppSettings>;
}> = [
{ kind: "realdebrid-api", secret: "fixture-rd-api-secret-1aK4", settings: { token: "fixture-rd-api-secret-1aK4" } },
{ kind: "realdebrid-web", secret: "fixture-rd-web-session-2bL5", settings: { token: "fixture-rd-web-session-2bL5", realDebridUseWebLogin: true } },
{ kind: "megadebrid-api", secret: "fixture-mega-api-secret-3cM6", settings: { megaCredentials: "mega-api@example.test:fixture-mega-api-secret-3cM6", megaDebridApiCredentials: "mega-api@example.test:fixture-mega-api-secret-3cM6", megaDebridApiEnabled: true } },
{ kind: "megadebrid-web", secret: "fixture-mega-web-secret-4dN7", settings: { megaCredentials: "mega-web@example.test:fixture-mega-web-secret-4dN7", megaDebridWebCredentials: "mega-web@example.test:fixture-mega-web-secret-4dN7", megaDebridWebEnabled: true } },
{ kind: "bestdebrid-api", secret: "fixture-best-api-secret-5eP8", settings: { bestToken: "fixture-best-api-secret-5eP8" } },
{ kind: "bestdebrid-web", secret: "fixture-best-web-session-6fQ9", settings: { bestToken: "fixture-best-web-session-6fQ9", bestDebridUseWebLogin: true } },
{ kind: "alldebrid-api", secret: "fixture-all-api-secret-7gR1", settings: { allDebridToken: "fixture-all-api-secret-7gR1" } },
{ kind: "alldebrid-web", secret: "fixture-all-web-session-8hS2", settings: { allDebridToken: "fixture-all-web-session-8hS2", allDebridUseWebLogin: true } },
{ kind: "ddownload-login", secret: "fixture-dd-secret-9jT3", settings: { ddownloadLogin: "dd@example.test", ddownloadPassword: "fixture-dd-secret-9jT3" } },
{ kind: "onefichier-api", secret: "fixture-one-secret-0kU4", settings: { oneFichierApiKey: "fixture-one-secret-0kU4" } },
{ kind: "debridlink-api", secret: "fixture-dl-secret-1mV5", settings: { debridLinkApiKeys: "fixture-dl-secret-1mV5" } },
{ kind: "linksnappy-login", secret: "fixture-ls-secret-2nW6", settings: { linkSnappyLogin: "ls@example.test", linkSnappyPassword: "fixture-ls-secret-2nW6" } }
];
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";
const firstId = "rda_rendererFirst";
const secondId = "rda_rendererSecond";
const settings = {
...defaultSettings(),
realDebridApiTokens: serializeRealDebridApiAccounts([{ id: firstId, token: firstToken }, { id: secondId, token: secondToken }]),
realDebridWebAccountIds: ["rdw_first"],
realDebridDisabledAccountIds: [secondId],
realDebridAccountDailyLimitBytes: { [firstId]: 100 },
realDebridAccountDailyUsageBytes: { [firstId]: 25 },
realDebridAccountTotalUsageBytes: { [firstId]: 500 },
debridAccountStatuses: {
[firstId]: { accountId: firstId, provider: "realdebrid" as const, label: "API-Token 1", maskedLogin: "Geschützter API-Token", valid: true, isPremium: true, premiumUntilMs: null, message: "Premium aktiv", checkedAt: 1 }
}
};
const state = createRendererState(settings);
const realDebridRows = state.accounts.filter((account) => account.provider === "realdebrid");
expect(realDebridRows).toEqual([
expect.objectContaining({ accountId: firstId, kind: "realdebrid-api", enabled: true, dailyLimitBytes: 100, dailyUsageBytes: 25, totalUsageBytes: 500, status: expect.objectContaining({ accountId: firstId }) }),
expect.objectContaining({ accountId: secondId, kind: "realdebrid-api", enabled: false }),
expect.objectContaining({ accountId: "rdw_first", kind: "realdebrid-web", enabled: true })
]);
expect(JSON.stringify(state)).not.toContain(firstToken);
expect(JSON.stringify(state)).not.toContain(secondToken);
expect(realDebridRows[0].accountId).not.toBe(`rda_${crypto.createHash("sha256").update(firstToken).digest("hex").slice(0, 32)}`);
});
it.each(ACCOUNT_FIXTURES)("serializes $kind without its representative secret", ({ kind, secret, settings }) => {
const state = createRendererState({ ...defaultSettings(), ...settings });
expect(state.accounts).toEqual(expect.arrayContaining([expect.objectContaining({ kind, hasSecret: true })]));
expect(JSON.stringify(state)).not.toContain(secret);
expect(JSON.stringify(state.settings)).not.toContain(secret);
});
it("excludes every provider and settings secret while preserving safe account metadata", () => {
const megaLogin = "renderer-fixture@example.test";
const settings = {
...defaultSettings(),
token: SECRETS.token,
megaLogin,
megaPassword: SECRETS.megaPassword,
megaCredentials: `${megaLogin}:${SECRETS.megaPassword}`,
megaDebridApiCredentials: `${megaLogin}:${SECRETS.megaPassword}`,
megaDebridApiEnabled: true,
bestToken: SECRETS.bestToken,
allDebridToken: SECRETS.allDebridToken,
ddownloadLogin: "renderer-dd@example.test",
ddownloadPassword: SECRETS.ddownloadPassword,
oneFichierApiKey: SECRETS.oneFichierApiKey,
debridLinkApiKeys: SECRETS.debridLinkApiKey,
linkSnappyLogin: "renderer-linksnappy@example.test",
linkSnappyPassword: SECRETS.linkSnappyPassword,
archivePasswordList: SECRETS.archivePassword,
notifyUrl: SECRETS.notifyUrl
};
const state = createRendererState(settings);
const serialized = JSON.stringify(state);
for (const secret of Object.values(SECRETS)) {
expect(serialized).not.toContain(secret);
}
expect(state.settings.archivePasswordListConfigured).toBe(true);
expect(state.settings.notifyUrlConfigured).toBe(true);
expect(state.accounts).toEqual(expect.arrayContaining([
expect.objectContaining({ kind: "realdebrid-api", hasSecret: true }),
expect.objectContaining({ kind: "megadebrid-api", identity: megaLogin, hasSecret: true }),
expect.objectContaining({ kind: "debridlink-api", hasSecret: true })
]));
});
it("redacts individual secrets embedded in status metadata for multi-account pools", () => {
const firstMegaSecret = "fixture-first-mega-pool-secret-3pX7";
const secondMegaSecret = "fixture-second-mega-pool-secret-4qY8";
const firstKey = "fixture-first-debridlink-pool-secret-5rZ9";
const secondKey = "fixture-second-debridlink-pool-secret-6sA1";
const secondMegaId = getMegaDebridAccountId("second-pool@example.test");
const secondKeyId = getDebridLinkApiKeyId(secondKey);
const settings = {
...defaultSettings(),
megaCredentials: `first-pool@example.test:${firstMegaSecret}\nsecond-pool@example.test:${secondMegaSecret}`,
megaDebridApiCredentials: `first-pool@example.test:${firstMegaSecret}\nsecond-pool@example.test:${secondMegaSecret}`,
megaDebridApiEnabled: true,
debridLinkApiKeys: `${firstKey}\n${secondKey}`,
debridAccountStatuses: {
[secondMegaId]: {
accountId: secondMegaId,
provider: "megadebrid" as const,
label: "Account 2",
maskedLogin: "se***st",
valid: false,
isPremium: false,
premiumUntilMs: null,
message: `Rejected ${secondMegaSecret}`,
checkedAt: 1
},
[secondKeyId]: {
accountId: secondKeyId,
provider: "debridlink" as const,
label: "Key 2",
maskedLogin: "fi***A1",
valid: false,
isPremium: false,
premiumUntilMs: null,
message: `Rejected ${secondKey}`,
checkedAt: 1
}
}
};
const serialized = JSON.stringify(createRendererState(settings));
for (const secret of [firstMegaSecret, secondMegaSecret, firstKey, secondKey]) {
expect(serialized).not.toContain(secret);
}
});
it("exposes only non-secret Real-Debrid account controls to the renderer", () => {
const settings = {
...defaultSettings(),
realDebridApiTokens: JSON.stringify({ version: 1, accounts: [{ id: "rda_visible", token: "fixture-rd-secret" }] }),
realDebridDisabledAccountIds: ["rda_visible"],
realDebridAccountDailyLimitBytes: { rda_visible: 10_000 },
realDebridAccountDailyUsageBytes: { rda_visible: 2_000 },
realDebridAccountTotalUsageBytes: { rda_visible: 8_000 }
};
const renderer = createRendererState(settings).settings;
expect(renderer.realDebridDisabledAccountIds).toEqual(["rda_visible"]);
expect(renderer.realDebridAccountDailyLimitBytes).toEqual({ rda_visible: 10_000 });
expect(renderer.realDebridAccountDailyUsageBytes).toEqual({ rda_visible: 2_000 });
expect(renderer.realDebridAccountTotalUsageBytes).toEqual({ rda_visible: 8_000 });
expect(JSON.stringify(renderer)).not.toContain("fixture-rd-secret");
expect(renderer).not.toHaveProperty("realDebridApiTokens");
expect(renderer).not.toHaveProperty("realDebridWebAccountIds");
});
});