real-debrid-downloader/tests/update-install-flow.test.ts
Sucukdeluxe dfd19260f6 Fix: Download-Queue läuft nach fehlgeschlagenem Update weiter (kein Stillstand bis Neustart)
Beim Klick auf "Update" stoppt das Tool zuerst die laufende Session
(stop({parkForRestart:true}) → running=false, alle Items auf "Wartet") und
startet dann die Installation. Schlug die Installation fehl — und das ist auf
realistischen Wegen möglich (git.24-music.de-Aussetzer, Netz weg über alle
Versuche, oder ein Release mit fehlendem/falschem Digest → Integritätsprüfung
schlägt fehl) — kam KEIN App-Neustart (nur bei Erfolg wird app.quit geplant) und
auch kein In-Process-Resume. Folge: die komplette Queue stand still (alle Items
"Wartet"), bis ein Mensch "Start" klickte oder das Programm neu startete — auf
einem unbeaufsichtigten Server mit ~1 TB/h ein echter Stillstand.

Fix: Der Stop-Installieren-Resume-Ablauf liegt jetzt in
runInstallWithResume(): War vorher eine Session aktiv und schlägt die
Installation fehl, wird die Session wieder gestartet (manager.start(),
idempotent — die Items sind bereits "queued", der Scheduler nimmt sie normal
auf). Der Resume greift auf BEIDEN Fehlerpfaden: started:false UND geworfene
Ausnahme (try/catch). Bei Erfolg (started:true) wird NICHT resumed (die App
beendet sich gleich), und war keine Session aktiv, passiert nichts.

Test: started:false → resume; install wirft → resume + rethrow; started:true →
kein resume; nicht-laufend → kein stop/resume. Logik in eine reine, testbare
Funktion ausgelagert (kein schwergewichtiger AppController-Konstruktor nötig).
2026-06-17 05:50:55 +02:00

64 lines
2.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { runInstallWithResume, InstallResumeManager } from "../src/main/update-install-flow";
function makeManager(running: boolean): InstallResumeManager & { startCalls: number; stopCalls: number; persistCalls: number; sessionRunning: boolean } {
return {
sessionRunning: running,
startCalls: 0,
stopCalls: 0,
persistCalls: 0,
isSessionRunning() {
return this.sessionRunning;
},
stop() {
this.stopCalls += 1;
this.sessionRunning = false;
},
persistNowSync() {
this.persistCalls += 1;
},
async start() {
this.startCalls += 1;
this.sessionRunning = true;
}
};
}
describe("runInstallWithResume", () => {
it("resumes a running session when the install returns started:false", async () => {
const m = makeManager(true);
const result = await runInstallWithResume(m, async () => ({ started: false }));
expect(result.started).toBe(false);
expect(m.stopCalls).toBe(1);
expect(m.startCalls).toBe(1);
expect(m.isSessionRunning()).toBe(true);
});
it("resumes a running session when the install THROWS, then rethrows", async () => {
const m = makeManager(true);
await expect(
runInstallWithResume(m, async () => {
throw new Error("network down");
})
).rejects.toThrow("network down");
expect(m.stopCalls).toBe(1);
expect(m.startCalls).toBe(1);
expect(m.isSessionRunning()).toBe(true);
});
it("does NOT resume when the install succeeds (started:true) — the app is about to quit", async () => {
const m = makeManager(true);
const result = await runInstallWithResume(m, async () => ({ started: true }));
expect(result.started).toBe(true);
expect(m.startCalls).toBe(0);
expect(m.isSessionRunning()).toBe(false);
});
it("does NOT resume when no session was running before the install", async () => {
const m = makeManager(false);
await runInstallWithResume(m, async () => ({ started: false }));
expect(m.stopCalls).toBe(0);
expect(m.startCalls).toBe(0);
});
});