Files
Multi-Debrid-Downloader/tests/backup-preload.test.ts
Sucukdeluxe 1cb381fa50 fix(security): require passphrases for local backups
Derive every MDD2 key from a non-empty user passphrase and the per-backup scrypt salt while keeping the embedded application material isolated to read-only MDD1 migration imports. Normalize missing, wrong, and authentication-failure results to the same controlled decryption error.

Add the modal-based export confirmation and format-aware import flow across renderer, preload, IPC, and controller boundaries. Clear transient passphrase state on completion or cancellation, retain pending import data only until consumption, and keep passphrases out of results, snapshots, payloads, and logs.

Cover mismatch and cancellation paths, MDD1 passphrase-free migration, preload forwarding, successful UI/crypto round-trips, one-byte-short ciphertext, authenticated empty ciphertext, and truncated legacy envelopes.
2026-08-11 22:32:58 +02:00

58 lines
2.1 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)
}));
vi.mock("electron", () => ({
contextBridge: {
exposeInMainWorld: (_name: string, api: ElectronApi) => {
electron.api = api;
}
},
ipcRenderer: {
invoke: electron.invoke,
on: vi.fn(),
removeListener: vi.fn(),
send: vi.fn()
}
}));
describe("backup preload contract", () => {
beforeAll(async () => {
await import("../src/preload/preload");
});
beforeEach(() => {
electron.invoke.mockClear();
});
it("forwards an export passphrase without adding it to the result contract", async () => {
electron.invoke.mockResolvedValueOnce({ saved: true });
const result = await electron.api?.exportBackup("one-operation test phrase");
expect(electron.invoke).toHaveBeenCalledWith(IPC_CHANNELS.EXPORT_BACKUP, "one-operation test phrase");
expect(result).toEqual({ saved: true });
});
it("keeps import selection and cancellation passphrase-free", async () => {
electron.invoke.mockResolvedValueOnce({ selected: true, requiresPassphrase: true });
await electron.api?.selectBackupImport();
await electron.api?.cancelBackupImport();
expect(electron.invoke).toHaveBeenNthCalledWith(1, IPC_CHANNELS.SELECT_BACKUP_IMPORT);
expect(electron.invoke).toHaveBeenNthCalledWith(2, IPC_CHANNELS.CANCEL_BACKUP_IMPORT);
});
it("forwards an import passphrase only to the consuming operation", async () => {
electron.invoke.mockResolvedValueOnce({ restored: true, relaunch: false, message: "Einstellungen wiederhergestellt" });
const result = await electron.api?.importBackup("one-operation test phrase");
expect(electron.invoke).toHaveBeenCalledWith(IPC_CHANNELS.IMPORT_BACKUP, "one-operation test phrase");
expect(result).toEqual({ restored: true, relaunch: false, message: "Einstellungen wiederhergestellt" });
});
});