real-debrid-downloader/tests/support-bundle.test.ts
Sucukdeluxe 21803f316b Fix: Support-Bundle-Export friert die UI nicht mehr 1-2 Minuten ein (asynchrones IO)
buildSupportBundle lief komplett synchron auf dem Electron-Main-Thread:
existsSync/readdirSync/statSync/readFileSync (via AdmZip.addLocalFile) ueber
ALLE Log-Dateien — bei Hunderten item-logs und gleichzeitig laufenden
Downloads (Platte ausgelastet) blockierte das den Event-Loop fuer 1-2 min,
die ganze Oberflaeche stand. Live belegt: ein Bundle mit Hunderten
item-logs-Dateien.

Fix: Alle Datei-Zugriffe im Bundle-Build auf fs.promises (nicht-blockierend)
umgestellt, buildSupportBundle ist jetzt async und liefert Promise<Buffer>.
Dateiinhalte werden per readFile gelesen und mit zip.addFile(buffer)
hinzugefuegt (gleiche Zip-Struktur wie vorher). Der Event-Loop bleibt
waehrend der IO-Wartezeiten frei → UI friert nicht mehr ein. Caller
angepasst (app-controller.exportSupportBundle async, main.ts await,
debug-server via .then).

Test support-bundle.test.ts: valides Zip mit Overview + echter Datei;
ein paralleler Timer feuert waehrend des Builds (Event-Loop nicht blockiert).
840/840 gruen, tsc unveraendert (6), Build ok.
2026-06-17 03:14:01 +02:00

68 lines
2.3 KiB
TypeScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import AdmZip from "adm-zip";
import { afterEach, describe, expect, it } from "vitest";
import { buildSupportBundle } from "../src/main/support-bundle";
import type { DownloadManager } from "../src/main/download-manager";
const tempDirs: string[] = [];
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
try { fs.rmSync(dir, { recursive: true, force: true }); } catch { }
}
});
function fakeManager(): DownloadManager {
const snapshot = {
stats: {},
session: { packages: {}, items: {}, packageOrder: [] },
speedText: "",
etaText: "",
canStart: false,
canStop: false,
canPause: false
};
return {
getSnapshot: () => snapshot,
getPackageLogPath: () => null,
getItemLogPath: () => null
} as unknown as DownloadManager;
}
describe("buildSupportBundle (async, non-blocking)", () => {
it("returns a Promise and produces a valid zip with overview + a real on-disk file", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-bundle-"));
tempDirs.push(root);
fs.writeFileSync(path.join(root, "debug_host.txt"), "host-info-test", "utf8");
const promise = buildSupportBundle(fakeManager(), root, { hostDiagnosticsMode: "none" });
expect(promise).toBeInstanceOf(Promise);
const buffer = await promise;
expect(Buffer.isBuffer(buffer)).toBe(true);
expect(buffer.length).toBeGreaterThan(0);
const entries = new AdmZip(buffer).getEntries().map((e) => e.entryName);
expect(entries).toContain("overview/meta.json");
expect(entries).toContain("overview/settings.json");
expect(entries).toContain("runtime/debug_host.txt");
const hostEntry = new AdmZip(buffer).getEntry("runtime/debug_host.txt");
expect(hostEntry?.getData().toString("utf8")).toBe("host-info-test");
});
it("does not block the event loop while building (a concurrent timer still fires)", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-bundle-"));
tempDirs.push(root);
let timerFired = false;
const timer = setTimeout(() => { timerFired = true; }, 0);
await buildSupportBundle(fakeManager(), root, { hostDiagnosticsMode: "none" });
clearTimeout(timer);
await new Promise((resolve) => setTimeout(resolve, 0));
expect(timerFired).toBe(true);
});
});