From 4b81869b6e4751140059eb35dd6324a10c2868a4 Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 19 May 2026 00:54:36 +0800 Subject: [PATCH] fix(account): keep download record pending through retry --- src/App.vue | 3 +- .../unit/App.download-records.test.ts | 85 ++++++++++++++++++- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/src/App.vue b/src/App.vue index 1bb3eb49..b070ea5a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1265,13 +1265,14 @@ const handleInstallCompleteForDownloadRecord = async ( const pendingRecord = pendingDownloadRecords.get(result.id); if (!pendingRecord) return; - pendingDownloadRecords.delete(result.id); if (!result.success || !isLoggedIn.value) return; try { await recordDownloadedApp(pendingRecord); } catch (error: unknown) { logger.warn({ err: error }, "记录下载应用失败"); + } finally { + pendingDownloadRecords.delete(result.id); } }; diff --git a/src/__tests__/unit/App.download-records.test.ts b/src/__tests__/unit/App.download-records.test.ts index 7611fe63..f101275c 100644 --- a/src/__tests__/unit/App.download-records.test.ts +++ b/src/__tests__/unit/App.download-records.test.ts @@ -1,9 +1,16 @@ -import { fireEvent, render, screen, waitFor } from "@testing-library/vue"; -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { + cleanup, + fireEvent, + render, + screen, + waitFor, +} from "@testing-library/vue"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import App from "@/App.vue"; import { recordDownloadedApp } from "@/modules/backendApi"; import { setAuthSession } from "@/global/authState"; +import { downloads } from "@/global/downloadStatus"; import type { DownloadResult } from "@/global/typedefinition"; const invoke = vi.fn(); @@ -88,9 +95,14 @@ vi.mock("@/modules/backendApi", () => ({ })); describe("App download records", () => { + afterEach(() => { + cleanup(); + }); + beforeEach(() => { vi.clearAllMocks(); ipcHandlers.clear(); + downloads.value = []; invoke.mockImplementation(async (channel: string) => { if (channel === "get-store-filter") return "apm"; if (channel === "check-spark-available") return false; @@ -188,4 +200,73 @@ describe("App download records", () => { ); }); }); + + it("keeps a pending download record through a failed install retry", async () => { + render(App); + + await fireEvent.click( + await screen.findByRole("button", { name: "全部应用 1" }), + ); + await fireEvent.click(await screen.findByText("WPS")); + await fireEvent.click(await screen.findByRole("button", { name: "安装" })); + + await waitFor(() => { + expect(send).toHaveBeenCalledWith( + "queue-install", + expect.stringContaining('"pkgname":"wps"'), + ); + }); + + const queuedPayload = vi + .mocked(send) + .mock.calls.find( + ([channel]) => channel === "queue-install", + )?.[1] as string; + const queuedDownload = JSON.parse(queuedPayload) as { id: number }; + const failedCompletion: DownloadResult = { + id: queuedDownload.id, + time: Date.now(), + message: "failed", + success: false, + exitCode: 1, + status: "failed", + origin: "apm", + }; + + ipcHandlers.get("install-complete")?.({}, failedCompletion); + downloads.value[0].status = "failed"; + + await waitFor(() => { + expect(recordDownloadedApp).not.toHaveBeenCalled(); + expect(screen.getByTitle("重试")).toBeInTheDocument(); + }); + + await fireEvent.click(screen.getByTitle("重试")); + + const successfulCompletion: DownloadResult = { + id: queuedDownload.id, + time: Date.now(), + message: "installed", + success: true, + exitCode: 0, + status: "completed", + origin: "apm", + }; + + ipcHandlers.get("install-complete")?.({}, successfulCompletion); + + await waitFor(() => { + expect(recordDownloadedApp).toHaveBeenCalledTimes(1); + expect(recordDownloadedApp).toHaveBeenCalledWith( + expect.objectContaining({ + appKey: "app:office:wps", + pkgname: "wps", + name: "WPS", + category: "office", + selectedOrigin: "apm", + version: "1.0.0", + }), + ); + }); + }); });