mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fix context handler tests
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
cdaba4a4e8
commit
77e529f6b2
@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { ClusterContextHandler } from "../context-handler/context-handler";
|
||||
import type { ClusterContextHandler, PrometheusDetails } from "../context-handler/context-handler";
|
||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
@ -13,15 +13,28 @@ import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { PrometheusProvider } from "../prometheus/provider";
|
||||
import { prometheusProviderInjectionToken } from "../prometheus/provider";
|
||||
import { runInAction } from "mobx";
|
||||
import createClusterInjectable from "../create-cluster/create-cluster.injectable";
|
||||
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
|
||||
import type { KubeAuthProxy } from "../kube-auth-proxy/kube-auth-proxy";
|
||||
import createKubeconfigManagerInjectable from "../kubeconfig-manager/create-kubeconfig-manager.injectable";
|
||||
import type { KubeconfigManager } from "../kubeconfig-manager/kubeconfig-manager";
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import type { ReadFile } from "../../common/fs/read-file.injectable";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import readFileInjectable from "../../common/fs/read-file.injectable";
|
||||
import jsyaml from "js-yaml";
|
||||
import type { AsyncResult } from "../../common/utils/async-result";
|
||||
import assert from "assert";
|
||||
|
||||
enum ServiceResult {
|
||||
Success,
|
||||
Failure,
|
||||
}
|
||||
|
||||
const createTestPrometheusProvider = (kind: string, alwaysFail: ServiceResult): PrometheusProvider => ({
|
||||
const createTestPrometheusProvider = (kind: string, name: string, alwaysFail: ServiceResult): PrometheusProvider => ({
|
||||
kind,
|
||||
name: "TestProvider1",
|
||||
name,
|
||||
isConfigurable: false,
|
||||
getQuery: () => {
|
||||
throw new Error("getQuery is not implemented.");
|
||||
@ -41,151 +54,232 @@ const createTestPrometheusProvider = (kind: string, alwaysFail: ServiceResult):
|
||||
},
|
||||
});
|
||||
|
||||
const clusterStub = {
|
||||
getProxyKubeconfig: () => ({
|
||||
makeApiClient: (): void => undefined,
|
||||
}),
|
||||
apiUrl: "http://localhost:81",
|
||||
} as unknown as Cluster;
|
||||
const proxyConfigValue = jsyaml.dump({
|
||||
apiVersion: "v1",
|
||||
clusters: [{
|
||||
cluster: {
|
||||
server: "https://127.0.0.1:55009",
|
||||
},
|
||||
name: "some-cluster-name",
|
||||
}],
|
||||
users: [{
|
||||
name: "some-user-name",
|
||||
user: {
|
||||
"client-certificate": "/some/path/to/client-cert",
|
||||
"client-key": "/some/path/to/client-key",
|
||||
},
|
||||
}],
|
||||
contexts: [{
|
||||
name: "some-proxy-context",
|
||||
context: {
|
||||
user: "some-user-name",
|
||||
cluster: "some-cluster-name",
|
||||
},
|
||||
}],
|
||||
"current-context": "some-proxy-context",
|
||||
});
|
||||
|
||||
describe("ContextHandler", () => {
|
||||
let createContextHandler: (cluster: Cluster) => ClusterContextHandler;
|
||||
let di: DiContainer;
|
||||
let cluster: Cluster;
|
||||
let readFileMock: AsyncFnMock<ReadFile>;
|
||||
|
||||
beforeEach(() => {
|
||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
di.override(createKubeAuthProxyInjectable, () => ({} as any));
|
||||
di.override(createKubeAuthProxyInjectable, () => () => ({
|
||||
run: async () => {},
|
||||
} as Partial<KubeAuthProxy> as KubeAuthProxy));
|
||||
di.override(createKubeconfigManagerInjectable, () => () => ({
|
||||
getPath: async () => "/some/proxy-kubeconfig",
|
||||
} as Partial<KubeconfigManager> as KubeconfigManager));
|
||||
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
|
||||
di.override(directoryForTempInjectable, () => "/some-directory-for-temp-data");
|
||||
|
||||
readFileMock = asyncFn();
|
||||
di.override(readFileInjectable, () => readFileMock);
|
||||
|
||||
createContextHandler = di.inject(createContextHandlerInjectable);
|
||||
|
||||
const createCluster = di.inject(createClusterInjectable);
|
||||
|
||||
cluster = createCluster({
|
||||
contextName: "some-context-name",
|
||||
id: "some-cluster-id",
|
||||
kubeConfigPath: "/some/path/to/kubeconfig",
|
||||
}, {
|
||||
clusterServerUrl: "http://localhost:81",
|
||||
});
|
||||
});
|
||||
|
||||
describe("getPrometheusService", () => {
|
||||
it.each([
|
||||
[0],
|
||||
[1],
|
||||
[2],
|
||||
[3],
|
||||
])("should throw after %d failure(s)", async (failures) => {
|
||||
runInAction(() => {
|
||||
for (let i = 0; i < failures; i += 1) {
|
||||
di.register(getInjectable({
|
||||
id: `test-prometheus-provider-failure-${i}`,
|
||||
injectionToken: prometheusProviderInjectionToken,
|
||||
instantiate: () => createTestPrometheusProvider(`id_failure_${i}`, ServiceResult.Failure),
|
||||
}));
|
||||
}
|
||||
for (let failures = 0; failures < 4; failures += 1) {
|
||||
describe(`with ${failures} providers that throw`, () => {
|
||||
beforeEach(() => {
|
||||
runInAction(() => {
|
||||
for (let i = 0; i < failures; i += 1) {
|
||||
di.register(getInjectable({
|
||||
id: `test-prometheus-provider-failure-${i}`,
|
||||
injectionToken: prometheusProviderInjectionToken,
|
||||
instantiate: () => createTestPrometheusProvider(`id_failure_${i}`, `TestService-${i}`, ServiceResult.Failure),
|
||||
}));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("when getting prometheus details", () => {
|
||||
let details: Promise<AsyncResult<PrometheusDetails, Error>>;
|
||||
|
||||
beforeEach(() => {
|
||||
details = createContextHandler(cluster).getPrometheusDetails();
|
||||
});
|
||||
|
||||
it("should read the proxy config file", () => {
|
||||
expect(readFileMock).toBeCalledWith("/some/proxy-kubeconfig");
|
||||
});
|
||||
|
||||
describe("when reading the proxy config file resolves", () => {
|
||||
beforeEach(async () => {
|
||||
await readFileMock.resolveSpecific(
|
||||
["/some/proxy-kubeconfig"],
|
||||
proxyConfigValue,
|
||||
);
|
||||
});
|
||||
|
||||
it("should resolve with a failed call", async () => {
|
||||
await expect(details).resolves.toMatchObject({ callWasSuccessful: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
expect(() => createContextHandler(clusterStub).getPrometheusDetails()).rejects.toThrowError();
|
||||
});
|
||||
for (let successes = 1; successes < 3; successes += 1) {
|
||||
for (let failures = 0; failures < 4; failures += 1) {
|
||||
describe(`with ${successes} successful providers followed by ${failures} erroring providers`, () => {
|
||||
beforeEach(() => {
|
||||
runInAction(() => {
|
||||
for (let i = 0; i < failures; i += 1) {
|
||||
di.register(getInjectable({
|
||||
id: `test-prometheus-provider-failure-${i}`,
|
||||
injectionToken: prometheusProviderInjectionToken,
|
||||
instantiate: () => createTestPrometheusProvider(`id_failure_${i}`, `TestService-${i}`, ServiceResult.Failure),
|
||||
}));
|
||||
}
|
||||
|
||||
it.each([
|
||||
[1, 0],
|
||||
[1, 1],
|
||||
[1, 2],
|
||||
[1, 3],
|
||||
[2, 0],
|
||||
[2, 1],
|
||||
[2, 2],
|
||||
[2, 3],
|
||||
])("should pick the first provider of %d success(es) after %d failure(s)", async (successes, failures) => {
|
||||
runInAction(() => {
|
||||
for (let i = 0; i < failures; i += 1) {
|
||||
di.register(getInjectable({
|
||||
id: `test-prometheus-provider-failure-${i}`,
|
||||
injectionToken: prometheusProviderInjectionToken,
|
||||
instantiate: () => createTestPrometheusProvider(`id_failure_${i}`, ServiceResult.Failure),
|
||||
}));
|
||||
}
|
||||
for (let i = 0; i < successes; i += 1) {
|
||||
di.register(getInjectable({
|
||||
id: `test-prometheus-provider-success-${i}`,
|
||||
injectionToken: prometheusProviderInjectionToken,
|
||||
instantiate: () => createTestPrometheusProvider(`id_success_${i}`, `TestService-${i+10}`, ServiceResult.Success),
|
||||
}));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
for (let i = 0; i < successes; i += 1) {
|
||||
di.register(getInjectable({
|
||||
id: `test-prometheus-provider-success-${i}`,
|
||||
injectionToken: prometheusProviderInjectionToken,
|
||||
instantiate: () => createTestPrometheusProvider(`id_success_${i}`, ServiceResult.Success),
|
||||
}));
|
||||
}
|
||||
});
|
||||
describe("when getting prometheus details", () => {
|
||||
let details: Promise<AsyncResult<PrometheusDetails, Error>>;
|
||||
|
||||
const details = await createContextHandler(clusterStub).getPrometheusDetails();
|
||||
beforeEach(() => {
|
||||
details = createContextHandler(cluster).getPrometheusDetails();
|
||||
});
|
||||
|
||||
expect(details.provider.kind === `id_failure_${failures}`);
|
||||
});
|
||||
it("should read the proxy config file", () => {
|
||||
expect(readFileMock).toBeCalledWith("/some/proxy-kubeconfig");
|
||||
});
|
||||
|
||||
it.each([
|
||||
[1, 0],
|
||||
[1, 1],
|
||||
[1, 2],
|
||||
[1, 3],
|
||||
[2, 0],
|
||||
[2, 1],
|
||||
[2, 2],
|
||||
[2, 3],
|
||||
])("should pick the first provider of %d success(es) before %d failure(s)", async (successes, failures) => {
|
||||
runInAction(() => {
|
||||
for (let i = 0; i < failures; i += 1) {
|
||||
di.register(getInjectable({
|
||||
id: `test-prometheus-provider-failure-${i}`,
|
||||
injectionToken: prometheusProviderInjectionToken,
|
||||
instantiate: () => createTestPrometheusProvider(`id_failure_${i}`, ServiceResult.Failure),
|
||||
}));
|
||||
}
|
||||
describe("when reading the proxy config file resolves", () => {
|
||||
beforeEach(async () => {
|
||||
await readFileMock.resolveSpecific(
|
||||
["/some/proxy-kubeconfig"],
|
||||
proxyConfigValue,
|
||||
);
|
||||
});
|
||||
|
||||
for (let i = 0; i < successes; i += 1) {
|
||||
di.register(getInjectable({
|
||||
id: `test-prometheus-provider-success-${i}`,
|
||||
injectionToken: prometheusProviderInjectionToken,
|
||||
instantiate: () => createTestPrometheusProvider(`id_success_${i}`, ServiceResult.Success),
|
||||
}));
|
||||
}
|
||||
});
|
||||
it("should resolve with the first success provider", async () => {
|
||||
const result = await details;
|
||||
|
||||
const details = await createContextHandler(clusterStub).getPrometheusDetails();
|
||||
assert(result.callWasSuccessful);
|
||||
|
||||
expect(details.provider.kind === "id_failure_0");
|
||||
});
|
||||
expect(result.response).toMatchObject({
|
||||
provider: {
|
||||
kind: `id_success_0`,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
it.each([
|
||||
[1, 0],
|
||||
[1, 1],
|
||||
[1, 2],
|
||||
[1, 3],
|
||||
[2, 0],
|
||||
[2, 1],
|
||||
[2, 2],
|
||||
[2, 3],
|
||||
])("should pick the first provider of %d success(es) between %d failure(s)", async (successes, failures) => {
|
||||
const beforeSuccesses = Math.floor(successes / 2);
|
||||
for (let successes = 1; successes < 3; successes += 1) {
|
||||
for (let failures = 0; failures < 4; failures += 1) {
|
||||
const beforeSuccesses = Math.floor(successes / 2);
|
||||
|
||||
runInAction(() => {
|
||||
for (let i = 0; i < beforeSuccesses; i += 1) {
|
||||
di.register(getInjectable({
|
||||
id: `test-prometheus-provider-success-${i}`,
|
||||
injectionToken: prometheusProviderInjectionToken,
|
||||
instantiate: () => createTestPrometheusProvider(`id_success_${i}`, ServiceResult.Success),
|
||||
}));
|
||||
}
|
||||
describe(`with ${successes} successful providers between by ${failures} erroring providers`, () => {
|
||||
beforeEach(() => {
|
||||
runInAction(() => {
|
||||
for (let i = 0; i < beforeSuccesses; i += 1) {
|
||||
di.register(getInjectable({
|
||||
id: `test-prometheus-provider-success-${i}`,
|
||||
injectionToken: prometheusProviderInjectionToken,
|
||||
instantiate: () => createTestPrometheusProvider(`id_success_${i}`, `TestService-${i}`, ServiceResult.Success),
|
||||
}));
|
||||
}
|
||||
|
||||
for (let i = 0; i < failures; i += 1) {
|
||||
di.register(getInjectable({
|
||||
id: `test-prometheus-provider-failure-${i}`,
|
||||
injectionToken: prometheusProviderInjectionToken,
|
||||
instantiate: () => createTestPrometheusProvider(`id_failure_${i}`, ServiceResult.Failure),
|
||||
}));
|
||||
}
|
||||
for (let i = 0; i < failures; i += 1) {
|
||||
di.register(getInjectable({
|
||||
id: `test-prometheus-provider-failure-${i}`,
|
||||
injectionToken: prometheusProviderInjectionToken,
|
||||
instantiate: () => createTestPrometheusProvider(`id_failure_${i}`, `TestService-${i+10}`, ServiceResult.Failure),
|
||||
}));
|
||||
}
|
||||
|
||||
for (let i = beforeSuccesses; i < successes; i += 1) {
|
||||
di.register(getInjectable({
|
||||
id: `test-prometheus-provider-success-${i}`,
|
||||
injectionToken: prometheusProviderInjectionToken,
|
||||
instantiate: () => createTestPrometheusProvider(`id_success_${i}`, ServiceResult.Success),
|
||||
}));
|
||||
}
|
||||
});
|
||||
for (let i = beforeSuccesses; i < successes; i += 1) {
|
||||
di.register(getInjectable({
|
||||
id: `test-prometheus-provider-success-${i}`,
|
||||
injectionToken: prometheusProviderInjectionToken,
|
||||
instantiate: () => createTestPrometheusProvider(`id_success_${i}`, `TestService-${i+20}`, ServiceResult.Success),
|
||||
}));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const details = await createContextHandler(clusterStub).getPrometheusDetails();
|
||||
describe("when getting prometheus details", () => {
|
||||
let details: Promise<AsyncResult<PrometheusDetails, Error>>;
|
||||
|
||||
expect(details.provider.kind === "id_success_0");
|
||||
});
|
||||
beforeEach(() => {
|
||||
details = createContextHandler(cluster).getPrometheusDetails();
|
||||
});
|
||||
|
||||
it("should read the proxy config file", () => {
|
||||
expect(readFileMock).toBeCalledWith("/some/proxy-kubeconfig");
|
||||
});
|
||||
|
||||
describe("when reading the proxy config file resolves", () => {
|
||||
beforeEach(async () => {
|
||||
await readFileMock.resolveSpecific(
|
||||
["/some/proxy-kubeconfig"],
|
||||
proxyConfigValue,
|
||||
);
|
||||
});
|
||||
|
||||
it("should resolve with the first success provider", async () => {
|
||||
const result = await details;
|
||||
|
||||
assert(result.callWasSuccessful);
|
||||
|
||||
expect(result.response).toMatchObject({
|
||||
provider: {
|
||||
kind: `id_success_0`,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -9,6 +9,7 @@ import type { Cluster } from "../../common/cluster/cluster";
|
||||
import type httpProxy from "http-proxy";
|
||||
import type { UrlWithStringQuery } from "url";
|
||||
import url from "url";
|
||||
import type { KubeConfig } from "@kubernetes/client-node";
|
||||
import { CoreV1Api } from "@kubernetes/client-node";
|
||||
import type { KubeAuthProxy } from "../kube-auth-proxy/kube-auth-proxy";
|
||||
import type { CreateKubeAuthProxy } from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||
@ -16,6 +17,7 @@ import type { GetPrometheusProviderByKind } from "../prometheus/get-by-kind.inje
|
||||
import type { IComputedValue } from "mobx";
|
||||
import type { Logger } from "../../common/logger";
|
||||
import type { MakeApiClient } from "../../common/cluster/make-api-client.injectable";
|
||||
import type { AsyncResult } from "../../common/utils/async-result";
|
||||
|
||||
export interface PrometheusDetails {
|
||||
prometheusPath: string;
|
||||
@ -41,7 +43,7 @@ export interface ContextHandlerDependencies {
|
||||
export interface ClusterContextHandler {
|
||||
readonly clusterUrl: UrlWithStringQuery;
|
||||
setupPrometheus(preferences?: ClusterPrometheusPreferences): void;
|
||||
getPrometheusDetails(): Promise<PrometheusDetails>;
|
||||
getPrometheusDetails(): Promise<AsyncResult<PrometheusDetails, Error>>;
|
||||
resolveAuthProxyUrl(): Promise<string>;
|
||||
resolveAuthProxyCa(): string;
|
||||
getApiTarget(isLongRunningRequest?: boolean): Promise<httpProxy.ServerOptions>;
|
||||
@ -50,6 +52,8 @@ export interface ClusterContextHandler {
|
||||
stopServer(): void;
|
||||
}
|
||||
|
||||
const formatPrometheusPath = ({ service, namespace, port }: PrometheusService) => `${namespace}/services/${service}:${port}`;
|
||||
|
||||
export class ContextHandler implements ClusterContextHandler {
|
||||
public readonly clusterUrl: UrlWithStringQuery;
|
||||
protected kubeAuthProxy?: KubeAuthProxy;
|
||||
@ -67,16 +71,20 @@ export class ContextHandler implements ClusterContextHandler {
|
||||
this.prometheus = preferences.prometheus;
|
||||
}
|
||||
|
||||
public async getPrometheusDetails(): Promise<PrometheusDetails> {
|
||||
const service = await this.getPrometheusService();
|
||||
const prometheusPath = this.ensurePrometheusPath(service);
|
||||
const provider = this.ensurePrometheusProvider(service);
|
||||
public async getPrometheusDetails(): Promise<AsyncResult<PrometheusDetails, Error>> {
|
||||
const result = await this.getPrometheusService();
|
||||
|
||||
return { prometheusPath, provider };
|
||||
}
|
||||
if (!result.callWasSuccessful) {
|
||||
return result;
|
||||
}
|
||||
|
||||
protected ensurePrometheusPath({ service, namespace, port }: PrometheusService): string {
|
||||
return `${namespace}/services/${service}:${port}`;
|
||||
const prometheusPath = formatPrometheusPath(result.response);
|
||||
const provider = this.ensurePrometheusProvider(result.response);
|
||||
|
||||
return {
|
||||
callWasSuccessful: true,
|
||||
response: { prometheusPath, provider },
|
||||
};
|
||||
}
|
||||
|
||||
protected ensurePrometheusProvider(service: PrometheusService): PrometheusProvider {
|
||||
@ -98,21 +106,45 @@ export class ContextHandler implements ClusterContextHandler {
|
||||
return this.dependencies.prometheusProviders.get();
|
||||
}
|
||||
|
||||
protected async getPrometheusService(): Promise<PrometheusService> {
|
||||
private async getProxyKubeconfig(): Promise<AsyncResult<KubeConfig, Error>> {
|
||||
try {
|
||||
const proxyConfig = await this.cluster.getProxyKubeconfig();
|
||||
|
||||
return {
|
||||
callWasSuccessful: true,
|
||||
response: proxyConfig,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
callWasSuccessful: false,
|
||||
error: error as Error,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected async getPrometheusService(): Promise<AsyncResult<PrometheusService, Error>> {
|
||||
this.setupPrometheus(this.cluster.preferences);
|
||||
|
||||
if (this.prometheus && this.prometheusProvider) {
|
||||
return {
|
||||
kind: this.prometheusProvider,
|
||||
namespace: this.prometheus.namespace,
|
||||
service: this.prometheus.service,
|
||||
port: this.prometheus.port,
|
||||
callWasSuccessful: true,
|
||||
response: {
|
||||
kind: this.prometheusProvider,
|
||||
namespace: this.prometheus.namespace,
|
||||
service: this.prometheus.service,
|
||||
port: this.prometheus.port,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const providers = this.listPotentialProviders();
|
||||
const proxyConfig = await this.cluster.getProxyKubeconfig();
|
||||
const apiClient = this.dependencies.makeApiClient(proxyConfig, CoreV1Api);
|
||||
const proxyConfigResult = await this.getProxyKubeconfig();
|
||||
|
||||
if (!proxyConfigResult.callWasSuccessful) {
|
||||
return proxyConfigResult;
|
||||
}
|
||||
|
||||
const apiClient = this.dependencies.makeApiClient(proxyConfigResult.response, CoreV1Api);
|
||||
const potentialServices = await Promise.allSettled(
|
||||
providers.map(provider => provider.getPrometheusService(apiClient)),
|
||||
);
|
||||
@ -126,12 +158,18 @@ export class ContextHandler implements ClusterContextHandler {
|
||||
|
||||
case "fulfilled":
|
||||
if (res.value) {
|
||||
return res.value;
|
||||
return {
|
||||
callWasSuccessful: true,
|
||||
response: res.value,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("No Prometheus service found", { cause: errors });
|
||||
return {
|
||||
callWasSuccessful: false,
|
||||
error: new Error("No Prometheus service found", { cause: errors }),
|
||||
};
|
||||
}
|
||||
|
||||
async resolveAuthProxyUrl(): Promise<string> {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user