feat(notifications): finalize package and run results

This commit is contained in:
Sucukdeluxe
2026-08-22 05:08:48 +02:00
parent c7e48891bb
commit 03da04b629
9 changed files with 1614 additions and 416 deletions
+105 -3
View File
@@ -185,7 +185,7 @@ describe("history model", () => {
expect(filterHistoryRows(searchable, "all", "d", now).map((row) => row.id)).toEqual(["new", "old"]);
});
it("derives hosters only from valid URL hostnames and clamps the calculated start time", () => {
it("derives hosters only from valid URL hostnames and clamps the calculated start time", () => {
expect(deriveHistoryHoster(["https://rapidgator.net/a", "https://rapidgator.net/b", "https://ddownload.com/c", "not a url"])).toBe("rapidgator.net, ddownload.com");
expect(deriveHistoryHoster([])).toBe("—");
expect(deriveHistoryHoster(undefined)).toBe("—");
@@ -194,8 +194,22 @@ describe("history model", () => {
const row = filterHistoryRows([entry({ id: "provider", name: "Provider", provider: "realdebrid", urls: [] })], "all", "", now)[0];
expect(row.hoster).toBe("—");
expect(row.providerLabel).toBe("Real-Debrid");
});
expect(row.providerLabel).toBe("Real-Debrid");
});
it("uses the authoritative lifecycle start instead of reconstructing it from completion", () => {
const lifecycleStartedAt = todayStart - 45_000;
const lifecycle = entry({
id: "lifecycle-start",
name: "Lifecycle",
startedAt: lifecycleStartedAt,
completedAt: todayStart + 60_000,
durationSeconds: 5
});
expect(deriveHistoryStartAt(lifecycle)).toBe(lifecycleStartedAt);
expect(filterHistoryRows([lifecycle], "all", "", now)[0].startAt).toBe(lifecycleStartedAt);
});
it("prunes removed ids and preserves the original set instance when every id survives", () => {
const stable = new Set(["today", "week"]);
@@ -597,6 +611,94 @@ describe("HistoryView", () => {
expect(html).toContain("https://rapidgator.net/file/test");
});
it("renders authoritative lifecycle timings, counts, failure phase and operation details", () => {
const structured = entry({
id: "structured",
name: "Strukturiert",
status: "partial",
startedAt: todayStart,
downloadEndedAt: todayStart + 60_000,
postProcessStartedAt: todayStart + 65_000,
completedAt: todayStart + 90_000,
downloadDurationSeconds: 60,
extractionDurationSeconds: 12,
remuxDurationSeconds: 8,
postProcessDurationSeconds: 25,
totalDurationSeconds: 90,
successfulFiles: 3,
failedFiles: 1,
cancelledFiles: 0,
archiveCount: 1,
partCount: 16,
outputCount: 10,
failurePhase: "remux",
archiveOperations: [{
id: "archive-1",
name: "show.part01.rar",
itemIds: ["item-1"],
partCount: 16,
startedAt: todayStart + 65_000,
completedAt: todayStart + 77_000,
durationMs: 12_000,
status: "completed",
errorCategory: ""
}],
remuxOperations: [{
id: "remux-1",
fileName: "episode.mkv",
startedAt: todayStart + 77_000,
completedAt: todayStart + 85_000,
durationMs: 8_000,
status: "failed",
errorCategory: "ffmpeg"
}]
});
const html = renderToStaticMarkup(
<HistoryView
actions={createActions()}
model={buildHistoryViewModel([structured], "all", "", [], [structured.id], false, "", now)}
/>
);
for (const label of [
"Download gestartet",
"Download beendet",
"Nachbearbeitung gestartet",
"Abgeschlossen",
"Downloaddauer",
"Entpackdauer",
"Remuxdauer",
"Nachbearbeitungsdauer",
"Gesamtdauer",
"Erfolgreich / Fehlgeschlagen / Abgebrochen",
"Archive / Parts / Ausgaben",
"Fehlerphase",
"Archivvorgänge",
"Remuxvorgänge"
]) {
expect(html).toContain(label);
}
expect(html).toContain("show.part01.rar");
expect(html).toContain("16 Parts");
expect(html).toContain("episode.mkv");
expect(html).toContain("ffmpeg");
expect(html).not.toContain("Downloaddauer (Altbestand)");
});
it("labels durationSeconds honestly for legacy entries", () => {
const legacy = entry({ id: "legacy", name: "Altbestand" });
const html = renderToStaticMarkup(
<HistoryView
actions={createActions()}
model={buildHistoryViewModel([legacy], "all", "", [], [legacy.id], false, "", now)}
/>
);
expect(html).toContain("Downloaddauer (Altbestand)");
expect(html).not.toContain("Download beendet");
expect(html).not.toContain("Nachbearbeitung gestartet");
});
it("uses the global animation setting for the history disclosure surface", () => {
const animated = buildHistoryViewModel(entries.slice(0, 1), "all", "", [], ["today"], false, "", now, true);
const immediate = buildHistoryViewModel(entries.slice(0, 1), "all", "", [], ["today"], false, "", now, false);