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

+2 -5
View File
@@ -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) => ({
+21 -44
View File
@@ -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);
-2
View File
@@ -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) ?? {};
}
}
+149 -2
View File
@@ -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) ?? {};
}
}
@@ -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;
@@ -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;
+11 -16
View File
@@ -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);
+7 -7
View File
@@ -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);