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:
Janne Savolainen authored and GitHub committed 2022-08-23 13:04:00 +03:00
1 parent 03d2389f01
commit 81f7bd3c2c
50 files changed
+788 -605

No files matched your search

+5 -4
View File
@@ -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;
@@ -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
View 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;
@@ -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;
@@ -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
View 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,
});
+15
View File
@@ -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
View File
@@ -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;