Files
Multi-Debrid-Downloader/tests/account-preload.test.ts
T
Sucukdeluxe bdcf9e3754 Persist collector state and stabilize download controls
Persist collector packages and disclosure state through validated atomic AppData storage with backup recovery and shutdown synchronization. Align download sidebar metrics with the hidden-extracted presentation scope and keep Start available through temporary Real-Debrid cooldowns while preserving hard account blocks.
2026-08-26 17:24:07 +02:00

170 lines
7.0 KiB
TypeScript

import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { IPC_CHANNELS } from "../src/shared/ipc";
import type { ElectronApi } from "../src/shared/preload-api";
const electron = vi.hoisted(() => ({
api: undefined as ElectronApi | undefined,
invoke: vi.fn<(...args: unknown[]) => Promise<unknown>>(async () => undefined),
getPathForFile: vi.fn(() => "C:\\Imports\\dropped.dlc"),
on: vi.fn(),
removeListener: vi.fn(),
sendSync: vi.fn((_: unknown, state: unknown) => state)
}));
vi.mock("electron", () => ({
contextBridge: {
exposeInMainWorld: (_name: string, api: ElectronApi) => {
electron.api = api;
}
},
ipcRenderer: {
invoke: electron.invoke,
on: electron.on,
removeListener: electron.removeListener,
sendSync: electron.sendSync,
send: vi.fn()
},
webUtils: { getPathForFile: electron.getPathForFile }
}));
describe("account preload contract", () => {
beforeAll(async () => {
await import("../src/preload/preload");
});
beforeEach(() => {
electron.invoke.mockClear();
electron.sendSync.mockClear();
});
it("forwards submitted secrets only in write-only account commands", async () => {
const secret = "fixture-preload-secret-5zK1";
electron.invoke.mockResolvedValueOnce({ accountId: "mda_fixture", settings: { language: "de" }, accounts: [] });
const result = await electron.api?.createAccount({
action: "create",
kind: "megadebrid-api",
identity: "preload-account@example.test",
secret,
dailyLimitBytes: 0
});
expect(electron.invoke).toHaveBeenCalledWith(
IPC_CHANNELS.CREATE_ACCOUNT,
expect.objectContaining({ secret })
);
expect(JSON.stringify(result)).not.toContain(secret);
});
it("exposes separate replace, update-secret and delete channels", async () => {
electron.invoke.mockResolvedValue({ accountId: "mda_fixture", settings: { language: "de" }, accounts: [] });
await electron.api?.replaceAccount({ action: "replace", kind: "megadebrid-api", accountId: "mda_fixture", identity: "account@example.test", secret: "", dailyLimitBytes: 0 });
await electron.api?.updateAccountSecret({ action: "update-secret", kind: "megadebrid-api", accountId: "mda_fixture", secret: "fixture-new-secret-8sP4" });
await electron.api?.deleteAccount({ action: "delete", kind: "megadebrid-api", accountId: "mda_fixture" });
expect(electron.invoke.mock.calls.map((call) => call[0])).toEqual([
IPC_CHANNELS.REPLACE_ACCOUNT,
IPC_CHANNELS.UPDATE_ACCOUNT_SECRET,
IPC_CHANNELS.DELETE_ACCOUNT
]);
});
it("forwards the selected bulk account-check scope", async () => {
electron.invoke.mockResolvedValue([]);
await electron.api?.checkDebridAccounts("active");
await electron.api?.checkDebridAccounts("all");
expect(electron.invoke.mock.calls).toEqual([
[IPC_CHANNELS.CHECK_DEBRID_ACCOUNTS, "active"],
[IPC_CHANNELS.CHECK_DEBRID_ACCOUNTS, "all"]
]);
});
it("forwards account-bound existing and create browser logins", async () => {
await electron.api?.openRealDebridLogin({ accountId: "rdw_existing" });
await electron.api?.openRealDebridLogin({ accountId: "rdw_reserved", create: true, dailyLimitBytes: 10_000 });
expect(electron.invoke.mock.calls).toEqual([
[IPC_CHANNELS.OPEN_REALDEBRID_LOGIN, { accountId: "rdw_existing" }],
[IPC_CHANNELS.OPEN_REALDEBRID_LOGIN, { accountId: "rdw_reserved", create: true, dailyLimitBytes: 10_000 }]
]);
});
it("reveals a stored secret only through the explicit account channel", async () => {
electron.invoke.mockResolvedValueOnce({ secret: "fixture-revealed-secret-7gH8" });
const result = await (electron.api as ElectronApi & {
revealAccountSecret: (request: { kind: "realdebrid-api"; accountId: string }) => Promise<{ secret: string }>;
}).revealAccountSecret({ kind: "realdebrid-api", accountId: "svc-realdebrid" });
expect(electron.invoke).toHaveBeenCalledWith(
IPC_CHANNELS.REVEAL_ACCOUNT_SECRET,
{ kind: "realdebrid-api", accountId: "svc-realdebrid" }
);
expect(result).toEqual({ secret: "fixture-revealed-secret-7gH8" });
});
it("loads the stored archive password list only through its dedicated channel", async () => {
const passwords = "fixture-archive-one\nfixture-archive-two";
electron.invoke.mockResolvedValueOnce({ passwords });
const result = await (electron.api as ElectronApi & {
getArchivePasswordList: () => Promise<{ passwords: string }>;
}).getArchivePasswordList();
expect(electron.invoke).toHaveBeenCalledWith(IPC_CHANNELS.GET_ARCHIVE_PASSWORD_LIST);
expect(result).toEqual({ passwords });
});
it("exposes separate collector preparation and enrichment channels", async () => {
const textRequest = { rawText: "https://example.com/file", addedAt: 1234 };
const packages = [{ id: "package", name: "Paket", nameSource: "inferred" as const, addedAt: 1234, links: [] }];
await electron.api?.prepareCollectorText(textRequest);
await electron.api?.prepareCollectorContainers(["C:\\Imports\\sample.dlc"], 2345);
await electron.api?.enrichCollectorPackages({ requestId: "request-preload", packages });
expect(electron.invoke.mock.calls).toEqual([
[IPC_CHANNELS.PREPARE_COLLECTOR_TEXT, textRequest],
[IPC_CHANNELS.PREPARE_COLLECTOR_CONTAINERS, ["C:\\Imports\\sample.dlc"], 2345],
[IPC_CHANNELS.ENRICH_COLLECTOR_PACKAGES, { requestId: "request-preload", packages }]
]);
});
it("loads and saves the persistent collector state through dedicated channels", async () => {
const state = { packages: [], collapsedPackageIds: [] };
await electron.api?.getCollectorState();
await electron.api?.saveCollectorState(state);
electron.api?.saveCollectorStateSync(state);
expect(electron.invoke.mock.calls).toEqual([
[IPC_CHANNELS.GET_COLLECTOR_STATE],
[IPC_CHANNELS.SAVE_COLLECTOR_STATE, state]
]);
expect(electron.sendSync).toHaveBeenCalledWith(IPC_CHANNELS.SAVE_COLLECTOR_STATE_SYNC, state);
});
it("resolves dropped files through Electron webUtils without IPC", () => {
const file = { name: "dropped.dlc" } as File;
expect(electron.api?.getPathForDroppedFile(file)).toBe("C:\\Imports\\dropped.dlc");
expect(electron.getPathForFile).toHaveBeenCalledWith(file);
expect(electron.invoke).not.toHaveBeenCalled();
});
it("subscribes and unsubscribes collector enrichment progress", () => {
const callback = vi.fn();
const unsubscribe = electron.api?.onCollectorEnrichmentProgress(callback);
const listener = electron.on.mock.calls.find((call) => call[0] === IPC_CHANNELS.COLLECTOR_ENRICHMENT_PROGRESS)?.[1] as ((event: unknown, value: unknown) => void) | undefined;
const progress = { requestId: "request-progress", result: { packages: [], invalidCount: 0, duplicateCount: 0 } };
listener?.({}, progress);
unsubscribe?.();
expect(callback).toHaveBeenCalledWith(progress);
expect(electron.removeListener).toHaveBeenCalledWith(IPC_CHANNELS.COLLECTOR_ENRICHMENT_PROGRESS, listener);
});
});