diff --git a/src/behaviours/update-app/installing-update-using-tray.test.ts b/src/behaviours/update-app/installing-update-using-tray.test.ts index e0de507abc..8e2c0a8ea1 100644 --- a/src/behaviours/update-app/installing-update-using-tray.test.ts +++ b/src/behaviours/update-app/installing-update-using-tray.test.ts @@ -14,7 +14,7 @@ import type { AsyncFnMock } from "@async-fn/jest"; import asyncFn from "@async-fn/jest"; import type { UpdateChannel, UpdateChannelId } from "../../main/update-app/update-channels"; import { updateChannels } from "../../main/update-app/update-channels"; -import downloadPlatformUpdateInjectable from "../../main/update-app/download-platform-update.injectable"; +import downloadPlatformUpdateInjectable from "../../main/update-app/download-platform-update/download-platform-update.injectable"; import selectedUpdateChannelInjectable from "../../main/update-app/selected-update-channel.injectable"; import progressOfUpdateDownloadInjectable from "../../main/update-app/progress-of-update-download.injectable"; import type { IComputedValue } from "mobx"; diff --git a/src/main/update-app/download-platform-update.injectable.ts b/src/main/update-app/download-platform-update.injectable.ts deleted file mode 100644 index bdffb524b7..0000000000 --- a/src/main/update-app/download-platform-update.injectable.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; - -const downloadPlatformUpdateInjectable = getInjectable({ - id: "download-platform-update", - - instantiate: () => { - return async () => {}; - }, -}); - -export default downloadPlatformUpdateInjectable; diff --git a/src/main/update-app/download-platform-update/download-platform-update.injectable.ts b/src/main/update-app/download-platform-update/download-platform-update.injectable.ts new file mode 100644 index 0000000000..cf4f5574bb --- /dev/null +++ b/src/main/update-app/download-platform-update/download-platform-update.injectable.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { ProgressInfo } from "electron-updater"; +import electronUpdaterInjectable from "../../electron-app/features/electron-updater.injectable"; +import progressOfUpdateDownloadInjectable from "../progress-of-update-download.injectable"; +import loggerInjectable from "../../../common/logger.injectable"; + +const downloadPlatformUpdateInjectable = getInjectable({ + id: "download-platform-update", + + instantiate: (di) => { + const electronUpdater = di.inject(electronUpdaterInjectable); + const progressOfUpdateDownload = di.inject(progressOfUpdateDownloadInjectable); + const logger = di.inject(loggerInjectable); + + const updateDownloadProgress = ({ percent }: ProgressInfo) => { + progressOfUpdateDownload.setValue(percent); + }; + + return async () => { + progressOfUpdateDownload.setValue(0); + + electronUpdater.on("download-progress", updateDownloadProgress); + + try { + await electronUpdater.downloadUpdate(); + } catch(error) { + logger.error("[UPDATE-APP/DOWNLOAD]", error); + + return { downloadWasSuccessful: false }; + } finally { + electronUpdater.off("download-progress", updateDownloadProgress); + } + + return { downloadWasSuccessful: true }; + }; + }, +}); + +export default downloadPlatformUpdateInjectable; diff --git a/src/main/update-app/download-platform-update/download-platform-update.test.ts b/src/main/update-app/download-platform-update/download-platform-update.test.ts new file mode 100644 index 0000000000..ef4d34e40c --- /dev/null +++ b/src/main/update-app/download-platform-update/download-platform-update.test.ts @@ -0,0 +1,150 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import electronUpdaterInjectable from "../../electron-app/features/electron-updater.injectable"; +import downloadPlatformUpdateInjectable from "./download-platform-update.injectable"; +import type { AppUpdater } from "electron-updater"; +import type { AsyncFnMock } from "@async-fn/jest"; +import asyncFn from "@async-fn/jest"; +import { getPromiseStatus } from "../../../common/test-utils/get-promise-status"; +import progressOfUpdateDownloadInjectable from "../progress-of-update-download.injectable"; +import type { DiContainer } from "@ogre-tools/injectable"; +import loggerInjectable from "../../../common/logger.injectable"; +import type { Logger } from "../../../common/logger"; + +describe("download-platform-update", () => { + let downloadPlatformUpdate: () => Promise<{ downloadWasSuccessful: boolean }>; + let downloadUpdateMock: AsyncFnMock<() => void>; + let electronUpdaterFake: AppUpdater; + let electronUpdaterOnMock: jest.Mock; + let electronUpdaterOffMock: jest.Mock; + let di: DiContainer; + let progressOfUpdateDownload: { value: { get: () => number }}; + let logErrorMock: jest.Mock; + + beforeEach(() => { + di = getDiForUnitTesting(); + + downloadUpdateMock = asyncFn(); + electronUpdaterOnMock = jest.fn(); + electronUpdaterOffMock = jest.fn(); + + electronUpdaterFake = { + channel: undefined, + autoDownload: undefined, + + on: electronUpdaterOnMock, + off: electronUpdaterOffMock, + + downloadUpdate: downloadUpdateMock, + } as unknown as AppUpdater; + + di.override(electronUpdaterInjectable, () => electronUpdaterFake); + + logErrorMock = jest.fn(); + di.override(loggerInjectable, () => ({ error: logErrorMock }) as unknown as Logger); + + downloadPlatformUpdate = di.inject(downloadPlatformUpdateInjectable); + progressOfUpdateDownload = di.inject(progressOfUpdateDownloadInjectable); + }); + + describe("when called", () => { + let actualPromise: Promise<{ downloadWasSuccessful: boolean }>; + + beforeEach(() => { + actualPromise = downloadPlatformUpdate(); + }); + + it("calls for downloading of update", () => { + expect(downloadUpdateMock).toHaveBeenCalled(); + }); + + it("does not resolve yet", async () => { + const promiseStatus = await getPromiseStatus(actualPromise); + + expect(promiseStatus.fulfilled).toBe(false); + }); + + it("starts progress of download from 0", () => { + expect(progressOfUpdateDownload.value.get()).toBe(0); + }); + + describe("when downloading progresses", () => { + beforeEach(() => { + const [, callback] = electronUpdaterOnMock.mock.calls.find( + ([event]) => event === "download-progress", + ); + + callback({ + percent: 42, + total: 0, + delta: 0, + transferred: 0, + bytesPerSecond: 0, + }); + }); + + it("updates progress of the download", () => { + expect(progressOfUpdateDownload.value.get()).toBe(42); + }); + + describe("when downloading resolves", () => { + beforeEach(async () => { + await downloadUpdateMock.resolve(); + }); + + it("resolves with success", async () => { + const actual = await actualPromise; + + expect(actual).toEqual({ downloadWasSuccessful: true }); + }); + + it("does not reset progress of download yet", () => { + expect(progressOfUpdateDownload.value.get()).toBe(42); + }); + + it("stops watching for download progress", () => { + expect(electronUpdaterOffMock).toHaveBeenCalledWith( + "download-progress", + expect.any(Function), + ); + }); + + it("when starting download again, resets progress of download", () => { + downloadPlatformUpdate(); + + expect(progressOfUpdateDownload.value.get()).toBe(0); + }); + }); + + describe("when downloading rejects", () => { + let errorStub: Error; + + beforeEach(() => { + errorStub = new Error("Some error"); + + downloadUpdateMock.reject(errorStub); + }); + + it("logs error", () => { + expect(logErrorMock).toHaveBeenCalledWith("[UPDATE-APP/DOWNLOAD]", errorStub); + }); + + it("stops watching for download progress", () => { + expect(electronUpdaterOffMock).toHaveBeenCalledWith( + "download-progress", + expect.any(Function), + ); + }); + + it("resolves with failure", async () => { + const actual = await actualPromise; + + expect(actual).toEqual({ downloadWasSuccessful: false }); + }); + }); + }); + }); +}); diff --git a/src/main/update-app/version-update.injectable.ts b/src/main/update-app/version-update.injectable.ts index 4589696edc..50f7a0a45f 100644 --- a/src/main/update-app/version-update.injectable.ts +++ b/src/main/update-app/version-update.injectable.ts @@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { IComputedValue, IObservableValue } from "mobx"; import { computed, observable, runInAction } from "mobx"; import selectedUpdateChannelInjectable from "./selected-update-channel.injectable"; -import downloadPlatformUpdateInjectable from "./download-platform-update.injectable"; +import downloadPlatformUpdateInjectable from "./download-platform-update/download-platform-update.injectable"; import type { CheckForPlatformUpdates } from "./check-for-platform-updates/check-for-platform-updates.injectable"; import checkForPlatformUpdatesInjectable from "./check-for-platform-updates/check-for-platform-updates.injectable"; import type { UpdateChannel } from "./update-channels";