diff --git a/src/main/package-telemetry.ts b/src/main/package-telemetry.ts index 7fa3110..b970207 100644 --- a/src/main/package-telemetry.ts +++ b/src/main/package-telemetry.ts @@ -58,8 +58,8 @@ function getFailure( return { failurePhase: "extract", errorCategory: failedArchive.errorCategory }; } const downloadError = downloadErrors.find(Boolean); - if (downloadError) { - return { failurePhase: "download", errorCategory: downloadError }; + if (downloadErrors.length > 0) { + return { failurePhase: "download", errorCategory: downloadError || "download" }; } return { failurePhase: null, errorCategory: "" }; } @@ -86,7 +86,7 @@ export function finalizePackageResult(telemetry: PackageTelemetry): PackageResul const cancelledFiles = cancelledDownloads + postProcessCancellations; const successfulFiles = Math.max(0, completedDownloads - postProcessFailures - postProcessCancellations); const startedAt = finiteNonNegative(packageEntry.downloadStartedAt); - const downloadEndedAt = finiteNonNegative(packageEntry.downloadEndedAt ?? packageEntry.downloadCompletedAt); + const downloadEndedAt = finiteNonNegative(packageEntry.downloadEndedAt) || finiteNonNegative(packageEntry.downloadCompletedAt); const postProcessStartedAt = finiteNonNegative(packageEntry.postProcessStartedAt); const postProcessCompletedAt = finiteNonNegative(packageEntry.postProcessCompletedAt); const completedAt = finiteNonNegative(packageEntry.terminalAt); diff --git a/tests/package-telemetry.test.ts b/tests/package-telemetry.test.ts index fcff01b..7b817e5 100644 --- a/tests/package-telemetry.test.ts +++ b/tests/package-telemetry.test.ts @@ -11,6 +11,7 @@ import { durationSecondsBetween, finalizePackageResult } from "../src/main/package-telemetry"; +import { normalizeLoadedSession } from "../src/main/storage"; function packageEntry(overrides: Partial = {}): PackageEntry { return { @@ -173,6 +174,21 @@ describe("package lifecycle telemetry", () => { })); }); + it("classifies a failed download without error text as a download-phase failure", () => { + const item = { ...downloadItem("item-1", "failed"), lastError: "", fullStatus: "" }; + const result = finalizePackageResult(telemetry({ + package: packageEntry({ status: "failed", itemIds: [item.id] }), + items: [item] + })); + + expect(result).toEqual(expect.objectContaining({ + status: "failed", + failedFiles: 1, + failurePhase: "download", + errorCategory: "download" + })); + }); + it("classifies a package with only cancelled work as cancelled", () => { const item = downloadItem("item-1", "cancelled"); const result = finalizePackageResult(telemetry({ @@ -211,6 +227,43 @@ describe("package lifecycle telemetry", () => { })); }); + it("retains legacy download timing after session normalization", () => { + const item = { ...downloadItem("item-1"), downloadedBytes: 240_000, totalBytes: 240_000 }; + const legacyPackage = packageEntry({ + itemIds: [item.id], + downloadStartedAt: 1_000, + downloadCompletedAt: 121_000, + terminalAt: 121_000, + updatedAt: 121_000 + }); + delete legacyPackage.downloadEndedAt; + const normalized = normalizeLoadedSession({ + version: 2, + packageOrder: [legacyPackage.id], + packages: { [legacyPackage.id]: legacyPackage }, + items: { [item.id]: item }, + runStartedAt: 1_000, + totalDownloadedBytes: 240_000, + summaryText: "", + reconnectUntil: 0, + reconnectReason: "", + paused: false, + running: false, + updatedAt: 121_000 + }); + const normalizedPackage = normalized.packages[legacyPackage.id]; + const result = finalizePackageResult({ + package: normalizedPackage, + items: normalizedPackage.itemIds.map((itemId) => normalized.items[itemId]) + }); + + expect(result).toEqual(expect.objectContaining({ + downloadEndedAt: 121_000, + downloadDurationSeconds: 120, + averageDownloadSpeedBps: 2_000 + })); + }); + it("uses cleanup, remux, extract, and download as deterministic failure precedence", () => { const failedItem = downloadItem("item-1", "failed"); const failedArchive = archiveOperation({ status: "failed", errorCategory: "archive-error" });