From 1dce6b2eb84626319188273b61bf87da749361af Mon Sep 17 00:00:00 2001 From: Antti Lustila Date: Wed, 15 Feb 2023 09:35:56 +0200 Subject: [PATCH] Add unit tests and update injectables Signed-off-by: Antti Lustila --- .../configurable-directories.test.ts | 109 ++++++++++++++++++ ...shed-directory-for-extension.injectable.ts | 43 +++++++ ...ile-system-provisioner-store.injectable.ts | 10 +- .../file-system-provisioner-store.ts | 23 +--- .../get-hash.injectable.ts | 15 +++ 5 files changed, 173 insertions(+), 27 deletions(-) create mode 100644 packages/core/src/extensions/__tests__/configurable-directories.test.ts create mode 100644 packages/core/src/extensions/extension-loader/file-system-provisioner-store/ensure-hashed-directory-for-extension.injectable.ts create mode 100644 packages/core/src/extensions/extension-loader/file-system-provisioner-store/get-hash.injectable.ts diff --git a/packages/core/src/extensions/__tests__/configurable-directories.test.ts b/packages/core/src/extensions/__tests__/configurable-directories.test.ts new file mode 100644 index 0000000000..7c7b3ba887 --- /dev/null +++ b/packages/core/src/extensions/__tests__/configurable-directories.test.ts @@ -0,0 +1,109 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { runInAction } from "mobx"; +import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import type { FakeExtensionOptions } from "../../renderer/components/test-utils/get-extension-fake"; +import logErrorInjectable from "../../common/log-error.injectable"; +import getHashInjectable from "../../extensions/extension-loader/file-system-provisioner-store/get-hash.injectable"; +import fsInjectable from "../../common/fs/fs.injectable"; + +describe("configurable directories for extension files", () => { + let builder: ApplicationBuilder; + let logErrorMock: jest.Mock; + + beforeEach(async () => { + builder = getApplicationBuilder(); + + builder.beforeApplicationStart( + (mainDi) => { + runInAction(() => { + mainDi.override(getHashInjectable, () => x => x); + }); + + logErrorMock = jest.fn(); + + mainDi.override(logErrorInjectable, () => logErrorMock); + }, + ); + + await builder.startHidden(); + + const window = builder.applicationWindow.create("some-window-id"); + + await window.start(); + + }); + + describe("when extension with a specific store name is enabled", () => { + let testExtensionOptions: FakeExtensionOptions; + + beforeEach(() => { + + testExtensionOptions = { + id: "some-extension", + name: "some-extension-name", + + mainOptions: { + manifest: { + name: "irrelevant", + storeName: "some-specific-store-name", + version: "0", + engines: { + lens: "0", + }, + }, + }, + }; + + builder.extensions.enable(testExtensionOptions); + }); + + it("creates extension directory for specific store name", async () => { + const fs = builder.mainDi.inject(fsInjectable); + + await builder.extensions.get("some-extension").main.getExtensionFileFolder(); + + const nonHashedExtensionDirectories = await fs.readdir("/some-directory-for-app-data/some-product-name/extension_data"); + + expect(nonHashedExtensionDirectories).toContain("some-specific-store-name"); + }); + }); + + describe("when extension with no specific store name is enabled", () => { + let testExtensionOptions: FakeExtensionOptions; + + beforeEach(() => { + + testExtensionOptions = { + id: "some-extension", + name: "some-extension-name", + + mainOptions: { + manifest: { + name: "some-package-name", + storeName: undefined, + version: "0", + engines: { + lens: "0", + }, + }, + }, + }; + + builder.extensions.enable(testExtensionOptions); + }); + + it("creates extension directory for package name", async () => { + const fs = builder.mainDi.inject(fsInjectable); + + await builder.extensions.get("some-extension").main.getExtensionFileFolder(); + + const nonHashedExtensionDirectories = await fs.readdir("/some-directory-for-app-data/some-product-name/extension_data"); + + expect(nonHashedExtensionDirectories).toContain("some-package-name"); + }); + }); +}); diff --git a/packages/core/src/extensions/extension-loader/file-system-provisioner-store/ensure-hashed-directory-for-extension.injectable.ts b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/ensure-hashed-directory-for-extension.injectable.ts new file mode 100644 index 0000000000..6b696f663d --- /dev/null +++ b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/ensure-hashed-directory-for-extension.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 type { ObservableMap } from "mobx"; +import { getInjectable } from "@ogre-tools/injectable"; + +import { getOrInsertWithAsync } from "../../../common/utils"; +import randomBytesInjectable from "../../../common/utils/random-bytes.injectable"; +import joinPathsInjectable from "../../../common/path/join-paths.injectable"; +import directoryForExtensionDataInjectable from "./directory-for-extension-data.injectable"; +import ensureDirInjectable from "../../../common/fs/ensure-dir.injectable"; +import getHashInjectable from "./get-hash.injectable"; + +export type EnsureHashedDirectoryForExtension = (extensionName: string, registeredExtensions: ObservableMap) => Promise; + +const ensureHashedDirectoryForExtensionInjectable = getInjectable({ + id: "ensure-hashed-directory-for-extension", + + instantiate: (di): EnsureHashedDirectoryForExtension => { + const randomBytes = di.inject(randomBytesInjectable); + const joinPaths = di.inject(joinPathsInjectable); + const directoryForExtensionData = di.inject(directoryForExtensionDataInjectable); + const ensureDirectory = di.inject(ensureDirInjectable); + const getHash = di.inject(getHashInjectable); + + return async (extensionName, registeredExtensions) => { + const dirPath = await getOrInsertWithAsync(registeredExtensions, extensionName, async () => { + const salt = (await randomBytes(32)).toString("hex"); + const hashedName = getHash(`${extensionName}/${salt}`); + + return joinPaths(directoryForExtensionData, hashedName); + }); + + await ensureDirectory(dirPath); + + return dirPath; + }; + }, +}); + +export default ensureHashedDirectoryForExtensionInjectable; diff --git a/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable.ts b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable.ts index b511437da9..7a9d3c4aa1 100644 --- a/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable.ts +++ b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable.ts @@ -4,10 +4,6 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { FileSystemProvisionerStore } from "./file-system-provisioner-store"; -import directoryForExtensionDataInjectable from "./directory-for-extension-data.injectable"; -import ensureDirectoryInjectable from "../../../common/fs/ensure-dir.injectable"; -import joinPathsInjectable from "../../../common/path/join-paths.injectable"; -import randomBytesInjectable from "../../../common/utils/random-bytes.injectable"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable"; import loggerInjectable from "../../../common/logger.injectable"; @@ -17,15 +13,12 @@ import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../../../ import { persistStateToConfigInjectionToken } from "../../../common/base-store/save-to-file"; import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable"; import { enlistMessageChannelListenerInjectionToken } from "../../../common/utils/channel/enlist-message-channel-listener-injection-token"; +import ensureHashedDirectoryForExtensionInjectable from "./ensure-hashed-directory-for-extension.injectable"; const fileSystemProvisionerStoreInjectable = getInjectable({ id: "file-system-provisioner-store", instantiate: (di) => new FileSystemProvisionerStore({ - directoryForExtensionData: di.inject(directoryForExtensionDataInjectable), - ensureDirectory: di.inject(ensureDirectoryInjectable), - joinPaths: di.inject(joinPathsInjectable), - randomBytes: di.inject(randomBytesInjectable), directoryForUserData: di.inject(directoryForUserDataInjectable), getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable), logger: di.inject(loggerInjectable), @@ -36,6 +29,7 @@ const fileSystemProvisionerStoreInjectable = getInjectable({ persistStateToConfig: di.inject(persistStateToConfigInjectionToken), enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken), shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken), + ensureHashedDirectoryForExtension: di.inject(ensureHashedDirectoryForExtensionInjectable), }), }); diff --git a/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.ts b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.ts index 5655a0cf06..7c4fb87361 100644 --- a/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.ts +++ b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.ts @@ -3,25 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { SHA256 } from "crypto-js"; import { action, makeObservable, observable } from "mobx"; import type { BaseStoreDependencies } from "../../../common/base-store/base-store"; import { BaseStore } from "../../../common/base-store/base-store"; import type { LensExtensionId } from "../../lens-extension"; -import { getOrInsertWithAsync, toJS } from "../../../common/utils"; -import type { EnsureDirectory } from "../../../common/fs/ensure-dir.injectable"; -import type { JoinPaths } from "../../../common/path/join-paths.injectable"; -import type { RandomBytes } from "../../../common/utils/random-bytes.injectable"; +import { toJS } from "../../../common/utils"; +import type { EnsureHashedDirectoryForExtension } from "./ensure-hashed-directory-for-extension.injectable"; interface FSProvisionModel { extensions: Record; // extension names to paths } interface Dependencies extends BaseStoreDependencies { - readonly directoryForExtensionData: string; - ensureDirectory: EnsureDirectory; - joinPaths: JoinPaths; - randomBytes: RandomBytes; + ensureHashedDirectoryForExtension: EnsureHashedDirectoryForExtension; } export class FileSystemProvisionerStore extends BaseStore { @@ -43,16 +37,7 @@ export class FileSystemProvisionerStore extends BaseStore { * @returns path to the folder that the extension can safely write files to. */ async requestDirectory(extensionName: string): Promise { - const dirPath = await getOrInsertWithAsync(this.registeredExtensions, extensionName, async () => { - const salt = (await this.dependencies.randomBytes(32)).toString("hex"); - const hashedName = SHA256(`${extensionName}/${salt}`).toString(); - - return this.dependencies.joinPaths(this.dependencies.directoryForExtensionData, hashedName); - }); - - await this.dependencies.ensureDirectory(dirPath); - - return dirPath; + return this.dependencies.ensureHashedDirectoryForExtension(extensionName, this.registeredExtensions); } @action diff --git a/packages/core/src/extensions/extension-loader/file-system-provisioner-store/get-hash.injectable.ts b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/get-hash.injectable.ts new file mode 100644 index 0000000000..5d7faa5884 --- /dev/null +++ b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/get-hash.injectable.ts @@ -0,0 +1,15 @@ +/** + * 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 { SHA256 } from "crypto-js"; + +const getHashInjectable = getInjectable({ + id: "get-hash", + + instantiate: () => (text: string) => SHA256(text).toString(), +}); + +export default getHashInjectable;