mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Replace jest.mock's with overriding dependencies (#6014)
Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
parent
03d2389f01
commit
81f7bd3c2c
@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import { action, comparer, computed, makeObservable, observable, reaction, when } from "mobx";
|
||||
import { broadcastMessage } from "../ipc";
|
||||
import type { ClusterContextHandler } from "../../main/context-handler/context-handler";
|
||||
import type { KubeConfig } from "@kubernetes/client-node";
|
||||
import { HttpError } from "@kubernetes/client-node";
|
||||
@ -25,6 +24,7 @@ import type { CanI } from "./authorization-review.injectable";
|
||||
import type { ListNamespaces } from "./list-namespaces.injectable";
|
||||
import assert from "assert";
|
||||
import type { Logger } from "../logger";
|
||||
import type { BroadcastMessage } from "../ipc/broadcast-message.injectable";
|
||||
|
||||
export interface ClusterDependencies {
|
||||
readonly directoryForKubeConfigs: string;
|
||||
@ -36,6 +36,7 @@ export interface ClusterDependencies {
|
||||
createAuthorizationReview: (config: KubeConfig) => CanI;
|
||||
createListNamespaces: (config: KubeConfig) => ListNamespaces;
|
||||
createVersionDetector: (cluster: Cluster) => VersionDetector;
|
||||
broadcastMessage: BroadcastMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -602,7 +603,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
||||
*/
|
||||
pushState(state = this.getState()) {
|
||||
this.dependencies.logger.silly(`[CLUSTER]: push-state`, state);
|
||||
broadcastMessage("cluster:state", this.id, state);
|
||||
this.dependencies.broadcastMessage("cluster:state", this.id, state);
|
||||
}
|
||||
|
||||
// get cluster system meta, e.g. use in "logger"
|
||||
@ -625,7 +626,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
||||
const update: KubeAuthUpdate = { message, isError };
|
||||
|
||||
this.dependencies.logger.debug(`[CLUSTER]: broadcasting connection update`, { ...update, meta: this.getMeta() });
|
||||
broadcastMessage(`cluster:${this.id}:connection-update`, update);
|
||||
this.dependencies.broadcastMessage(`cluster:${this.id}:connection-update`, update);
|
||||
}
|
||||
|
||||
protected async getAllowedNamespaces(proxyConfig: KubeConfig) {
|
||||
@ -645,7 +646,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
||||
const { response } = error as HttpError & { response: Response };
|
||||
|
||||
this.dependencies.logger.info("[CLUSTER]: listing namespaces is forbidden, broadcasting", { clusterId: this.id, error: response.body });
|
||||
broadcastMessage(clusterListNamespaceForbiddenChannel, this.id);
|
||||
this.dependencies.broadcastMessage(clusterListNamespaceForbiddenChannel, this.id);
|
||||
}
|
||||
|
||||
return namespaceList;
|
||||
|
||||
10
src/common/fs/stat/stat.global-override-for-injectable.ts
Normal file
10
src/common/fs/stat/stat.global-override-for-injectable.ts
Normal file
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import statInjectable from "./stat.injectable";
|
||||
import { getGlobalOverride } from "../../test-utils/get-global-override";
|
||||
|
||||
export default getGlobalOverride(statInjectable, () => () => {
|
||||
throw new Error("Tried to call stat without explicit override");
|
||||
});
|
||||
14
src/common/fs/stat/stat.injectable.ts
Normal file
14
src/common/fs/stat/stat.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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 fsInjectable from "../fs.injectable";
|
||||
|
||||
const statInjectable = getInjectable({
|
||||
id: "stat",
|
||||
|
||||
instantiate: (di) => di.inject(fsInjectable).stat,
|
||||
});
|
||||
|
||||
export default statInjectable;
|
||||
76
src/common/fs/validate-directory.injectable.ts
Normal file
76
src/common/fs/validate-directory.injectable.ts
Normal file
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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 { AsyncResult } from "../utils/async-result";
|
||||
import { isErrnoException } from "../utils";
|
||||
import type { Stats } from "fs-extra";
|
||||
import { lowerFirst } from "lodash/fp";
|
||||
import statInjectable from "./stat/stat.injectable";
|
||||
|
||||
export type ValidateDirectory = (path: string) => Promise<AsyncResult<undefined>>;
|
||||
|
||||
function getUserReadableFileType(stats: Stats): string {
|
||||
if (stats.isFile()) {
|
||||
return "a file";
|
||||
}
|
||||
|
||||
if (stats.isFIFO()) {
|
||||
return "a pipe";
|
||||
}
|
||||
|
||||
if (stats.isSocket()) {
|
||||
return "a socket";
|
||||
}
|
||||
|
||||
if (stats.isBlockDevice()) {
|
||||
return "a block device";
|
||||
}
|
||||
|
||||
if (stats.isCharacterDevice()) {
|
||||
return "a character device";
|
||||
}
|
||||
|
||||
return "an unknown file type";
|
||||
}
|
||||
|
||||
const validateDirectoryInjectable = getInjectable({
|
||||
id: "validate-directory",
|
||||
|
||||
instantiate: (di): ValidateDirectory => {
|
||||
const stat = di.inject(statInjectable);
|
||||
|
||||
return async (path) => {
|
||||
try {
|
||||
const stats = await stat(path);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return { callWasSuccessful: true, response: undefined };
|
||||
}
|
||||
|
||||
return { callWasSuccessful: false, error: `the provided path is ${getUserReadableFileType(stats)} and not a directory.` };
|
||||
} catch (error) {
|
||||
if (!isErrnoException(error)) {
|
||||
return { callWasSuccessful: false, error: "of an unknown error, please try again." };
|
||||
}
|
||||
|
||||
const humanReadableErrors: Record<string, string> = {
|
||||
ENOENT: "the provided path does not exist.",
|
||||
EACCES: "search permissions is denied for one of the directories in the prefix of the provided path.",
|
||||
ELOOP: "the provided path is a sym-link which points to a chain of sym-links that is too long to resolve. Perhaps it is cyclic.",
|
||||
ENAMETOOLONG: "the pathname is too long to be used.",
|
||||
ENOTDIR: "a prefix of the provided path is not a directory.",
|
||||
};
|
||||
|
||||
const humanReadableError = error.code
|
||||
? humanReadableErrors[error.code]
|
||||
: lowerFirst(String(error));
|
||||
|
||||
return { callWasSuccessful: false, error: humanReadableError };
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default validateDirectoryInjectable;
|
||||
10
src/common/fs/watch/watch.global-override-for-injectable.ts
Normal file
10
src/common/fs/watch/watch.global-override-for-injectable.ts
Normal file
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getGlobalOverride } from "../../test-utils/get-global-override";
|
||||
import watchInjectable from "./watch.injectable";
|
||||
|
||||
export default getGlobalOverride(watchInjectable, () => () => {
|
||||
throw new Error("Tried to call file system watch without explicit override");
|
||||
});
|
||||
18
src/common/fs/watch/watch.injectable.ts
Normal file
18
src/common/fs/watch/watch.injectable.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 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 { FSWatcher, WatchOptions } from "chokidar";
|
||||
import { watch } from "chokidar";
|
||||
|
||||
export type Watch = (path: string, options?: WatchOptions) => FSWatcher;
|
||||
|
||||
// TODO: Introduce wrapper to allow simpler API
|
||||
const watchInjectable = getInjectable({
|
||||
id: "watch",
|
||||
instantiate: (): Watch => watch,
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default watchInjectable;
|
||||
@ -5,9 +5,11 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { broadcastMessage } from "./ipc";
|
||||
|
||||
export type BroadcastMessage = (channel: string, ...args: any[]) => Promise<void>;
|
||||
|
||||
const broadcastMessageInjectable = getInjectable({
|
||||
id: "broadcast-message",
|
||||
instantiate: () => broadcastMessage,
|
||||
instantiate: (): BroadcastMessage => broadcastMessage,
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
|
||||
@ -13,10 +13,17 @@ import logger from "../../main/logger";
|
||||
import type { ClusterFrameInfo } from "../cluster-frames";
|
||||
import { clusterFrameMap } from "../cluster-frames";
|
||||
import type { Disposer } from "../utils";
|
||||
import ipcMainInjectable from "../../main/utils/channel/ipc-main/ipc-main.injectable";
|
||||
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
import ipcRendererInjectable from "../../renderer/utils/channel/ipc-renderer.injectable";
|
||||
|
||||
export const broadcastMainChannel = "ipc:broadcast-main";
|
||||
|
||||
export function ipcMainHandle(channel: string, listener: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any) {
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
|
||||
const ipcMain = di.inject(ipcMainInjectable);
|
||||
|
||||
ipcMain.handle(channel, async (event, ...args) => {
|
||||
return sanitizePayload(await listener(event, ...args));
|
||||
});
|
||||
@ -78,12 +85,20 @@ export async function broadcastMessage(channel: string, ...args: any[]): Promise
|
||||
}
|
||||
|
||||
export function ipcMainOn(channel: string, listener: (event: Electron.IpcMainEvent, ...args: any[]) => any): Disposer {
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
|
||||
const ipcMain = di.inject(ipcMainInjectable);
|
||||
|
||||
ipcMain.on(channel, listener);
|
||||
|
||||
return () => ipcMain.off(channel, listener);
|
||||
}
|
||||
|
||||
export function ipcRendererOn(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => any): Disposer {
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
|
||||
const ipcRenderer = di.inject(ipcRendererInjectable);
|
||||
|
||||
ipcRenderer.on(channel, listener);
|
||||
|
||||
return () => ipcRenderer.off(channel, listener);
|
||||
|
||||
@ -8,7 +8,6 @@ import { matchPath } from "react-router";
|
||||
import { countBy } from "lodash";
|
||||
import { isDefined, iter } from "../utils";
|
||||
import { pathToRegexp } from "path-to-regexp";
|
||||
import logger from "../../main/logger";
|
||||
import type Url from "url-parse";
|
||||
import { RoutingError, RoutingErrorType } from "./error";
|
||||
import type { ExtensionsStore } from "../../extensions/extensions-store/extensions-store";
|
||||
@ -17,6 +16,7 @@ import type { LensExtension } from "../../extensions/lens-extension";
|
||||
import type { RouteHandler, RouteParams } from "../../extensions/registries/protocol-handler";
|
||||
import { when } from "mobx";
|
||||
import { ipcRenderer } from "electron";
|
||||
import type { Logger } from "../logger";
|
||||
|
||||
// IPC channel for protocol actions. Main broadcasts the open-url events to this channel.
|
||||
export const ProtocolHandlerIpcPrefix = "protocol-handler";
|
||||
@ -66,6 +66,7 @@ export function foldAttemptResults(mainAttempt: RouteAttempt, rendererAttempt: R
|
||||
export interface LensProtocolRouterDependencies {
|
||||
readonly extensionLoader: ExtensionLoader;
|
||||
readonly extensionsStore: ExtensionsStore;
|
||||
readonly logger: Logger;
|
||||
}
|
||||
|
||||
export abstract class LensProtocolRouter {
|
||||
@ -130,7 +131,7 @@ export abstract class LensProtocolRouter {
|
||||
data.extensionName = extensionName;
|
||||
}
|
||||
|
||||
logger.info(`${LensProtocolRouter.LoggingPrefix}: No handler found`, data);
|
||||
this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: No handler found`, data);
|
||||
|
||||
return RouteAttempt.MISSING;
|
||||
}
|
||||
@ -183,7 +184,7 @@ export abstract class LensProtocolRouter {
|
||||
timeout: 5_000,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.info(
|
||||
this.dependencies.logger.info(
|
||||
`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not installed (${error})`,
|
||||
);
|
||||
|
||||
@ -193,18 +194,18 @@ export abstract class LensProtocolRouter {
|
||||
const extension = extensionLoader.getInstanceByName(name);
|
||||
|
||||
if (!extension) {
|
||||
logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but does not have a class for ${ipcRenderer ? "renderer" : "main"}`);
|
||||
this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but does not have a class for ${ipcRenderer ? "renderer" : "main"}`);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
if (!this.dependencies.extensionsStore.isEnabled(extension)) {
|
||||
logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not enabled`);
|
||||
this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not enabled`);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched`);
|
||||
this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched`);
|
||||
|
||||
return extension;
|
||||
}
|
||||
@ -250,7 +251,7 @@ export abstract class LensProtocolRouter {
|
||||
*/
|
||||
public addInternalHandler(urlSchema: string, handler: RouteHandler): this {
|
||||
pathToRegexp(urlSchema); // verify now that the schema is valid
|
||||
logger.info(`${LensProtocolRouter.LoggingPrefix}: internal registering ${urlSchema}`);
|
||||
this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: internal registering ${urlSchema}`);
|
||||
this.internalRoutes.set(urlSchema, handler);
|
||||
|
||||
return this;
|
||||
|
||||
@ -10,8 +10,10 @@ import extensionLoaderInjectable from "../extension-loader/extension-loader.inje
|
||||
import { runInAction } from "mobx";
|
||||
import updateExtensionsStateInjectable from "../extension-loader/update-extensions-state/update-extensions-state.injectable";
|
||||
import mockFs from "mock-fs";
|
||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||
import { delay } from "../../renderer/utils";
|
||||
import { getDiForUnitTesting } from "../../renderer/getDiForUnitTesting";
|
||||
import ipcRendererInjectable from "../../renderer/utils/channel/ipc-renderer.injectable";
|
||||
import type { IpcRenderer } from "electron";
|
||||
|
||||
console = new Console(stdout, stderr);
|
||||
|
||||
@ -19,10 +21,14 @@ const manifestPath = "manifest/path";
|
||||
const manifestPath2 = "manifest/path2";
|
||||
const manifestPath3 = "manifest/path3";
|
||||
|
||||
jest.mock(
|
||||
"electron",
|
||||
() => ({
|
||||
ipcRenderer: {
|
||||
describe("ExtensionLoader", () => {
|
||||
let extensionLoader: ExtensionLoader;
|
||||
let updateExtensionStateMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
di.override(ipcRendererInjectable, () => ({
|
||||
invoke: jest.fn(async (channel: string) => {
|
||||
if (channel === "extension-loader:main:state") {
|
||||
return [
|
||||
@ -59,59 +65,46 @@ jest.mock(
|
||||
|
||||
return [];
|
||||
}),
|
||||
on: jest.fn(
|
||||
(channel: string, listener: (event: any, ...args: any[]) => void) => {
|
||||
if (channel === "extension-loader:main:state") {
|
||||
// First initialize with extensions 1 and 2
|
||||
// and then broadcast event to remove extension 2 and add extension number 3
|
||||
setTimeout(() => {
|
||||
listener({}, [
|
||||
[
|
||||
|
||||
on: (channel: string, listener: (event: any, ...args: any[]) => void) => {
|
||||
if (channel === "extension-loader:main:state") {
|
||||
// First initialize with extensions 1 and 2
|
||||
// and then broadcast event to remove extension 2 and add extension number 3
|
||||
setTimeout(() => {
|
||||
listener({}, [
|
||||
[
|
||||
manifestPath,
|
||||
{
|
||||
manifest: {
|
||||
name: "TestExtension",
|
||||
version: "1.0.0",
|
||||
},
|
||||
id: manifestPath,
|
||||
absolutePath: "/test/1",
|
||||
manifestPath,
|
||||
{
|
||||
manifest: {
|
||||
name: "TestExtension",
|
||||
version: "1.0.0",
|
||||
},
|
||||
id: manifestPath,
|
||||
absolutePath: "/test/1",
|
||||
manifestPath,
|
||||
isBundled: false,
|
||||
isEnabled: true,
|
||||
isBundled: false,
|
||||
isEnabled: true,
|
||||
},
|
||||
],
|
||||
[
|
||||
manifestPath3,
|
||||
{
|
||||
manifest: {
|
||||
name: "TestExtension3",
|
||||
version: "3.0.0",
|
||||
},
|
||||
],
|
||||
[
|
||||
manifestPath3,
|
||||
{
|
||||
manifest: {
|
||||
name: "TestExtension3",
|
||||
version: "3.0.0",
|
||||
},
|
||||
id: manifestPath3,
|
||||
absolutePath: "/test/3",
|
||||
manifestPath: manifestPath3,
|
||||
isBundled: false,
|
||||
isEnabled: true,
|
||||
},
|
||||
],
|
||||
]);
|
||||
}, 10);
|
||||
}
|
||||
},
|
||||
),
|
||||
},
|
||||
}),
|
||||
{
|
||||
virtual: true,
|
||||
},
|
||||
);
|
||||
|
||||
describe("ExtensionLoader", () => {
|
||||
let extensionLoader: ExtensionLoader;
|
||||
let updateExtensionStateMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
id: manifestPath3,
|
||||
absolutePath: "/test/3",
|
||||
manifestPath: manifestPath3,
|
||||
isBundled: false,
|
||||
isEnabled: true,
|
||||
},
|
||||
],
|
||||
]);
|
||||
}, 10);
|
||||
}
|
||||
},
|
||||
}) as unknown as IpcRenderer);
|
||||
|
||||
mockFs();
|
||||
|
||||
|
||||
@ -13,6 +13,10 @@ import installExtensionInjectable from "../extension-installer/install-extension
|
||||
import extensionPackageRootDirectoryInjectable from "../extension-installer/extension-package-root-directory/extension-package-root-directory.injectable";
|
||||
import installExtensionsInjectable from "../extension-installer/install-extensions/install-extensions.injectable";
|
||||
import staticFilesDirectoryInjectable from "../../common/vars/static-files-directory.injectable";
|
||||
import readJsonFileInjectable from "../../common/fs/read-json-file.injectable";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import pathExistsInjectable from "../../common/fs/path-exists.injectable";
|
||||
import watchInjectable from "../../common/fs/watch/watch.injectable";
|
||||
|
||||
const extensionDiscoveryInjectable = getInjectable({
|
||||
id: "extension-discovery",
|
||||
@ -40,6 +44,10 @@ const extensionDiscoveryInjectable = getInjectable({
|
||||
),
|
||||
|
||||
staticFilesDirectory: di.inject(staticFilesDirectoryInjectable),
|
||||
readJsonFile: di.inject(readJsonFileInjectable),
|
||||
pathExists: di.inject(pathExistsInjectable),
|
||||
watch: di.inject(watchInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@ -4,53 +4,29 @@
|
||||
*/
|
||||
|
||||
import type { FSWatcher } from "chokidar";
|
||||
import { watch } from "chokidar";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { Console } from "console";
|
||||
import * as fse from "fs-extra";
|
||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||
import extensionDiscoveryInjectable from "../extension-discovery/extension-discovery.injectable";
|
||||
import type { ExtensionDiscovery } from "../extension-discovery/extension-discovery";
|
||||
import installExtensionInjectable
|
||||
from "../extension-installer/install-extension/install-extension.injectable";
|
||||
import directoryForUserDataInjectable
|
||||
from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import installExtensionInjectable from "../extension-installer/install-extension/install-extension.injectable";
|
||||
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import mockFs from "mock-fs";
|
||||
import { delay } from "../../renderer/utils";
|
||||
import { observable, when } from "mobx";
|
||||
import appVersionInjectable from "../../common/vars/app-version.injectable";
|
||||
|
||||
jest.setTimeout(60_000);
|
||||
|
||||
jest.mock("../../common/ipc");
|
||||
jest.mock("chokidar", () => ({
|
||||
watch: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("fs-extra");
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
getVersion: () => "99.99.99",
|
||||
getName: () => "lens",
|
||||
setName: jest.fn(),
|
||||
setPath: jest.fn(),
|
||||
getPath: () => "tmp",
|
||||
getLocale: () => "en",
|
||||
setLoginItemSettings: jest.fn(),
|
||||
},
|
||||
ipcMain: {
|
||||
on: jest.fn(),
|
||||
handle: jest.fn(),
|
||||
},
|
||||
}));
|
||||
import readJsonFileInjectable from "../../common/fs/read-json-file.injectable";
|
||||
import pathExistsInjectable from "../../common/fs/path-exists.injectable";
|
||||
import watchInjectable from "../../common/fs/watch/watch.injectable";
|
||||
|
||||
console = new Console(process.stdout, process.stderr); // fix mockFS
|
||||
const mockedWatch = watch as jest.MockedFunction<typeof watch>;
|
||||
const mockedFse = fse as jest.Mocked<typeof fse>;
|
||||
|
||||
describe("ExtensionDiscovery", () => {
|
||||
let extensionDiscovery: ExtensionDiscovery;
|
||||
let readJsonFileMock: jest.Mock;
|
||||
let pathExistsMock: jest.Mock;
|
||||
let watchMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
@ -59,6 +35,15 @@ describe("ExtensionDiscovery", () => {
|
||||
di.override(installExtensionInjectable, () => () => Promise.resolve());
|
||||
di.override(appVersionInjectable, () => "5.0.0");
|
||||
|
||||
readJsonFileMock = jest.fn();
|
||||
di.override(readJsonFileInjectable, () => readJsonFileMock);
|
||||
|
||||
pathExistsMock = jest.fn(() => Promise.resolve(true));
|
||||
di.override(pathExistsInjectable, () => pathExistsMock);
|
||||
|
||||
watchMock = jest.fn();
|
||||
di.override(watchInjectable, () => watchMock);
|
||||
|
||||
mockFs();
|
||||
|
||||
extensionDiscovery = di.inject(extensionDiscoveryInjectable);
|
||||
@ -72,7 +57,7 @@ describe("ExtensionDiscovery", () => {
|
||||
const letTestFinish = observable.box(false);
|
||||
let addHandler!: (filePath: string) => void;
|
||||
|
||||
mockedFse.readJson.mockImplementation((p) => {
|
||||
readJsonFileMock.mockImplementation((p) => {
|
||||
expect(p).toBe(path.join(os.homedir(), ".k8slens/extensions/my-extension/package.json"));
|
||||
|
||||
return {
|
||||
@ -84,8 +69,6 @@ describe("ExtensionDiscovery", () => {
|
||||
};
|
||||
});
|
||||
|
||||
mockedFse.pathExists.mockImplementation(() => true);
|
||||
|
||||
const mockWatchInstance = {
|
||||
on: jest.fn((event: string, handler: typeof addHandler) => {
|
||||
if (event === "add") {
|
||||
@ -96,7 +79,7 @@ describe("ExtensionDiscovery", () => {
|
||||
}),
|
||||
} as unknown as FSWatcher;
|
||||
|
||||
mockedWatch.mockImplementationOnce(() => mockWatchInstance);
|
||||
watchMock.mockImplementationOnce(() => mockWatchInstance);
|
||||
|
||||
// Need to force isLoaded to be true so that the file watching is started
|
||||
extensionDiscovery.isLoaded = true;
|
||||
@ -139,7 +122,7 @@ describe("ExtensionDiscovery", () => {
|
||||
}),
|
||||
} as unknown as FSWatcher;
|
||||
|
||||
mockedWatch.mockImplementationOnce(() => mockWatchInstance);
|
||||
watchMock.mockImplementationOnce(() => mockWatchInstance);
|
||||
|
||||
// Need to force isLoaded to be true so that the file watching is started
|
||||
extensionDiscovery.isLoaded = true;
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { watch } from "chokidar";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { EventEmitter } from "events";
|
||||
import fse from "fs-extra";
|
||||
@ -12,7 +11,6 @@ import os from "os";
|
||||
import path from "path";
|
||||
import { broadcastMessage, ipcMainHandle, ipcRendererOn } from "../../common/ipc";
|
||||
import { isErrnoException, toJS } from "../../common/utils";
|
||||
import logger from "../../main/logger";
|
||||
import type { ExtensionsStore } from "../extensions-store/extensions-store";
|
||||
import type { ExtensionLoader } from "../extension-loader";
|
||||
import type { LensExtensionId, LensExtensionManifest } from "../lens-extension";
|
||||
@ -21,6 +19,10 @@ import type { ExtensionInstallationStateStore } from "../extension-installation-
|
||||
import type { PackageJson } from "type-fest";
|
||||
import { extensionDiscoveryStateChannel } from "../../common/ipc/extension-handling";
|
||||
import { requestInitialExtensionDiscovery } from "../../renderer/ipc";
|
||||
import type { ReadJson } from "../../common/fs/read-json-file.injectable";
|
||||
import type { Logger } from "../../common/logger";
|
||||
import type { PathExists } from "../../common/fs/path-exists.injectable";
|
||||
import type { Watch } from "../../common/fs/watch/watch.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
extensionLoader: ExtensionLoader;
|
||||
@ -35,6 +37,10 @@ interface Dependencies {
|
||||
installExtensions: (packageJsonPath: string, packagesJson: PackageJson) => Promise<void>;
|
||||
extensionPackageRootDirectory: string;
|
||||
staticFilesDirectory: string;
|
||||
readJsonFile: ReadJson;
|
||||
pathExists: PathExists;
|
||||
watch: Watch;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export interface InstalledExtension {
|
||||
@ -155,13 +161,12 @@ export class ExtensionDiscovery {
|
||||
* Dependencies are installed automatically after an extension folder is copied.
|
||||
*/
|
||||
async watchExtensions(): Promise<void> {
|
||||
logger.info(`${logModule} watching extension add/remove in ${this.localFolderPath}`);
|
||||
this.dependencies.logger.info(`${logModule} watching extension add/remove in ${this.localFolderPath}`);
|
||||
|
||||
// Wait until .load() has been called and has been resolved
|
||||
await this.whenLoaded;
|
||||
|
||||
// chokidar works better than fs.watch
|
||||
watch(this.localFolderPath, {
|
||||
this.dependencies.watch(this.localFolderPath, {
|
||||
// For adding and removing symlinks to work, the depth has to be 1.
|
||||
depth: 1,
|
||||
ignoreInitial: true,
|
||||
@ -206,11 +211,11 @@ export class ExtensionDiscovery {
|
||||
await this.dependencies.installExtension(extension.absolutePath);
|
||||
|
||||
this.extensions.set(extension.id, extension);
|
||||
logger.info(`${logModule} Added extension ${extension.manifest.name}`);
|
||||
this.dependencies.logger.info(`${logModule} Added extension ${extension.manifest.name}`);
|
||||
this.events.emit("add", extension);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`${logModule}: failed to add extension: ${error}`, { error });
|
||||
this.dependencies.logger.error(`${logModule}: failed to add extension: ${error}`, { error });
|
||||
} finally {
|
||||
this.dependencies.extensionInstallationStateStore.clearInstallingFromMain(manifestPath);
|
||||
}
|
||||
@ -247,13 +252,13 @@ export class ExtensionDiscovery {
|
||||
const lensExtensionId = extension.manifestPath;
|
||||
|
||||
this.extensions.delete(extension.id);
|
||||
logger.info(`${logModule} removed extension ${extensionName}`);
|
||||
this.dependencies.logger.info(`${logModule} removed extension ${extensionName}`);
|
||||
this.events.emit("remove", lensExtensionId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
logger.warn(`${logModule} extension ${extensionFolderName} not found, can't remove`);
|
||||
this.dependencies.logger.warn(`${logModule} extension ${extensionFolderName} not found, can't remove`);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -275,12 +280,12 @@ export class ExtensionDiscovery {
|
||||
const extension = this.extensions.get(extensionId) ?? this.dependencies.extensionLoader.getExtension(extensionId);
|
||||
|
||||
if (!extension) {
|
||||
return void logger.warn(`${logModule} could not uninstall extension, not found`, { id: extensionId });
|
||||
return void this.dependencies.logger.warn(`${logModule} could not uninstall extension, not found`, { id: extensionId });
|
||||
}
|
||||
|
||||
const { manifest, absolutePath } = extension;
|
||||
|
||||
logger.info(`${logModule} Uninstalling ${manifest.name}`);
|
||||
this.dependencies.logger.info(`${logModule} Uninstalling ${manifest.name}`);
|
||||
|
||||
await this.removeSymlinkByPackageName(manifest.name);
|
||||
|
||||
@ -296,7 +301,7 @@ export class ExtensionDiscovery {
|
||||
|
||||
this.loadStarted = true;
|
||||
|
||||
logger.info(
|
||||
this.dependencies.logger.info(
|
||||
`${logModule} loading extensions from ${this.dependencies.extensionPackageRootDirectory}`,
|
||||
);
|
||||
|
||||
@ -358,12 +363,12 @@ export class ExtensionDiscovery {
|
||||
*/
|
||||
protected async getByManifest(manifestPath: string, { isBundled = false } = {}): Promise<InstalledExtension | null> {
|
||||
try {
|
||||
const manifest = await fse.readJson(manifestPath) as LensExtensionManifest;
|
||||
const manifest = await this.dependencies.readJsonFile(manifestPath) as unknown as LensExtensionManifest;
|
||||
const id = this.getInstalledManifestPath(manifest.name);
|
||||
const isEnabled = this.dependencies.extensionsStore.isEnabled({ id, isBundled });
|
||||
const extensionDir = path.dirname(manifestPath);
|
||||
const npmPackage = path.join(extensionDir, `${manifest.name}-${manifest.version}.tgz`);
|
||||
const absolutePath = (isProduction && await fse.pathExists(npmPackage)) ? npmPackage : extensionDir;
|
||||
const absolutePath = (isProduction && await this.dependencies.pathExists(npmPackage)) ? npmPackage : extensionDir;
|
||||
const isCompatible = (isBundled && this.dependencies.isCompatibleBundledExtension(manifest)) || this.dependencies.isCompatibleExtension(manifest);
|
||||
|
||||
return {
|
||||
@ -378,9 +383,9 @@ export class ExtensionDiscovery {
|
||||
} catch (error) {
|
||||
if (isErrnoException(error) && error.code === "ENOTDIR") {
|
||||
// ignore this error, probably from .DS_Store file
|
||||
logger.debug(`${logModule}: failed to load extension manifest through a not-dir-like at ${manifestPath}`);
|
||||
this.dependencies.logger.debug(`${logModule}: failed to load extension manifest through a not-dir-like at ${manifestPath}`);
|
||||
} else {
|
||||
logger.error(`${logModule}: can't load extension manifest at ${manifestPath}: ${error}`);
|
||||
this.dependencies.logger.error(`${logModule}: can't load extension manifest at ${manifestPath}: ${error}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -395,7 +400,7 @@ export class ExtensionDiscovery {
|
||||
const userExtensions = await this.loadFromFolder(this.localFolderPath, bundledExtensions.map((extension) => extension.manifest.name));
|
||||
|
||||
for (const extension of userExtensions) {
|
||||
if ((await fse.pathExists(extension.manifestPath)) === false) {
|
||||
if (!(await this.dependencies.pathExists(extension.manifestPath))) {
|
||||
try {
|
||||
await this.dependencies.installExtension(extension.absolutePath);
|
||||
} catch (error) {
|
||||
@ -404,7 +409,7 @@ export class ExtensionDiscovery {
|
||||
: String(error || "unknown error");
|
||||
const { name, version } = extension.manifest;
|
||||
|
||||
logger.error(`${logModule}: failed to install user extension ${name}@${version}: ${message}`);
|
||||
this.dependencies.logger.error(`${logModule}: failed to install user extension ${name}@${version}: ${message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -438,7 +443,7 @@ export class ExtensionDiscovery {
|
||||
extensions.push(extension);
|
||||
}
|
||||
}
|
||||
logger.debug(`${logModule}: ${extensions.length} extensions loaded`, { folderPath, extensions });
|
||||
this.dependencies.logger.debug(`${logModule}: ${extensions.length} extensions loaded`, { folderPath, extensions });
|
||||
|
||||
return extensions;
|
||||
}
|
||||
@ -473,7 +478,7 @@ export class ExtensionDiscovery {
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`${logModule}: ${extensions.length} extensions loaded`, { folderPath, extensions });
|
||||
this.dependencies.logger.debug(`${logModule}: ${extensions.length} extensions loaded`, { folderPath, extensions });
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { ipcRenderer } from "electron";
|
||||
import { ipcMain, ipcRenderer } from "electron";
|
||||
import { isEqual } from "lodash";
|
||||
import type { ObservableMap } from "mobx";
|
||||
import { action, computed, makeObservable, observable, observe, reaction, when } from "mobx";
|
||||
@ -127,10 +127,10 @@ export class ExtensionLoader {
|
||||
|
||||
@action
|
||||
async init() {
|
||||
if (ipcRenderer) {
|
||||
await this.initRenderer();
|
||||
} else {
|
||||
if (ipcMain) {
|
||||
await this.initMain();
|
||||
} else {
|
||||
await this.initRenderer();
|
||||
}
|
||||
|
||||
await Promise.all([this.whenLoaded]);
|
||||
|
||||
@ -2,11 +2,7 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
jest.mock("../../common/ipc");
|
||||
jest.mock("request");
|
||||
jest.mock("request-promise-native");
|
||||
|
||||
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
|
||||
import { Console } from "console";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import { Kubectl } from "../kubectl/kubectl";
|
||||
@ -41,6 +37,7 @@ describe("create clusters", () => {
|
||||
di.override(kubectlBinaryNameInjectable, () => "kubectl");
|
||||
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
|
||||
di.override(normalizedPlatformInjectable, () => "darwin");
|
||||
di.override(broadcastMessageInjectable, () => async () => {});
|
||||
di.override(authorizationReviewInjectable, () => () => () => Promise.resolve(true));
|
||||
di.override(listNamespacesInjectable, () => () => () => Promise.resolve([ "default" ]));
|
||||
di.override(createContextHandlerInjectable, () => (cluster) => ({
|
||||
|
||||
@ -3,45 +3,13 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
jest.mock("winston", () => ({
|
||||
format: {
|
||||
colorize: jest.fn(),
|
||||
combine: jest.fn(),
|
||||
simple: jest.fn(),
|
||||
label: jest.fn(),
|
||||
timestamp: jest.fn(),
|
||||
printf: jest.fn(),
|
||||
padLevels: jest.fn(),
|
||||
ms: jest.fn(),
|
||||
splat: jest.fn(),
|
||||
},
|
||||
createLogger: jest.fn().mockReturnValue({
|
||||
silly: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
log: jest.fn(),
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
crit: jest.fn(),
|
||||
}),
|
||||
transports: {
|
||||
Console: jest.fn(),
|
||||
File: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock("../../common/ipc");
|
||||
jest.mock("child_process");
|
||||
jest.mock("tcp-port-used");
|
||||
|
||||
import waitUntilPortIsUsedInjectable from "../kube-auth-proxy/wait-until-port-is-used/wait-until-port-is-used.injectable";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import type { KubeAuthProxy } from "../kube-auth-proxy/kube-auth-proxy";
|
||||
import { broadcastMessage } from "../../common/ipc";
|
||||
import type { ChildProcess } from "child_process";
|
||||
import { spawn } from "child_process";
|
||||
import { Kubectl } from "../kubectl/kubectl";
|
||||
import type { DeepMockProxy } from "jest-mock-extended";
|
||||
import { mockDeep, mock } from "jest-mock-extended";
|
||||
import { waitUntilUsed } from "tcp-port-used";
|
||||
import type { Readable } from "stream";
|
||||
import { EventEmitter } from "stream";
|
||||
import { Console } from "console";
|
||||
@ -59,17 +27,18 @@ import directoryForTempInjectable from "../../common/app-paths/directory-for-tem
|
||||
import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable";
|
||||
import kubectlBinaryNameInjectable from "../kubectl/binary-name.injectable";
|
||||
import kubectlDownloadingNormalizedArchInjectable from "../kubectl/normalized-arch.injectable";
|
||||
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
|
||||
|
||||
console = new Console(stdout, stderr);
|
||||
|
||||
const mockBroadcastIpc = broadcastMessage as jest.MockedFunction<typeof broadcastMessage>;
|
||||
const mockSpawn = spawn as jest.MockedFunction<typeof spawn>;
|
||||
const mockWaitUntilUsed = waitUntilUsed as jest.MockedFunction<typeof waitUntilUsed>;
|
||||
const clusterServerUrl = "https://192.168.64.3:8443";
|
||||
|
||||
describe("kube auth proxy tests", () => {
|
||||
let createCluster: CreateCluster;
|
||||
let createKubeAuthProxy: (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy;
|
||||
let spawnMock: jest.Mock;
|
||||
let waitUntilPortIsUsedMock: jest.Mock;
|
||||
let broadcastMessageMock: jest.Mock;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
@ -105,7 +74,15 @@ describe("kube auth proxy tests", () => {
|
||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||
di.override(directoryForTempInjectable, () => "some-directory-for-temp");
|
||||
|
||||
di.override(spawnInjectable, () => mockSpawn);
|
||||
spawnMock = jest.fn();
|
||||
di.override(spawnInjectable, () => spawnMock);
|
||||
|
||||
waitUntilPortIsUsedMock = jest.fn();
|
||||
di.override(waitUntilPortIsUsedInjectable, () => waitUntilPortIsUsedMock);
|
||||
|
||||
broadcastMessageMock = jest.fn();
|
||||
di.override(broadcastMessageInjectable, () => broadcastMessageMock);
|
||||
|
||||
di.override(kubectlBinaryNameInjectable, () => "kubectl");
|
||||
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
|
||||
di.override(normalizedPlatformInjectable, () => "darwin");
|
||||
@ -211,12 +188,12 @@ describe("kube auth proxy tests", () => {
|
||||
|
||||
return stdout;
|
||||
});
|
||||
mockSpawn.mockImplementationOnce((command: string): ChildProcess => {
|
||||
spawnMock.mockImplementationOnce((command: string): ChildProcess => {
|
||||
expect(path.basename(command).split(".")[0]).toBe("lens-k8s-proxy");
|
||||
|
||||
return mockedCP;
|
||||
});
|
||||
mockWaitUntilUsed.mockReturnValueOnce(Promise.resolve());
|
||||
waitUntilPortIsUsedMock.mockReturnValueOnce(Promise.resolve());
|
||||
|
||||
const cluster = createCluster({
|
||||
id: "foobar",
|
||||
@ -233,34 +210,34 @@ describe("kube auth proxy tests", () => {
|
||||
await proxy.run();
|
||||
listeners.emit("error", { message: "foobarbat" });
|
||||
|
||||
expect(mockBroadcastIpc).toBeCalledWith("cluster:foobar:connection-update", { message: "foobarbat", isError: true });
|
||||
expect(broadcastMessageMock).toBeCalledWith("cluster:foobar:connection-update", { message: "foobarbat", isError: true });
|
||||
});
|
||||
|
||||
it("should call spawn and broadcast exit", async () => {
|
||||
await proxy.run();
|
||||
listeners.emit("exit", 0);
|
||||
|
||||
expect(mockBroadcastIpc).toBeCalledWith("cluster:foobar:connection-update", { message: "proxy exited with code: 0", isError: false });
|
||||
expect(broadcastMessageMock).toBeCalledWith("cluster:foobar:connection-update", { message: "proxy exited with code: 0", isError: false });
|
||||
});
|
||||
|
||||
it("should call spawn and broadcast errors from stderr", async () => {
|
||||
await proxy.run();
|
||||
listeners.emit("stderr/data", "an error");
|
||||
|
||||
expect(mockBroadcastIpc).toBeCalledWith("cluster:foobar:connection-update", { message: "an error", isError: true });
|
||||
expect(broadcastMessageMock).toBeCalledWith("cluster:foobar:connection-update", { message: "an error", isError: true });
|
||||
});
|
||||
|
||||
it("should call spawn and broadcast stdout serving info", async () => {
|
||||
await proxy.run();
|
||||
|
||||
expect(mockBroadcastIpc).toBeCalledWith("cluster:foobar:connection-update", { message: "Authentication proxy started", isError: false });
|
||||
expect(broadcastMessageMock).toBeCalledWith("cluster:foobar:connection-update", { message: "Authentication proxy started", isError: false });
|
||||
});
|
||||
|
||||
it("should call spawn and broadcast stdout other info", async () => {
|
||||
await proxy.run();
|
||||
listeners.emit("stdout/data", "some info");
|
||||
|
||||
expect(mockBroadcastIpc).toBeCalledWith("cluster:foobar:connection-update", { message: "some info", isError: false });
|
||||
expect(broadcastMessageMock).toBeCalledWith("cluster:foobar:connection-update", { message: "some info", isError: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -15,6 +15,7 @@ import listNamespacesInjectable from "../../common/cluster/list-namespaces.injec
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import detectorRegistryInjectable from "../cluster-detectors/detector-registry.injectable";
|
||||
import createVersionDetectorInjectable from "../cluster-detectors/create-version-detector.injectable";
|
||||
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
|
||||
|
||||
const createClusterInjectable = getInjectable({
|
||||
id: "create-cluster",
|
||||
@ -30,6 +31,7 @@ const createClusterInjectable = getInjectable({
|
||||
logger: di.inject(loggerInjectable),
|
||||
detectorRegistry: di.inject(detectorRegistryInjectable),
|
||||
createVersionDetector: di.inject(createVersionDetectorInjectable),
|
||||
broadcastMessage: di.inject(broadcastMessageInjectable),
|
||||
};
|
||||
|
||||
return (model, configData) => new Cluster(dependencies, model, configData);
|
||||
|
||||
@ -58,7 +58,6 @@ import type { ClusterFrameInfo } from "../common/cluster-frames";
|
||||
import { observable } from "mobx";
|
||||
import waitForElectronToBeReadyInjectable from "./electron-app/features/wait-for-electron-to-be-ready.injectable";
|
||||
import setupListenerForCurrentClusterFrameInjectable from "./start-main-application/lens-window/current-cluster-frame/setup-listener-for-current-cluster-frame.injectable";
|
||||
import ipcMainInjectable from "./utils/channel/ipc-main/ipc-main.injectable";
|
||||
import setupRunnablesAfterWindowIsOpenedInjectable from "./electron-app/runnables/setup-runnables-after-window-is-opened.injectable";
|
||||
import broadcastMessageInjectable from "../common/ipc/broadcast-message.injectable";
|
||||
import getElectronThemeInjectable from "./electron-app/features/get-electron-theme.injectable";
|
||||
@ -252,7 +251,6 @@ const overrideElectronFeatures = (di: DiContainer) => {
|
||||
di.override(shouldStartHiddenInjectable, () => false);
|
||||
di.override(showMessagePopupInjectable, () => () => {});
|
||||
di.override(waitForElectronToBeReadyInjectable, () => () => Promise.resolve());
|
||||
di.override(ipcMainInjectable, () => ({}));
|
||||
di.override(getElectronThemeInjectable, () => () => "dark");
|
||||
di.override(syncThemeFromOperatingSystemInjectable, () => ({ start: () => {}, stop: () => {} }));
|
||||
di.override(electronQuitAndInstallUpdateInjectable, () => () => {});
|
||||
|
||||
@ -1,153 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { sortCharts } from "../../../common/utils";
|
||||
import type { HelmRepo } from "../../../common/helm/helm-repo";
|
||||
|
||||
const charts = new Map([
|
||||
["stable", {
|
||||
"invalid-semver": sortCharts([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "weird-versioning",
|
||||
version: "I am not semver",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "weird-versioning",
|
||||
version: "v4.3.0",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "weird-versioning",
|
||||
version: "I am not semver but more",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "weird-versioning",
|
||||
version: "v4.4.0",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
"apm-server": sortCharts([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "apm-server",
|
||||
version: "2.1.7",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "apm-server",
|
||||
version: "2.1.6",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
"redis": sortCharts([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "apm-server",
|
||||
version: "1.0.0",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "apm-server",
|
||||
version: "0.0.9",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
}],
|
||||
["experiment", {
|
||||
"fairwind": sortCharts([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "fairwind",
|
||||
version: "0.0.1",
|
||||
repo: "experiment",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "fairwind",
|
||||
version: "0.0.2",
|
||||
repo: "experiment",
|
||||
digest: "test",
|
||||
deprecated: true,
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
}],
|
||||
["bitnami", {
|
||||
"hotdog": sortCharts([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "hotdog",
|
||||
version: "1.0.1",
|
||||
repo: "bitnami",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "hotdog",
|
||||
version: "1.0.2",
|
||||
repo: "bitnami",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
"pretzel": sortCharts([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "pretzel",
|
||||
version: "1.0",
|
||||
repo: "bitnami",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "pretzel",
|
||||
version: "1.0.1",
|
||||
repo: "bitnami",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
}],
|
||||
]);
|
||||
|
||||
export class HelmChartManager {
|
||||
constructor(private repo: HelmRepo){ }
|
||||
|
||||
static forRepo(repo: HelmRepo) {
|
||||
return new this(repo);
|
||||
}
|
||||
|
||||
public async charts(): Promise<any> {
|
||||
return charts.get(this.repo.name) ?? {};
|
||||
}
|
||||
}
|
||||
@ -8,8 +8,8 @@ import listHelmChartsInjectable from "../helm-service/list-helm-charts.injectabl
|
||||
import getActiveHelmRepositoriesInjectable from "../repositories/get-active-helm-repositories/get-active-helm-repositories.injectable";
|
||||
import type { AsyncResult } from "../../../common/utils/async-result";
|
||||
import type { HelmRepo } from "../../../common/helm/helm-repo";
|
||||
|
||||
jest.mock("../helm-chart-manager");
|
||||
import { sortCharts } from "../../../common/utils";
|
||||
import helmChartManagerInjectable from "../helm-chart-manager.injectable";
|
||||
|
||||
describe("Helm Service tests", () => {
|
||||
let listHelmCharts: () => Promise<any>;
|
||||
@ -20,6 +20,11 @@ describe("Helm Service tests", () => {
|
||||
|
||||
getActiveHelmRepositoriesMock = jest.fn();
|
||||
|
||||
di.override(
|
||||
helmChartManagerInjectable,
|
||||
(di, repo) => new HelmChartManagerFake(repo) as unknown,
|
||||
);
|
||||
|
||||
di.override(getActiveHelmRepositoriesInjectable, () => getActiveHelmRepositoriesMock);
|
||||
|
||||
di.unoverride(listHelmChartsInjectable);
|
||||
@ -195,3 +200,145 @@ describe("Helm Service tests", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const charts = new Map([
|
||||
["stable", {
|
||||
"invalid-semver": sortCharts([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "weird-versioning",
|
||||
version: "I am not semver",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "weird-versioning",
|
||||
version: "v4.3.0",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "weird-versioning",
|
||||
version: "I am not semver but more",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "weird-versioning",
|
||||
version: "v4.4.0",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
"apm-server": sortCharts([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "apm-server",
|
||||
version: "2.1.7",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "apm-server",
|
||||
version: "2.1.6",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
"redis": sortCharts([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "apm-server",
|
||||
version: "1.0.0",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "apm-server",
|
||||
version: "0.0.9",
|
||||
repo: "stable",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
}],
|
||||
["experiment", {
|
||||
"fairwind": sortCharts([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "fairwind",
|
||||
version: "0.0.1",
|
||||
repo: "experiment",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "fairwind",
|
||||
version: "0.0.2",
|
||||
repo: "experiment",
|
||||
digest: "test",
|
||||
deprecated: true,
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
}],
|
||||
["bitnami", {
|
||||
"hotdog": sortCharts([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "hotdog",
|
||||
version: "1.0.1",
|
||||
repo: "bitnami",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "hotdog",
|
||||
version: "1.0.2",
|
||||
repo: "bitnami",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
"pretzel": sortCharts([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "pretzel",
|
||||
version: "1.0",
|
||||
repo: "bitnami",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "pretzel",
|
||||
version: "1.0.1",
|
||||
repo: "bitnami",
|
||||
digest: "test",
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
}],
|
||||
]);
|
||||
|
||||
class HelmChartManagerFake {
|
||||
constructor(private repo: HelmRepo){ }
|
||||
|
||||
public async charts(): Promise<any> {
|
||||
return charts.get(this.repo.name) ?? {};
|
||||
}
|
||||
}
|
||||
|
||||
19
src/main/helm/helm-chart-manager-cache.injectable.ts
Normal file
19
src/main/helm/helm-chart-manager-cache.injectable.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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";
|
||||
|
||||
export interface ChartCacheEntry {
|
||||
data: string; // serialized JSON
|
||||
mtimeMs: number;
|
||||
}
|
||||
|
||||
export type HelmChartManagerCache = Map<string, ChartCacheEntry>;
|
||||
|
||||
const helmChartManagerCacheInjectable = getInjectable({
|
||||
id: "helm-chart-manager-cache",
|
||||
instantiate: (): HelmChartManagerCache => new Map(),
|
||||
});
|
||||
|
||||
export default helmChartManagerCacheInjectable;
|
||||
26
src/main/helm/helm-chart-manager.injectable.ts
Normal file
26
src/main/helm/helm-chart-manager.injectable.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import type { HelmRepo } from "../../common/helm/helm-repo";
|
||||
import { HelmChartManager } from "./helm-chart-manager";
|
||||
import helmChartManagerCacheInjectable from "./helm-chart-manager-cache.injectable";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
|
||||
const helmChartManagerInjectable = getInjectable({
|
||||
id: "helm-chart-manager",
|
||||
|
||||
instantiate: (di, repo: HelmRepo) => {
|
||||
const cache = di.inject(helmChartManagerCacheInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return new HelmChartManager(repo, { cache, logger });
|
||||
},
|
||||
|
||||
lifecycle: lifecycleEnum.keyedSingleton({
|
||||
getInstanceKey: (di, repo: HelmRepo) => repo.name,
|
||||
}),
|
||||
});
|
||||
|
||||
export default helmChartManagerInjectable;
|
||||
@ -5,39 +5,34 @@
|
||||
|
||||
import fs from "fs";
|
||||
import * as yaml from "js-yaml";
|
||||
import logger from "../logger";
|
||||
import type { RepoHelmChartList } from "../../common/k8s-api/endpoints/helm-charts.api";
|
||||
import { iter, put, sortCharts } from "../../common/utils";
|
||||
import { execHelm } from "./exec";
|
||||
import type { SetRequired } from "type-fest";
|
||||
import { assert } from "console";
|
||||
import type { HelmRepo } from "../../common/helm/helm-repo";
|
||||
|
||||
interface ChartCacheEntry {
|
||||
data: string; // serialized JSON
|
||||
mtimeMs: number;
|
||||
}
|
||||
import type { HelmChartManagerCache } from "./helm-chart-manager-cache.injectable";
|
||||
import type { Logger } from "../../common/logger";
|
||||
|
||||
export interface HelmCacheFile {
|
||||
apiVersion: string;
|
||||
entries: RepoHelmChartList;
|
||||
}
|
||||
|
||||
export class HelmChartManager {
|
||||
static readonly #cache = new Map<string, ChartCacheEntry>();
|
||||
interface Dependencies {
|
||||
cache: HelmChartManagerCache;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export class HelmChartManager {
|
||||
protected readonly repo: SetRequired<HelmRepo, "cacheFilePath">;
|
||||
|
||||
private constructor(repo: HelmRepo) {
|
||||
constructor(repo: HelmRepo, private dependencies: Dependencies) {
|
||||
assert(repo.cacheFilePath, "CacheFilePath must be provided on the helm repo");
|
||||
|
||||
this.repo = repo as SetRequired<HelmRepo, "cacheFilePath">;
|
||||
}
|
||||
|
||||
static forRepo(repo: HelmRepo) {
|
||||
return new this(repo);
|
||||
}
|
||||
|
||||
public async chartVersions(name: string) {
|
||||
const charts = await this.charts();
|
||||
|
||||
@ -48,7 +43,7 @@ export class HelmChartManager {
|
||||
try {
|
||||
return await this.cachedYaml();
|
||||
} catch(error) {
|
||||
logger.error("HELM-CHART-MANAGER]: failed to list charts", { error });
|
||||
this.dependencies.logger.error("HELM-CHART-MANAGER]: failed to list charts", { error });
|
||||
|
||||
return {};
|
||||
}
|
||||
@ -84,7 +79,7 @@ export class HelmChartManager {
|
||||
const normalized = normalizeHelmCharts(this.repo.name, data.entries);
|
||||
|
||||
return put(
|
||||
HelmChartManager.#cache,
|
||||
this.dependencies.cache,
|
||||
this.repo.name,
|
||||
{
|
||||
data: JSON.stringify(normalized),
|
||||
@ -94,7 +89,7 @@ export class HelmChartManager {
|
||||
}
|
||||
|
||||
protected async cachedYaml(): Promise<RepoHelmChartList> {
|
||||
let cacheEntry = HelmChartManager.#cache.get(this.repo.name);
|
||||
let cacheEntry = this.dependencies.cache.get(this.repo.name);
|
||||
|
||||
if (!cacheEntry) {
|
||||
cacheEntry = await this.updateYamlCache();
|
||||
|
||||
@ -3,14 +3,16 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { HelmChartManager } from "../helm-chart-manager";
|
||||
import getActiveHelmRepositoryInjectable from "../repositories/get-active-helm-repository.injectable";
|
||||
import type { HelmRepo } from "../../../common/helm/helm-repo";
|
||||
import helmChartManagerInjectable from "../helm-chart-manager.injectable";
|
||||
|
||||
const getHelmChartValuesInjectable = getInjectable({
|
||||
id: "get-helm-chart-values",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getActiveHelmRepository = di.inject(getActiveHelmRepositoryInjectable);
|
||||
const getChartManager = (repo: HelmRepo) => di.inject(helmChartManagerInjectable, repo);
|
||||
|
||||
return async (repoName: string, chartName: string, version = "") => {
|
||||
const repo = await getActiveHelmRepository(repoName);
|
||||
@ -19,7 +21,7 @@ const getHelmChartValuesInjectable = getInjectable({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return HelmChartManager.forRepo(repo).getValues(chartName, version);
|
||||
return getChartManager(repo).getValues(chartName, version);
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@ -3,14 +3,16 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { HelmChartManager } from "../helm-chart-manager";
|
||||
import getActiveHelmRepositoryInjectable from "../repositories/get-active-helm-repository.injectable";
|
||||
import type { HelmRepo } from "../../../common/helm/helm-repo";
|
||||
import helmChartManagerInjectable from "../helm-chart-manager.injectable";
|
||||
|
||||
const getHelmChartInjectable = getInjectable({
|
||||
id: "get-helm-chart",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getActiveHelmRepository = di.inject(getActiveHelmRepositoryInjectable);
|
||||
const getChartManager = (repo: HelmRepo) => di.inject(helmChartManagerInjectable, repo);
|
||||
|
||||
return async (repoName: string, chartName: string, version = "") => {
|
||||
const repo = await getActiveHelmRepository(repoName);
|
||||
@ -19,7 +21,7 @@ const getHelmChartInjectable = getInjectable({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const chartManager = HelmChartManager.forRepo(repo);
|
||||
const chartManager = getChartManager(repo);
|
||||
|
||||
return {
|
||||
readme: await chartManager.getReadme(chartName, version),
|
||||
|
||||
@ -5,14 +5,16 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import assert from "assert";
|
||||
import { object } from "../../../common/utils";
|
||||
import { HelmChartManager } from "../helm-chart-manager";
|
||||
import getActiveHelmRepositoriesInjectable from "../repositories/get-active-helm-repositories/get-active-helm-repositories.injectable";
|
||||
import type { HelmRepo } from "../../../common/helm/helm-repo";
|
||||
import helmChartManagerInjectable from "../helm-chart-manager.injectable";
|
||||
|
||||
const listHelmChartsInjectable = getInjectable({
|
||||
id: "list-helm-charts",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getActiveHelmRepositories = di.inject(getActiveHelmRepositoriesInjectable);
|
||||
const getChartManager = (repo: HelmRepo) => di.inject(helmChartManagerInjectable, repo);
|
||||
|
||||
return async () => {
|
||||
const result = await getActiveHelmRepositories();
|
||||
@ -27,7 +29,7 @@ const listHelmChartsInjectable = getInjectable({
|
||||
async (repo) =>
|
||||
[
|
||||
repo.name,
|
||||
await HelmChartManager.forRepo(repo).charts(),
|
||||
await getChartManager(repo).charts(),
|
||||
] as const,
|
||||
),
|
||||
),
|
||||
|
||||
@ -13,6 +13,7 @@ import spawnInjectable from "../child-process/spawn.injectable";
|
||||
import { getKubeAuthProxyCertificate } from "./get-kube-auth-proxy-certificate";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import baseBundledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable";
|
||||
import waitUntilPortIsUsedInjectable from "./wait-until-port-is-used/wait-until-port-is-used.injectable";
|
||||
|
||||
export type CreateKubeAuthProxy = (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy;
|
||||
|
||||
@ -29,6 +30,7 @@ const createKubeAuthProxyInjectable = getInjectable({
|
||||
proxyCert: getKubeAuthProxyCertificate(clusterUrl.hostname, selfsigned.generate),
|
||||
spawn: di.inject(spawnInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
waitUntilPortIsUsed: di.inject(waitUntilPortIsUsedInjectable),
|
||||
};
|
||||
|
||||
return new KubeAuthProxy(dependencies, cluster, environmentVariables);
|
||||
|
||||
@ -4,10 +4,8 @@
|
||||
*/
|
||||
|
||||
import type { ChildProcess } from "child_process";
|
||||
import { waitUntilUsed } from "tcp-port-used";
|
||||
import { randomBytes } from "crypto";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import logger from "../logger";
|
||||
import { getPortFrom } from "../utils/get-port";
|
||||
import { makeObservable, observable, when } from "mobx";
|
||||
import type { SelfSignedCert } from "selfsigned";
|
||||
@ -15,6 +13,7 @@ import assert from "assert";
|
||||
import { TypedRegEx } from "typed-regex";
|
||||
import type { Spawn } from "../child-process/spawn.injectable";
|
||||
import type { Logger } from "../../common/logger";
|
||||
import type { WaitUntilPortIsUsed } from "./wait-until-port-is-used/wait-until-port-is-used.injectable";
|
||||
|
||||
const startingServeMatcher = "starting to serve on (?<address>.+)";
|
||||
const startingServeRegex = Object.assign(TypedRegEx(startingServeMatcher, "i"), {
|
||||
@ -24,8 +23,9 @@ const startingServeRegex = Object.assign(TypedRegEx(startingServeMatcher, "i"),
|
||||
export interface KubeAuthProxyDependencies {
|
||||
readonly proxyBinPath: string;
|
||||
readonly proxyCert: SelfSignedCert;
|
||||
spawn: Spawn;
|
||||
readonly spawn: Spawn;
|
||||
readonly logger: Logger;
|
||||
readonly waitUntilPortIsUsed: WaitUntilPortIsUsed;
|
||||
}
|
||||
|
||||
export class KubeAuthProxy {
|
||||
@ -106,13 +106,13 @@ export class KubeAuthProxy {
|
||||
onFind: () => this.cluster.broadcastConnectUpdate("Authentication proxy started"),
|
||||
});
|
||||
|
||||
logger.info(`[KUBE-AUTH-PROXY]: found port=${this._port}`);
|
||||
this.dependencies.logger.info(`[KUBE-AUTH-PROXY]: found port=${this._port}`);
|
||||
|
||||
try {
|
||||
await waitUntilUsed(this.port, 500, 10000);
|
||||
await this.dependencies.waitUntilPortIsUsed(this.port, 500, 10000);
|
||||
this.ready = true;
|
||||
} catch (error) {
|
||||
logger.warn("[KUBE-AUTH-PROXY]: waitUntilUsed failed", error);
|
||||
this.dependencies.logger.warn("[KUBE-AUTH-PROXY]: waitUntilUsed failed", error);
|
||||
this.cluster.broadcastConnectUpdate("Proxy port failed to be used within timelimit, restarting...", true);
|
||||
this.exit();
|
||||
|
||||
@ -124,7 +124,7 @@ export class KubeAuthProxy {
|
||||
this.ready = false;
|
||||
|
||||
if (this.proxyProcess) {
|
||||
logger.debug("[KUBE-AUTH]: stopping local proxy", this.cluster.getMeta());
|
||||
this.dependencies.logger.debug("[KUBE-AUTH]: stopping local proxy", this.cluster.getMeta());
|
||||
this.proxyProcess.removeAllListeners();
|
||||
this.proxyProcess.stderr?.removeAllListeners();
|
||||
this.proxyProcess.stdout?.removeAllListeners();
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getGlobalOverride } from "../../../common/test-utils/get-global-override";
|
||||
import waitUntilPortIsUsedInjectable from "./wait-until-port-is-used.injectable";
|
||||
|
||||
export default getGlobalOverride(
|
||||
waitUntilPortIsUsedInjectable,
|
||||
() => () => {
|
||||
throw new Error(
|
||||
"Tried to wait until port is used without explicit override.",
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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 { waitUntilUsed } from "tcp-port-used";
|
||||
|
||||
export type WaitUntilPortIsUsed = (
|
||||
port: number,
|
||||
retryAfterMs: number,
|
||||
timeoutAfterMs: number
|
||||
) => Promise<void>;
|
||||
|
||||
const waitUntilPortIsUsedInjectable = getInjectable({
|
||||
id: "wait-until-port-is-used",
|
||||
instantiate: (): WaitUntilPortIsUsed => waitUntilUsed,
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default waitUntilPortIsUsedInjectable;
|
||||
@ -5,7 +5,6 @@
|
||||
|
||||
import * as uuid from "uuid";
|
||||
|
||||
import { broadcastMessage } from "../../../common/ipc";
|
||||
import { ProtocolHandlerExtension, ProtocolHandlerInternal, ProtocolHandlerInvalid } from "../../../common/protocol-handler";
|
||||
import { delay, noop } from "../../../common/utils";
|
||||
import type { ExtensionsStore, IsEnabledExtensionDescriptor } from "../../../extensions/extensions-store/extensions-store";
|
||||
@ -19,8 +18,7 @@ import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||
import type { ObservableMap } from "mobx";
|
||||
import extensionInstancesInjectable from "../../../extensions/extension-loader/extension-instances.injectable";
|
||||
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
|
||||
jest.mock("../../../common/ipc");
|
||||
import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable";
|
||||
|
||||
function throwIfDefined(val: any): void {
|
||||
if (val != null) {
|
||||
@ -32,6 +30,7 @@ describe("protocol router tests", () => {
|
||||
let extensionInstances: ObservableMap<LensExtensionId, LensExtension>;
|
||||
let lpr: LensProtocolRouterMain;
|
||||
let enabledExtensions: Set<string>;
|
||||
let broadcastMessageMock: jest.Mock;
|
||||
|
||||
beforeEach(async () => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
@ -46,6 +45,9 @@ describe("protocol router tests", () => {
|
||||
|
||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||
|
||||
broadcastMessageMock = jest.fn();
|
||||
di.override(broadcastMessageInjectable, () => broadcastMessageMock);
|
||||
|
||||
extensionInstances = di.inject(extensionInstancesInjectable);
|
||||
lpr = di.inject(lensProtocolRouterMainInjectable);
|
||||
|
||||
@ -54,12 +56,12 @@ describe("protocol router tests", () => {
|
||||
|
||||
it("should broadcast invalid protocol on non-lens URLs", async () => {
|
||||
await lpr.route("https://google.ca");
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInvalid, "invalid protocol", "https://google.ca");
|
||||
expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerInvalid, "invalid protocol", "https://google.ca");
|
||||
});
|
||||
|
||||
it("should broadcast invalid host on non internal or non extension URLs", async () => {
|
||||
await lpr.route("lens://foobar");
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInvalid, "invalid host", "lens://foobar");
|
||||
expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerInvalid, "invalid host", "lens://foobar");
|
||||
});
|
||||
|
||||
it("should not throw when has valid host", async () => {
|
||||
@ -101,8 +103,8 @@ describe("protocol router tests", () => {
|
||||
}
|
||||
|
||||
await delay(50);
|
||||
expect(broadcastMessage).toHaveBeenCalledWith(ProtocolHandlerInternal, "lens://app", "matched");
|
||||
expect(broadcastMessage).toHaveBeenCalledWith(ProtocolHandlerExtension, "lens://extension/@mirantis/minikube", "matched");
|
||||
expect(broadcastMessageMock).toHaveBeenCalledWith(ProtocolHandlerInternal, "lens://app", "matched");
|
||||
expect(broadcastMessageMock).toHaveBeenCalledWith(ProtocolHandlerExtension, "lens://extension/@mirantis/minikube", "matched");
|
||||
});
|
||||
|
||||
it("should call handler if matches", async () => {
|
||||
@ -117,7 +119,7 @@ describe("protocol router tests", () => {
|
||||
}
|
||||
|
||||
expect(called).toBe(true);
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page", "matched");
|
||||
expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page", "matched");
|
||||
});
|
||||
|
||||
it("should call most exact handler", async () => {
|
||||
@ -133,7 +135,7 @@ describe("protocol router tests", () => {
|
||||
}
|
||||
|
||||
expect(called).toBe("foo");
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo", "matched");
|
||||
expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo", "matched");
|
||||
});
|
||||
|
||||
it("should call most exact handler for an extension", async () => {
|
||||
@ -174,7 +176,7 @@ describe("protocol router tests", () => {
|
||||
|
||||
await delay(50);
|
||||
expect(called).toBe("foob");
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/@foobar/icecream/page/foob", "matched");
|
||||
expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/@foobar/icecream/page/foob", "matched");
|
||||
});
|
||||
|
||||
it("should work with non-org extensions", async () => {
|
||||
@ -244,7 +246,7 @@ describe("protocol router tests", () => {
|
||||
await delay(50);
|
||||
|
||||
expect(called).toBe(1);
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/icecream/page", "matched");
|
||||
expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/icecream/page", "matched");
|
||||
});
|
||||
|
||||
it("should throw if urlSchema is invalid", () => {
|
||||
@ -266,7 +268,7 @@ describe("protocol router tests", () => {
|
||||
}
|
||||
|
||||
expect(called).toBe(3);
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat", "matched");
|
||||
expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat", "matched");
|
||||
});
|
||||
|
||||
it("should call most exact handler with 2 found handlers", async () => {
|
||||
@ -283,6 +285,6 @@ describe("protocol router tests", () => {
|
||||
}
|
||||
|
||||
expect(called).toBe(1);
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat", "matched");
|
||||
expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat", "matched");
|
||||
});
|
||||
});
|
||||
|
||||
@ -7,6 +7,8 @@ import extensionLoaderInjectable from "../../../extensions/extension-loader/exte
|
||||
import { LensProtocolRouterMain } from "./lens-protocol-router-main";
|
||||
import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
|
||||
import showApplicationWindowInjectable from "../../start-main-application/lens-window/show-application-window.injectable";
|
||||
import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
|
||||
const lensProtocolRouterMainInjectable = getInjectable({
|
||||
id: "lens-protocol-router-main",
|
||||
@ -16,6 +18,8 @@ const lensProtocolRouterMainInjectable = getInjectable({
|
||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
||||
extensionsStore: di.inject(extensionsStoreInjectable),
|
||||
showApplicationWindow: di.inject(showApplicationWindowInjectable),
|
||||
broadcastMessage: di.inject(broadcastMessageInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@ -3,15 +3,14 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import logger from "../../logger";
|
||||
import * as proto from "../../../common/protocol-handler";
|
||||
import URLParse from "url-parse";
|
||||
import type { LensExtension } from "../../../extensions/lens-extension";
|
||||
import { broadcastMessage } from "../../../common/ipc";
|
||||
import { observable, when, makeObservable } from "mobx";
|
||||
import type { LensProtocolRouterDependencies, RouteAttempt } from "../../../common/protocol-handler";
|
||||
import { ProtocolHandlerInvalid } from "../../../common/protocol-handler";
|
||||
import { disposer, noop } from "../../../common/utils";
|
||||
import type { BroadcastMessage } from "../../../common/ipc/broadcast-message.injectable";
|
||||
|
||||
export interface FallbackHandler {
|
||||
(name: string): Promise<boolean>;
|
||||
@ -36,6 +35,7 @@ function checkHost<Query>(url: URLParse<Query>): boolean {
|
||||
|
||||
export interface LensProtocolRouterMainDependencies extends LensProtocolRouterDependencies {
|
||||
showApplicationWindow: () => Promise<void>;
|
||||
broadcastMessage: BroadcastMessage;
|
||||
}
|
||||
|
||||
export class LensProtocolRouterMain extends proto.LensProtocolRouter {
|
||||
@ -73,7 +73,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
|
||||
this.dependencies.showApplicationWindow().catch(noop);
|
||||
const routeInternally = checkHost(url);
|
||||
|
||||
logger.info(`${proto.LensProtocolRouter.LoggingPrefix}: routing ${url.toString()}`);
|
||||
this.dependencies.logger.info(`${proto.LensProtocolRouter.LoggingPrefix}: routing ${url.toString()}`);
|
||||
|
||||
if (routeInternally) {
|
||||
this._routeToInternal(url);
|
||||
@ -81,12 +81,12 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
|
||||
await this._routeToExtension(url);
|
||||
}
|
||||
} catch (error) {
|
||||
broadcastMessage(ProtocolHandlerInvalid, error ? String(error) : "unknown error", rawUrl);
|
||||
this.dependencies.broadcastMessage(ProtocolHandlerInvalid, error ? String(error) : "unknown error", rawUrl);
|
||||
|
||||
if (error instanceof proto.RoutingError) {
|
||||
logger.error(`${proto.LensProtocolRouter.LoggingPrefix}: ${error}`, { url: error.url });
|
||||
this.dependencies.logger.error(`${proto.LensProtocolRouter.LoggingPrefix}: ${error}`, { url: error.url });
|
||||
} else {
|
||||
logger.error(`${proto.LensProtocolRouter.LoggingPrefix}: ${error}`, { rawUrl });
|
||||
this.dependencies.logger.error(`${proto.LensProtocolRouter.LoggingPrefix}: ${error}`, { rawUrl });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -119,7 +119,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
|
||||
const rawUrl = url.toString(); // for sending to renderer
|
||||
const attempt = super._routeToInternal(url);
|
||||
|
||||
this.disposers.push(when(() => this.rendererLoaded, () => broadcastMessage(proto.ProtocolHandlerInternal, rawUrl, attempt)));
|
||||
this.disposers.push(when(() => this.rendererLoaded, () => this.dependencies.broadcastMessage(proto.ProtocolHandlerInternal, rawUrl, attempt)));
|
||||
|
||||
return attempt;
|
||||
}
|
||||
@ -136,7 +136,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
|
||||
*/
|
||||
const attempt = await super._routeToExtension(new URLParse(url.toString(), true));
|
||||
|
||||
this.disposers.push(when(() => this.rendererLoaded, () => broadcastMessage(proto.ProtocolHandlerExtension, rawUrl, attempt)));
|
||||
this.disposers.push(when(() => this.rendererLoaded, () => this.dependencies.broadcastMessage(proto.ProtocolHandlerExtension, rawUrl, attempt)));
|
||||
|
||||
return attempt;
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ import { beforeApplicationIsLoadingInjectionToken } from "./runnable-tokens/befo
|
||||
import { onLoadOfApplicationInjectionToken } from "./runnable-tokens/on-load-of-application-injection-token";
|
||||
import { afterApplicationIsLoadedInjectionToken } from "./runnable-tokens/after-application-is-loaded-injection-token";
|
||||
import splashWindowInjectable from "./lens-window/splash-window/splash-window.injectable";
|
||||
|
||||
import openDeepLinkInjectable from "../protocol-handler/lens-protocol-router-main/open-deep-link-for-url/open-deep-link.injectable";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import { find, map, startsWith, toLower } from "lodash/fp";
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { IpcMain } from "electron";
|
||||
import { getGlobalOverride } from "../../../../common/test-utils/get-global-override";
|
||||
import ipcMainInjectable from "./ipc-main.injectable";
|
||||
|
||||
export default getGlobalOverride(ipcMainInjectable, () => ({
|
||||
handle: () => {},
|
||||
on: () => {},
|
||||
off: () => {},
|
||||
}) as unknown as IpcMain);
|
||||
@ -6,12 +6,14 @@ import { getInjectable } from "@ogre-tools/injectable";
|
||||
import catalogCategoryRegistryInjectable from "../../../../common/catalog/category-registry.injectable";
|
||||
import navigateInjectable from "../../../navigation/navigate.injectable";
|
||||
import { CatalogEntityRegistry } from "./registry";
|
||||
import loggerInjectable from "../../../../common/logger.injectable";
|
||||
|
||||
const catalogEntityRegistryInjectable = getInjectable({
|
||||
id: "catalog-entity-registry",
|
||||
instantiate: (di) => new CatalogEntityRegistry({
|
||||
categoryRegistry: di.inject(catalogCategoryRegistryInjectable),
|
||||
navigate: di.inject(navigateInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@ -10,12 +10,12 @@ import "../../../../common/catalog-entities";
|
||||
import { iter } from "../../../utils";
|
||||
import type { Disposer } from "../../../utils";
|
||||
import { once } from "lodash";
|
||||
import logger from "../../../../common/logger";
|
||||
import { CatalogRunEvent } from "../../../../common/catalog/catalog-run-event";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { catalogInitChannel, catalogItemsChannel, catalogEntityRunListener } from "../../../../common/ipc/catalog";
|
||||
import { isMainFrame } from "process";
|
||||
import type { Navigate } from "../../../navigation/navigate.injectable";
|
||||
import type { Logger } from "../../../../common/logger";
|
||||
|
||||
export type EntityFilter = (entity: CatalogEntity) => any;
|
||||
export type CatalogEntityOnBeforeRun = (event: CatalogRunEvent) => void | Promise<void>;
|
||||
@ -23,6 +23,7 @@ export type CatalogEntityOnBeforeRun = (event: CatalogRunEvent) => void | Promis
|
||||
interface Dependencies {
|
||||
navigate: Navigate;
|
||||
readonly categoryRegistry: CatalogCategoryRegistry;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export class CatalogEntityRegistry {
|
||||
@ -219,7 +220,7 @@ export class CatalogEntityRegistry {
|
||||
* @returns Whether the entities `onRun` method should be executed
|
||||
*/
|
||||
async onBeforeRun(entity: CatalogEntity): Promise<boolean> {
|
||||
logger.debug(`[CATALOG-ENTITY-REGISTRY]: run onBeforeRun on ${entity.getId()}`);
|
||||
this.dependencies.logger.debug(`[CATALOG-ENTITY-REGISTRY]: run onBeforeRun on ${entity.getId()}`);
|
||||
|
||||
const runEvent = new CatalogRunEvent({ target: entity });
|
||||
|
||||
@ -227,7 +228,7 @@ export class CatalogEntityRegistry {
|
||||
try {
|
||||
await onBeforeRun(runEvent);
|
||||
} catch (error) {
|
||||
logger.warn(`[CATALOG-ENTITY-REGISTRY]: entity ${entity.getId()} onBeforeRun threw an error`, error);
|
||||
this.dependencies.logger.warn(`[CATALOG-ENTITY-REGISTRY]: entity ${entity.getId()} onBeforeRun threw an error`, error);
|
||||
}
|
||||
|
||||
if (runEvent.defaultPrevented) {
|
||||
@ -253,9 +254,9 @@ export class CatalogEntityRegistry {
|
||||
},
|
||||
});
|
||||
} else {
|
||||
logger.debug(`onBeforeRun for ${entity.getId()} returned false`);
|
||||
this.dependencies.logger.debug(`onBeforeRun for ${entity.getId()} returned false`);
|
||||
}
|
||||
})
|
||||
.catch(error => logger.error(`[CATALOG-ENTITY-REGISTRY]: entity ${entity.getId()} onRun threw an error`, error));
|
||||
.catch(error => this.dependencies.logger.error(`[CATALOG-ENTITY-REGISTRY]: entity ${entity.getId()} onRun threw an error`, error));
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,10 +7,9 @@ import React from "react";
|
||||
import { screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { Catalog } from "./catalog";
|
||||
import { mockWindow } from "../../../../__mocks__/windowMock";
|
||||
import type { CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog";
|
||||
import { CatalogEntity } from "../../../common/catalog";
|
||||
import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
|
||||
import type { CatalogEntityOnBeforeRun, CatalogEntityRegistry } from "../../api/catalog/entity/registry";
|
||||
import { CatalogEntityDetailRegistry } from "../../../extensions/registries";
|
||||
import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
@ -19,38 +18,15 @@ import catalogEntityStoreInjectable from "./catalog-entity-store/catalog-entity-
|
||||
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
|
||||
import type { DiRender } from "../test-utils/renderFor";
|
||||
import { renderFor } from "../test-utils/renderFor";
|
||||
import mockFs from "mock-fs";
|
||||
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 type { AppEvent } from "../../../common/app-event-bus/event-bus";
|
||||
import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable";
|
||||
import { computed } from "mobx";
|
||||
import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable";
|
||||
|
||||
mockWindow();
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
getVersion: () => "99.99.99",
|
||||
getName: () => "lens",
|
||||
setName: jest.fn(),
|
||||
setPath: jest.fn(),
|
||||
getPath: () => "tmp",
|
||||
getLocale: () => "en",
|
||||
setLoginItemSettings: jest.fn(),
|
||||
},
|
||||
ipcMain: {
|
||||
on: jest.fn(),
|
||||
handle: jest.fn(),
|
||||
},
|
||||
ipcRenderer: {
|
||||
on: jest.fn(),
|
||||
invoke: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock("./hotbar-toggle-menu-item", () => ({
|
||||
HotbarToggleMenuItem: () => <div>menu item</div>,
|
||||
}));
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import { flushPromises } from "../../../common/test-utils/flush-promises";
|
||||
|
||||
class MockCatalogEntity extends CatalogEntity {
|
||||
public apiVersion = "api";
|
||||
@ -95,7 +71,6 @@ describe("<Catalog />", () => {
|
||||
|
||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||
|
||||
mockFs();
|
||||
CatalogEntityDetailRegistry.createInstance();
|
||||
|
||||
render = renderFor(di);
|
||||
@ -103,8 +78,6 @@ describe("<Catalog />", () => {
|
||||
catalogEntityItem = createMockCatalogEntity(onRun);
|
||||
catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
||||
|
||||
di.override(catalogEntityRegistryInjectable, () => catalogEntityRegistry);
|
||||
|
||||
emitEvent = jest.fn();
|
||||
|
||||
di.override(appEventBusInjectable, () => ({
|
||||
@ -119,60 +92,71 @@ describe("<Catalog />", () => {
|
||||
|
||||
afterEach(() => {
|
||||
CatalogEntityDetailRegistry.resetInstance();
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it("can use catalogEntityRegistry.addOnBeforeRun to add hooks for catalog entities", (done) => {
|
||||
catalogEntityRegistry.addOnBeforeRun(
|
||||
(event) => {
|
||||
expect(event.target.getId()).toBe("a_catalogEntity_uid");
|
||||
expect(event.target.getName()).toBe("a catalog entity");
|
||||
describe("can use catalogEntityRegistry.addOnBeforeRun to add hooks for catalog entities", () => {
|
||||
let onBeforeRunMock: AsyncFnMock<CatalogEntityOnBeforeRun>;
|
||||
|
||||
setTimeout(() => {
|
||||
expect(onRun).toHaveBeenCalled();
|
||||
done();
|
||||
}, 500);
|
||||
},
|
||||
);
|
||||
beforeEach(() => {
|
||||
onBeforeRunMock = asyncFn();
|
||||
|
||||
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
|
||||
|
||||
render(<Catalog />);
|
||||
|
||||
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
|
||||
});
|
||||
|
||||
it("calls on before run event", () => {
|
||||
const target = onBeforeRunMock.mock.calls[0][0].target;
|
||||
|
||||
const actual = { id: target.getId(), name: target.getName() };
|
||||
|
||||
expect(actual).toEqual({
|
||||
id: "a_catalogEntity_uid",
|
||||
name: "a catalog entity",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not call onRun yet", () => {
|
||||
expect(onRun).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("when before run event resolves, calls onRun", async () => {
|
||||
await onBeforeRunMock.resolve();
|
||||
|
||||
expect(onRun).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("onBeforeRun prevents event => onRun wont be triggered", async () => {
|
||||
const onBeforeRunMock = jest.fn((event) => event.preventDefault());
|
||||
|
||||
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
|
||||
|
||||
render(<Catalog />);
|
||||
|
||||
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(onRun).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("onBeforeRun prevents event => onRun wont be triggered", (done) => {
|
||||
catalogEntityRegistry.addOnBeforeRun(
|
||||
(e) => {
|
||||
setTimeout(() => {
|
||||
expect(onRun).not.toHaveBeenCalled();
|
||||
done();
|
||||
}, 500);
|
||||
e.preventDefault();
|
||||
},
|
||||
);
|
||||
it("addOnBeforeRun throw an exception => onRun will be triggered", async () => {
|
||||
const onBeforeRunMock = jest.fn(() => {
|
||||
throw new Error("some error");
|
||||
});
|
||||
|
||||
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
|
||||
|
||||
render(<Catalog />);
|
||||
|
||||
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
|
||||
});
|
||||
|
||||
it("addOnBeforeRun throw an exception => onRun will be triggered", (done) => {
|
||||
catalogEntityRegistry.addOnBeforeRun(
|
||||
() => {
|
||||
setTimeout(() => {
|
||||
expect(onRun).toHaveBeenCalled();
|
||||
done();
|
||||
}, 500);
|
||||
await flushPromises();
|
||||
|
||||
throw new Error("error!");
|
||||
},
|
||||
);
|
||||
|
||||
render(<Catalog />);
|
||||
|
||||
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
|
||||
expect(onRun).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("addOnRunHook return a promise and does not prevent run event => onRun()", (done) => {
|
||||
@ -189,40 +173,34 @@ describe("<Catalog />", () => {
|
||||
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
|
||||
});
|
||||
|
||||
it("addOnRunHook return a promise and prevents event wont be triggered", (done) => {
|
||||
catalogEntityRegistry.addOnBeforeRun(
|
||||
async (e) => {
|
||||
expect(onRun).not.toBeCalled();
|
||||
it("addOnRunHook return a promise and prevents event wont be triggered", async () => {
|
||||
const onBeforeRunMock = asyncFn();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(onRun).not.toBeCalled();
|
||||
done();
|
||||
}, 500);
|
||||
|
||||
e.preventDefault();
|
||||
},
|
||||
);
|
||||
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
|
||||
|
||||
render(<Catalog />);
|
||||
|
||||
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
|
||||
|
||||
onBeforeRunMock.mock.calls[0][0].preventDefault();
|
||||
|
||||
await onBeforeRunMock.resolve();
|
||||
|
||||
expect(onRun).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("addOnRunHook return a promise and reject => onRun will be triggered", (done) => {
|
||||
catalogEntityRegistry.addOnBeforeRun(
|
||||
async () => {
|
||||
setTimeout(() => {
|
||||
expect(onRun).toHaveBeenCalled();
|
||||
done();
|
||||
}, 500);
|
||||
it("addOnRunHook return a promise and reject => onRun will be triggered", async () => {
|
||||
const onBeforeRunMock = asyncFn();
|
||||
|
||||
throw new Error("rejection!");
|
||||
},
|
||||
);
|
||||
catalogEntityRegistry.addOnBeforeRun(onBeforeRunMock);
|
||||
|
||||
render(<Catalog />);
|
||||
|
||||
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
|
||||
|
||||
await onBeforeRunMock.reject();
|
||||
|
||||
expect(onRun).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("emits catalog open AppEvent", () => {
|
||||
|
||||
@ -4,17 +4,20 @@
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render } from "@testing-library/react";
|
||||
import { SecretDetails } from "../secret-details";
|
||||
import { Secret, SecretType } from "../../../../common/k8s-api/endpoints";
|
||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||
import { renderFor } from "../../test-utils/renderFor";
|
||||
|
||||
jest.mock("../../kube-object-meta/kube-object-meta", () => ({
|
||||
KubeObjectMeta: () => null,
|
||||
}));
|
||||
|
||||
|
||||
describe("SecretDetails tests", () => {
|
||||
it("should show the visibility toggle when the secret value is ''", () => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
const render = renderFor(di);
|
||||
|
||||
const secret = new Secret({
|
||||
apiVersion: "v1",
|
||||
kind: "secret",
|
||||
|
||||
@ -4,15 +4,22 @@
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { findByTestId, findByText, render } from "@testing-library/react";
|
||||
import { findByTestId, findByText } from "@testing-library/react";
|
||||
import { NetworkPolicy } from "../../../../common/k8s-api/endpoints";
|
||||
import { NetworkPolicyDetails } from "../network-policy-details";
|
||||
|
||||
jest.mock("../../kube-object-meta/kube-object-meta", () => ({
|
||||
KubeObjectMeta: () => null,
|
||||
}));
|
||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||
import type { DiRender } from "../../test-utils/renderFor";
|
||||
import { renderFor } from "../../test-utils/renderFor";
|
||||
|
||||
describe("NetworkPolicyDetails", () => {
|
||||
let render: DiRender;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
render = renderFor(di);
|
||||
});
|
||||
|
||||
it("should render w/o errors", () => {
|
||||
const policy = new NetworkPolicy({
|
||||
metadata: {} as never,
|
||||
|
||||
@ -4,28 +4,38 @@
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, waitFor } from "@testing-library/react";
|
||||
import { waitFor } from "@testing-library/react";
|
||||
import { ClusterLocalTerminalSetting } from "../cluster-local-terminal-settings";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { stat } from "fs/promises";
|
||||
import { Notifications } from "../../../notifications";
|
||||
import type { Stats } from "fs";
|
||||
import type { Cluster } from "../../../../../common/cluster/cluster";
|
||||
|
||||
const mockStat = stat as jest.MockedFunction<typeof stat>;
|
||||
|
||||
jest.mock("fs", () => {
|
||||
const actual = jest.requireActual("fs");
|
||||
|
||||
actual.promises.stat = jest.fn();
|
||||
|
||||
return actual;
|
||||
});
|
||||
|
||||
jest.mock("../../../notifications");
|
||||
import { getDiForUnitTesting } from "../../../../getDiForUnitTesting";
|
||||
import type { DiRender } from "../../../test-utils/renderFor";
|
||||
import { renderFor } from "../../../test-utils/renderFor";
|
||||
import showErrorNotificationInjectable from "../../../notifications/show-error-notification.injectable";
|
||||
import statInjectable from "../../../../../common/fs/stat/stat.injectable";
|
||||
|
||||
describe("ClusterLocalTerminalSettings", () => {
|
||||
let render: DiRender;
|
||||
let showErrorNotificationMock: jest.Mock;
|
||||
let statMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
showErrorNotificationMock = jest.fn();
|
||||
|
||||
statMock = jest.fn();
|
||||
|
||||
di.override(statInjectable, () => statMock);
|
||||
|
||||
di.override(
|
||||
showErrorNotificationInjectable,
|
||||
() => showErrorNotificationMock,
|
||||
);
|
||||
|
||||
render = renderFor(di);
|
||||
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
@ -89,7 +99,7 @@ describe("ClusterLocalTerminalSettings", () => {
|
||||
});
|
||||
|
||||
it("should save the new CWD if path is a directory", async () => {
|
||||
mockStat.mockImplementation(async (path) => {
|
||||
statMock.mockImplementation(async (path) => {
|
||||
expect(path).toBe("/foobar");
|
||||
|
||||
return {
|
||||
@ -114,7 +124,7 @@ describe("ClusterLocalTerminalSettings", () => {
|
||||
});
|
||||
|
||||
it("should not save the new CWD if path is a file", async () => {
|
||||
mockStat.mockImplementation(async (path) => {
|
||||
statMock.mockImplementation(async (path) => {
|
||||
expect(path).toBe("/foobar");
|
||||
|
||||
return {
|
||||
@ -136,6 +146,6 @@ describe("ClusterLocalTerminalSettings", () => {
|
||||
userEvent.type(dn, "/foobar");
|
||||
userEvent.click(dom.baseElement);
|
||||
|
||||
await waitFor(() => expect(Notifications.error).toBeCalled());
|
||||
await waitFor(() => expect(showErrorNotificationMock).toHaveBeenCalled());
|
||||
});
|
||||
});
|
||||
|
||||
@ -8,79 +8,25 @@ import { observer } from "mobx-react";
|
||||
import type { Cluster } from "../../../../common/cluster/cluster";
|
||||
import { Input } from "../../input";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import { stat } from "fs/promises";
|
||||
import { Notifications } from "../../notifications";
|
||||
import { isErrnoException, resolveTilde } from "../../../utils";
|
||||
import type { ShowNotification } from "../../notifications";
|
||||
import { resolveTilde } from "../../../utils";
|
||||
import { Icon } from "../../icon";
|
||||
import { PathPicker } from "../../path-picker";
|
||||
import { isWindows } from "../../../../common/vars";
|
||||
import type { Stats } from "fs";
|
||||
import logger from "../../../../common/logger";
|
||||
import { lowerFirst } from "lodash";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
|
||||
import type { ValidateDirectory } from "../../../../common/fs/validate-directory.injectable";
|
||||
import validateDirectoryInjectable from "../../../../common/fs/validate-directory.injectable";
|
||||
|
||||
export interface ClusterLocalTerminalSettingProps {
|
||||
cluster: Cluster;
|
||||
}
|
||||
|
||||
function getUserReadableFileType(stats: Stats): string {
|
||||
if (stats.isFile()) {
|
||||
return "a file";
|
||||
}
|
||||
|
||||
if (stats.isFIFO()) {
|
||||
return "a pipe";
|
||||
}
|
||||
|
||||
if (stats.isSocket()) {
|
||||
return "a socket";
|
||||
}
|
||||
|
||||
if (stats.isBlockDevice()) {
|
||||
return "a block device";
|
||||
}
|
||||
|
||||
if (stats.isCharacterDevice()) {
|
||||
return "a character device";
|
||||
}
|
||||
|
||||
return "an unknown file type";
|
||||
interface Dependencies {
|
||||
showErrorNotification: ShowNotification;
|
||||
validateDirectory: ValidateDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that `dir` currently points to a directory. If so return `false`.
|
||||
* Otherwise, return a user readable error message string for displaying.
|
||||
* @param dir The path to be validated
|
||||
*/
|
||||
async function validateDirectory(dir: string): Promise<string | false> {
|
||||
try {
|
||||
const stats = await stat(dir);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return `the provided path is ${getUserReadableFileType(stats)} and not a directory.`;
|
||||
} catch (error) {
|
||||
switch (isErrnoException(error) ? error.code : undefined) {
|
||||
case "ENOENT":
|
||||
return `the provided path does not exist.`;
|
||||
case "EACCES":
|
||||
return `search permissions is denied for one of the directories in the prefix of the provided path.`;
|
||||
case "ELOOP":
|
||||
return `the provided path is a sym-link which points to a chain of sym-links that is too long to resolve. Perhaps it is cyclic.`;
|
||||
case "ENAMETOOLONG":
|
||||
return `the pathname is too long to be used.`;
|
||||
case "ENOTDIR":
|
||||
return `a prefix of the provided path is not a directory.`;
|
||||
default:
|
||||
logger.warn(`[CLUSTER-LOCAL-TERMINAL-SETTINGS]: unexpected error in validateDirectory for resolved path=${dir}`, error);
|
||||
|
||||
return error ? lowerFirst(String(error)) : "of an unknown error, please try again.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const ClusterLocalTerminalSetting = observer(({ cluster }: ClusterLocalTerminalSettingProps) => {
|
||||
const NonInjectedClusterLocalTerminalSetting = observer(({ cluster, showErrorNotification, validateDirectory }: Dependencies & ClusterLocalTerminalSettingProps) => {
|
||||
if (!cluster) {
|
||||
return null;
|
||||
}
|
||||
@ -109,15 +55,15 @@ export const ClusterLocalTerminalSetting = observer(({ cluster }: ClusterLocalTe
|
||||
cluster.preferences.terminalCWD = undefined;
|
||||
} else {
|
||||
const dir = resolveTilde(directory);
|
||||
const errorMessage = await validateDirectory(dir);
|
||||
const result = await validateDirectory(dir);
|
||||
|
||||
if (errorMessage) {
|
||||
Notifications.error(
|
||||
if (!result.callWasSuccessful) {
|
||||
showErrorNotification(
|
||||
<>
|
||||
<b>Terminal Working Directory</b>
|
||||
<p>
|
||||
{"Your changes were not saved because "}
|
||||
{errorMessage}
|
||||
{result.error}
|
||||
</p>
|
||||
</>,
|
||||
);
|
||||
@ -200,3 +146,16 @@ export const ClusterLocalTerminalSetting = observer(({ cluster }: ClusterLocalTe
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export const ClusterLocalTerminalSetting = withInjectables<Dependencies, ClusterLocalTerminalSettingProps>(
|
||||
NonInjectedClusterLocalTerminalSetting,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
showErrorNotification: di.inject(showErrorNotificationInjectable),
|
||||
validateDirectory: di.inject(validateDirectoryInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -19,7 +19,6 @@ exports[`kube-object-menu given kube object renders 1`] = `
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
tabindex="0"
|
||||
tooltip="Delete"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
@ -28,6 +27,9 @@ exports[`kube-object-menu given kube object renders 1`] = `
|
||||
delete
|
||||
</span>
|
||||
</i>
|
||||
<div>
|
||||
Delete
|
||||
</div>
|
||||
<span
|
||||
class="title"
|
||||
>
|
||||
@ -59,7 +61,6 @@ exports[`kube-object-menu given kube object when removing kube object renders 1`
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
tabindex="0"
|
||||
tooltip="Delete"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
@ -68,6 +69,9 @@ exports[`kube-object-menu given kube object when removing kube object renders 1`
|
||||
delete
|
||||
</span>
|
||||
</i>
|
||||
<div>
|
||||
Delete
|
||||
</div>
|
||||
<span
|
||||
class="title"
|
||||
>
|
||||
@ -148,7 +152,6 @@ exports[`kube-object-menu given kube object when rerendered with different kube
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
tabindex="0"
|
||||
tooltip="Delete"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
@ -157,6 +160,9 @@ exports[`kube-object-menu given kube object when rerendered with different kube
|
||||
delete
|
||||
</span>
|
||||
</i>
|
||||
<div>
|
||||
Delete
|
||||
</div>
|
||||
<span
|
||||
class="title"
|
||||
>
|
||||
@ -185,7 +191,6 @@ exports[`kube-object-menu given kube object when rerendered with different kube
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
tabindex="0"
|
||||
tooltip="Delete"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
@ -194,6 +199,9 @@ exports[`kube-object-menu given kube object when rerendered with different kube
|
||||
delete
|
||||
</span>
|
||||
</i>
|
||||
<div>
|
||||
Delete
|
||||
</div>
|
||||
<span
|
||||
class="title"
|
||||
>
|
||||
@ -277,7 +285,6 @@ exports[`kube-object-menu given kube object with namespace when removing kube ob
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
tabindex="0"
|
||||
tooltip="Delete"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
@ -286,6 +293,9 @@ exports[`kube-object-menu given kube object with namespace when removing kube ob
|
||||
delete
|
||||
</span>
|
||||
</i>
|
||||
<div>
|
||||
Delete
|
||||
</div>
|
||||
<span
|
||||
class="title"
|
||||
>
|
||||
@ -369,7 +379,6 @@ exports[`kube-object-menu given kube object without namespace when removing kube
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
tabindex="0"
|
||||
tooltip="Delete"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
@ -378,6 +387,9 @@ exports[`kube-object-menu given kube object without namespace when removing kube
|
||||
delete
|
||||
</span>
|
||||
</i>
|
||||
<div>
|
||||
Delete
|
||||
</div>
|
||||
<span
|
||||
class="title"
|
||||
>
|
||||
|
||||
@ -26,12 +26,6 @@ import createEditResourceTabInjectable from "../dock/edit-resource/edit-resource
|
||||
import hideDetailsInjectable from "../kube-detail-params/hide-details.injectable";
|
||||
import { kubeObjectMenuItemInjectionToken } from "./kube-object-menu-item-injection-token";
|
||||
|
||||
// TODO: Make tooltips free of side effects by making it deterministic
|
||||
jest.mock("../tooltip/tooltip");
|
||||
jest.mock("../tooltip/withTooltip", () => ({
|
||||
withTooltip: (target: any) => target,
|
||||
}));
|
||||
|
||||
// TODO: make `animated={false}` not required to make tests deterministic
|
||||
describe("kube-object-menu", () => {
|
||||
let di: DiContainer;
|
||||
|
||||
@ -8,6 +8,7 @@ import { Cluster } from "../../common/cluster/cluster";
|
||||
import directoryForKubeConfigsInjectable from "../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
|
||||
|
||||
const createClusterInjectable = getInjectable({
|
||||
id: "create-cluster",
|
||||
@ -16,6 +17,7 @@ const createClusterInjectable = getInjectable({
|
||||
const dependencies: ClusterDependencies = {
|
||||
directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
broadcastMessage: di.inject(broadcastMessageInjectable),
|
||||
|
||||
// TODO: Dismantle wrong abstraction
|
||||
// Note: "as never" to get around strictness in unnatural scenario
|
||||
|
||||
@ -32,8 +32,6 @@ import { ApiManager } from "../common/k8s-api/api-manager";
|
||||
import lensResourcesDirInjectable from "../common/vars/lens-resources-dir.injectable";
|
||||
import broadcastMessageInjectable from "../common/ipc/broadcast-message.injectable";
|
||||
import apiManagerInjectable from "../common/k8s-api/api-manager/manager.injectable";
|
||||
import ipcRendererInjectable from "./utils/channel/ipc-renderer.injectable";
|
||||
import type { IpcRenderer } from "electron";
|
||||
import setupOnApiErrorListenersInjectable from "./api/setup-on-api-errors.injectable";
|
||||
import { observable, computed } from "mobx";
|
||||
import defaultShellInjectable from "./components/+preferences/default-shell.injectable";
|
||||
@ -164,11 +162,6 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {})
|
||||
di.override(maximizeWindowInjectable, () => () => {});
|
||||
di.override(toggleMaximizeWindowInjectable, () => () => {});
|
||||
|
||||
di.override(ipcRendererInjectable, () => ({
|
||||
invoke: () => {},
|
||||
on: () => {},
|
||||
}) as unknown as IpcRenderer);
|
||||
|
||||
overrideFunctionalInjectables(di, [
|
||||
broadcastMessageInjectable,
|
||||
getFilePathsInjectable,
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import type { OpenDialogOptions } from "electron";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { clusterActivateHandler, clusterClearDeletingHandler, clusterDeleteHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterSetDeletingHandler, clusterSetFrameIdHandler, clusterStates } from "../../common/ipc/cluster";
|
||||
import type { ClusterId, ClusterState } from "../../common/cluster-types";
|
||||
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel, type WindowAction } from "../../common/ipc/window";
|
||||
@ -14,12 +13,22 @@ import type { InstalledExtension } from "../../extensions/extension-discovery/ex
|
||||
import type { LensExtensionId } from "../../extensions/lens-extension";
|
||||
import { toJS } from "../utils";
|
||||
import type { Location } from "history";
|
||||
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
import ipcRendererInjectable from "../utils/channel/ipc-renderer.injectable";
|
||||
|
||||
function requestMain(channel: string, ...args: any[]) {
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
|
||||
const ipcRenderer = di.inject(ipcRendererInjectable);
|
||||
|
||||
return ipcRenderer.invoke(channel, ...args.map(toJS));
|
||||
}
|
||||
|
||||
function emitToMain(channel: string, ...args: any[]) {
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
|
||||
const ipcRenderer = di.inject(ipcRendererInjectable);
|
||||
|
||||
return ipcRenderer.send(channel, ...args.map(toJS));
|
||||
}
|
||||
|
||||
|
||||
@ -5,8 +5,8 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
|
||||
import { LensProtocolRouterRenderer } from "./lens-protocol-router-renderer";
|
||||
import extensionsStoreInjectable
|
||||
from "../../../extensions/extensions-store/extensions-store.injectable";
|
||||
import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
|
||||
const lensProtocolRouterRendererInjectable = getInjectable({
|
||||
id: "lens-protocol-router-renderer",
|
||||
@ -15,6 +15,7 @@ const lensProtocolRouterRendererInjectable = getInjectable({
|
||||
new LensProtocolRouterRenderer({
|
||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
||||
extensionsStore: di.inject(extensionsStoreInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@ -8,10 +8,9 @@ import { ipcRenderer } from "electron";
|
||||
import * as proto from "../../../common/protocol-handler";
|
||||
import Url from "url-parse";
|
||||
import { onCorrect } from "../../../common/ipc";
|
||||
import type { LensProtocolRouterDependencies } from "../../../common/protocol-handler";
|
||||
import { foldAttemptResults, ProtocolHandlerInvalid, RouteAttempt } from "../../../common/protocol-handler";
|
||||
import { Notifications } from "../../components/notifications";
|
||||
import type { ExtensionLoader } from "../../../extensions/extension-loader";
|
||||
import type { ExtensionsStore } from "../../../extensions/extensions-store/extensions-store";
|
||||
|
||||
function verifyIpcArgs(args: unknown[]): args is [string, RouteAttempt] {
|
||||
if (args.length !== 2) {
|
||||
@ -32,11 +31,7 @@ function verifyIpcArgs(args: unknown[]): args is [string, RouteAttempt] {
|
||||
}
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
extensionLoader: ExtensionLoader;
|
||||
extensionsStore: ExtensionsStore;
|
||||
}
|
||||
|
||||
interface Dependencies extends LensProtocolRouterDependencies {}
|
||||
|
||||
export class LensProtocolRouterRenderer extends proto.LensProtocolRouter {
|
||||
constructor(protected dependencies: Dependencies) {
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { IpcRenderer } from "electron";
|
||||
import ipcRendererInjectable from "./ipc-renderer.injectable";
|
||||
import { getGlobalOverride } from "../../../common/test-utils/get-global-override";
|
||||
|
||||
export default getGlobalOverride(ipcRendererInjectable, () => ({
|
||||
invoke: () => {},
|
||||
on: () => {},
|
||||
}) as unknown as IpcRenderer);
|
||||
Loading…
Reference in New Issue
Block a user