Test: Regressionstests fuer die Failed-Transition-Benachrichtigung (Mixed-Paket, Dedup, Run-Guard, Marker-Freigabe)

This commit is contained in:
Sucukdeluxe 2026-06-10 00:37:40 +02:00
parent 86d935e449
commit c2ba225047

153
tests/notify-hooks.test.ts Normal file
View File

@ -0,0 +1,153 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
vi.mock("../src/main/notify", async (importActual) => {
const actual = await importActual<typeof import("../src/main/notify")>();
return { ...actual, sendNotification: vi.fn().mockResolvedValue(true) };
});
import { DownloadManager } from "../src/main/download-manager";
import { defaultSettings } from "../src/main/constants";
import { createStoragePaths, emptySession } from "../src/main/storage";
import { shutdownItemLogs } from "../src/main/item-log";
import { shutdownPackageLogs } from "../src/main/package-log";
import { shutdownRenameLog } from "../src/main/rename-log";
import { sendNotification } from "../src/main/notify";
const mockedSend = sendNotification as unknown as ReturnType<typeof vi.fn>;
const tempDirs: string[] = [];
afterEach(() => {
mockedSend.mockClear();
shutdownItemLogs();
shutdownPackageLogs();
shutdownRenameLog();
for (const dir of tempDirs.splice(0)) {
try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* ignore */ }
}
});
function setup(): { manager: DownloadManager; session: ReturnType<typeof emptySession> } {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-nh-"));
tempDirs.push(root);
const session = emptySession();
const manager = new DownloadManager(
{
...defaultSettings(),
token: "rd-token",
outputDir: path.join(root, "out"),
extractDir: path.join(root, "extract"),
notifyUrl: "https://discord.com/api/webhooks/123/abc",
notifyOnPackageCompleted: true,
notifyOnPackageFailed: true
},
session,
createStoragePaths(path.join(root, "state"))
);
return { manager, session };
}
function addPackage(session: ReturnType<typeof emptySession>, itemStatuses: string[]): any {
const pkgId = "pkg-1";
const pkg: any = {
id: pkgId,
name: "Test.Show.S01",
outputDir: "C:/out",
extractDir: "C:/extract",
status: "queued",
itemIds: itemStatuses.map((_s, i) => `it-${i}`),
cancelled: false,
enabled: true,
priority: "normal",
createdAt: 1,
updatedAt: 1
};
session.packages[pkgId] = pkg;
session.packageOrder.push(pkgId);
itemStatuses.forEach((status, i) => {
session.items[`it-${i}`] = {
id: `it-${i}`,
packageId: pkgId,
url: `https://dummy/${i}`,
provider: null,
status,
retries: 0,
speedBps: 0,
downloadedBytes: 0,
totalBytes: null,
progressPercent: 0,
fileName: `f${i}.rar`,
targetPath: "",
resumable: true,
attempts: 1,
lastError: "",
fullStatus: "",
createdAt: 1,
updatedAt: 1
} as any;
});
return pkg;
}
describe("refreshPackageStatus failed-transition notify", () => {
it("notifies a MIXED package (some success, last finisher failed) — the lost-webhook case", () => {
const { manager, session } = setup();
const pkg = addPackage(session, ["completed", "failed"]);
session.running = true;
(manager as any).refreshPackageStatus(pkg);
expect(pkg.status).toBe("failed");
expect(mockedSend).toHaveBeenCalledTimes(1);
expect(mockedSend.mock.calls[0][1].title).toBe("❌ Paket fehlgeschlagen");
expect(mockedSend.mock.calls[0][1].message).toContain("1 von 2");
});
it("notifies an all-failed package and dedups repeat refreshes", () => {
const { manager, session } = setup();
const pkg = addPackage(session, ["failed", "failed"]);
session.running = true;
(manager as any).refreshPackageStatus(pkg);
(manager as any).refreshPackageStatus(pkg);
expect(pkg.status).toBe("failed");
expect(mockedSend).toHaveBeenCalledTimes(1);
});
it("stays silent outside a run (startup recovery must not spam)", () => {
const { manager, session } = setup();
const pkg = addPackage(session, ["failed"]);
session.running = false;
(manager as any).refreshPackageStatus(pkg);
expect(pkg.status).toBe("failed");
expect(mockedSend).not.toHaveBeenCalled();
});
it("does not notify while items are still pending", () => {
const { manager, session } = setup();
const pkg = addPackage(session, ["failed", "queued"]);
session.running = true;
(manager as any).refreshPackageStatus(pkg);
expect(pkg.status).toBe("queued");
expect(mockedSend).not.toHaveBeenCalled();
});
it("releases the dedup marker when the send ultimately fails (retro-notify possible)", async () => {
const { manager, session } = setup();
const pkg = addPackage(session, ["failed", "failed"]);
session.running = true;
mockedSend.mockResolvedValueOnce(false);
(manager as any).refreshPackageStatus(pkg);
await new Promise((r) => setTimeout(r, 0));
expect((manager as any).notifiedPackages.has(pkg.id)).toBe(false);
});
});