Add behavioural tests for Cluster Menu K8s Resources in Sidebar menu not being shown (#7280)

* Add behavioural tests to cover bug fix

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove previous fix to fix last test

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* More consistent impl of flushPromises

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fixup tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove ContextHandler test (dead code)

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix PrometheusHandler describe text

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix type errors

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add useful case test-utils helper

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Rename file to match token

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Cleanup tests to fix type errors and use tables

Signed-off-by: Sebastian Malton <sebastian@malton.name>

---------

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton authored and GitHub committed 2023-03-21 22:00:12 -04:00
1 parent 5409324236
commit 8a80607d85
32 files changed
+1104 -774

No files matched your search

@@ -15,8 +15,11 @@ export interface ClusterData {
readonly id: string;
}
export type RequestApiVersions = (cluster: ClusterData) => AsyncResult<KubeResourceListGroup[], Error>;
export interface ApiVersionsRequester {
request(cluster: ClusterData): AsyncResult<KubeResourceListGroup[], Error>;
readonly orderNumber: number;
}
export const requestApiVersionsInjectionToken = getInjectionToken<RequestApiVersions>({
export const apiVersionsRequesterInjectionToken = getInjectionToken<ApiVersionsRequester>({
id: "request-api-versions-token",
});
@@ -6,10 +6,7 @@
import { type KubeConfig, HttpError } from "@kubernetes/client-node";
import { reaction, comparer, runInAction } from "mobx";
import { ClusterStatus } from "../../common/cluster-types";
import type { CreateAuthorizationReview } from "../../common/cluster/authorization-review.injectable";
import type { Cluster } from "../../common/cluster/cluster";
import type { CreateListNamespaces } from "../../common/cluster/list-namespaces.injectable";
import type { RequestNamespaceListPermissionsFor, RequestNamespaceListPermissions } from "../../common/cluster/request-namespace-list-permissions.injectable";
import type { BroadcastMessage } from "../../common/ipc/broadcast-message.injectable";
import { clusterListNamespaceForbiddenChannel } from "../../common/ipc/cluster";
import type { Logger } from "../../common/logger";
@@ -25,7 +22,6 @@ import type { RequestApiResources } from "./request-api-resources.injectable";
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import broadcastConnectionUpdateInjectable from "./broadcast-connection-update.injectable";
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
import createAuthorizationReviewInjectable from "../../common/cluster/authorization-review.injectable";
import createListNamespacesInjectable from "../../common/cluster/list-namespaces.injectable";
import kubeAuthProxyServerInjectable from "./kube-auth-proxy-server.injectable";
import loadProxyKubeconfigInjectable from "./load-proxy-kubeconfig.injectable";
@@ -33,21 +29,31 @@ import loggerInjectable from "../../common/logger.injectable";
import prometheusHandlerInjectable from "./prometheus-handler/prometheus-handler.injectable";
import removeProxyKubeconfigInjectable from "./remove-proxy-kubeconfig.injectable";
import requestApiResourcesInjectable from "./request-api-resources.injectable";
import requestNamespaceListPermissionsForInjectable from "../../common/cluster/request-namespace-list-permissions.injectable";
import type { DetectClusterMetadata } from "../cluster-detectors/detect-cluster-metadata.injectable";
import type { FallibleOnlyClusterMetadataDetector } from "../cluster-detectors/token";
import clusterVersionDetectorInjectable from "../cluster-detectors/cluster-version-detector.injectable";
import detectClusterMetadataInjectable from "../cluster-detectors/detect-cluster-metadata.injectable";
import { replaceObservableObject } from "../../common/utils/replace-observable-object";
import type { CreateAuthorizationApi } from "../../common/cluster/create-authorization-api.injectable";
import type { CreateCanI } from "../../common/cluster/create-can-i.injectable";
import type { CreateRequestNamespaceListPermissions, RequestNamespaceListPermissions } from "../../common/cluster/create-request-namespace-list-permissions.injectable";
import type { Cluster } from "../../common/cluster/cluster";
import createAuthorizationApiInjectable from "../../common/cluster/create-authorization-api.injectable";
import createCanIInjectable from "../../common/cluster/create-can-i.injectable";
import createRequestNamespaceListPermissionsInjectable from "../../common/cluster/create-request-namespace-list-permissions.injectable";
import type { CreateCoreApi } from "../../common/cluster/create-core-api.injectable";
import createCoreApiInjectable from "../../common/cluster/create-core-api.injectable";
interface Dependencies {
readonly logger: Logger;
readonly prometheusHandler: ClusterPrometheusHandler;
readonly kubeAuthProxyServer: KubeAuthProxyServer;
readonly clusterVersionDetector: FallibleOnlyClusterMetadataDetector;
createAuthorizationReview: CreateAuthorizationReview;
createCanI: CreateCanI;
requestApiResources: RequestApiResources;
requestNamespaceListPermissionsFor: RequestNamespaceListPermissionsFor;
createRequestNamespaceListPermissions: CreateRequestNamespaceListPermissions;
createAuthorizationApi: CreateAuthorizationApi;
createCoreApi: CreateCoreApi;
createListNamespaces: CreateListNamespaces;
detectClusterMetadata: DetectClusterMetadata;
broadcastMessage: BroadcastMessage;
@@ -224,8 +230,9 @@ class ClusterConnection {
private async refreshAccessibility(): Promise<void> {
this.dependencies.logger.info(`[CLUSTER]: refreshAccessibility`, this.cluster.getMeta());
const proxyConfig = await this.dependencies.loadProxyKubeconfig();
const canI = this.dependencies.createAuthorizationReview(proxyConfig);
const requestNamespaceListPermissions = this.dependencies.requestNamespaceListPermissionsFor(proxyConfig);
const api = this.dependencies.createAuthorizationApi(proxyConfig);
const canI = this.dependencies.createCanI(api);
const requestNamespaceListPermissions = this.dependencies.createRequestNamespaceListPermissions(api);
const isAdmin = await canI({
namespace: "kube-system",
@@ -360,7 +367,8 @@ class ClusterConnection {
}
try {
const listNamespaces = this.dependencies.createListNamespaces(proxyConfig);
const api = this.dependencies.createCoreApi(proxyConfig);
const listNamespaces = this.dependencies.createListNamespaces(api);
return await listNamespaces();
} catch (error) {
@@ -403,13 +411,15 @@ const clusterConnectionInjectable = getInjectable({
prometheusHandler: di.inject(prometheusHandlerInjectable, cluster),
broadcastConnectionUpdate: di.inject(broadcastConnectionUpdateInjectable, cluster),
broadcastMessage: di.inject(broadcastMessageInjectable),
createAuthorizationReview: di.inject(createAuthorizationReviewInjectable),
createListNamespaces: di.inject(createListNamespacesInjectable),
detectClusterMetadata: di.inject(detectClusterMetadataInjectable),
loadProxyKubeconfig: di.inject(loadProxyKubeconfigInjectable, cluster),
removeProxyKubeconfig: di.inject(removeProxyKubeconfigInjectable, cluster),
requestApiResources: di.inject(requestApiResourcesInjectable),
requestNamespaceListPermissionsFor: di.inject(requestNamespaceListPermissionsForInjectable),
createAuthorizationApi: di.inject(createAuthorizationApiInjectable),
createCoreApi: di.inject(createCoreApiInjectable),
createCanI: di.inject(createCanIInjectable),
createRequestNamespaceListPermissions: di.inject(createRequestNamespaceListPermissionsInjectable),
},
cluster,
),
@@ -8,7 +8,7 @@ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import type { Cluster } from "../../common/cluster/cluster";
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
import kubeAuthProxyCertificateInjectable from "../kube-auth-proxy/kube-auth-proxy-certificate.injectable";
import type { KubeAuthProxy } from "../kube-auth-proxy/kube-auth-proxy";
import type { KubeAuthProxy } from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
export interface KubeAuthProxyServer {
getApiTarget(isLongRunningRequest?: boolean): Promise<ServerOptions>;
@@ -7,11 +7,12 @@ import { getInjectable } from "@ogre-tools/injectable";
import loggerInjectable from "../../common/logger.injectable";
import type { KubeApiResource } from "../../common/rbac";
import type { Cluster } from "../../common/cluster/cluster";
import { requestApiVersionsInjectionToken } from "./request-api-versions";
import { apiVersionsRequesterInjectionToken } from "./api-versions-requester";
import { backoffCaller, withConcurrencyLimit } from "@k8slens/utilities";
import requestKubeApiResourcesForInjectable from "./request-kube-api-resources-for.injectable";
import type { AsyncResult } from "@k8slens/utilities";
import broadcastConnectionUpdateInjectable from "./broadcast-connection-update.injectable";
import { byOrderNumber } from "../../common/utils/composable-responsibilities/orderable/orderable";
export type RequestApiResources = (cluster: Cluster) => AsyncResult<KubeApiResource[], Error>;
@@ -24,7 +25,8 @@ const requestApiResourcesInjectable = getInjectable({
id: "request-api-resources",
instantiate: (di): RequestApiResources => {
const logger = di.inject(loggerInjectable);
const apiVersionRequesters = di.injectMany(requestApiVersionsInjectionToken);
const apiVersionRequesters = di.injectMany(apiVersionsRequesterInjectionToken)
.sort(byOrderNumber);
const requestKubeApiResourcesFor = di.inject(requestKubeApiResourcesForInjectable);
return async (...args) => {
@@ -35,7 +37,7 @@ const requestApiResourcesInjectable = getInjectable({
const groupLists: KubeResourceListGroup[] = [];
for (const apiVersionRequester of apiVersionRequesters) {
const result = await backoffCaller(() => apiVersionRequester(cluster), {
const result = await backoffCaller(() => apiVersionRequester.request(cluster), {
onIntermediateError: (error, attempt) => {
broadcastConnectionUpdate({
message: `Failed to list kube API resource kinds, attempt ${attempt}: ${error}`,
@@ -5,33 +5,36 @@
import type { V1APIVersions } from "@kubernetes/client-node";
import { getInjectable } from "@ogre-tools/injectable";
import k8sRequestInjectable from "../k8s-request.injectable";
import { requestApiVersionsInjectionToken } from "./request-api-versions";
import { apiVersionsRequesterInjectionToken } from "./api-versions-requester";
const requestCoreApiVersionsInjectable = getInjectable({
id: "request-core-api-versions",
instantiate: (di) => {
const k8sRequest = di.inject(k8sRequestInjectable);
return async (cluster) => {
try {
const { versions } = await k8sRequest(cluster, "/api") as V1APIVersions;
return {
request: async (cluster) => {
try {
const { versions } = await k8sRequest(cluster, "/api") as V1APIVersions;
return {
callWasSuccessful: true,
response: versions.map(version => ({
group: "",
path: `/api/${version}`,
})),
};
} catch (error) {
return {
callWasSuccessful: false,
error: error as Error,
};
}
return {
callWasSuccessful: true,
response: versions.map(version => ({
group: "",
path: `/api/${version}`,
})),
};
} catch (error) {
return {
callWasSuccessful: false,
error: error as Error,
};
}
},
orderNumber: 10,
};
},
injectionToken: requestApiVersionsInjectionToken,
injectionToken: apiVersionsRequesterInjectionToken,
});
export default requestCoreApiVersionsInjectable;
@@ -8,7 +8,7 @@ import type { Cluster } from "../../common/cluster/cluster";
import type { KubeApiResource } from "../../common/rbac";
import type { AsyncResult } from "@k8slens/utilities";
import k8sRequestInjectable from "../k8s-request.injectable";
import type { KubeResourceListGroup } from "./request-api-versions";
import type { KubeResourceListGroup } from "./api-versions-requester";
export type RequestKubeApiResources = (grouping: KubeResourceListGroup) => AsyncResult<KubeApiResource[], Error>;
@@ -6,35 +6,40 @@ import type { V1APIGroupList } from "@kubernetes/client-node";
import { getInjectable } from "@ogre-tools/injectable";
import { iter } from "@k8slens/utilities";
import k8sRequestInjectable from "../k8s-request.injectable";
import { requestApiVersionsInjectionToken } from "./request-api-versions";
import { apiVersionsRequesterInjectionToken } from "./api-versions-requester";
const requestNonCoreApiVersionsInjectable = getInjectable({
id: "request-non-core-api-versions",
instantiate: (di) => {
const k8sRequest = di.inject(k8sRequestInjectable);
return async (cluster) => {
try {
const { groups } = await k8sRequest(cluster, "/apis") as V1APIGroupList;
return {
request: async (cluster) => {
try {
const { groups } = (await k8sRequest(cluster, "/apis")) as V1APIGroupList;
return {
callWasSuccessful: true,
response: iter.chain(groups.values())
.flatMap(group => group.versions.map(version => ({
group: group.name,
path: `/apis/${version.groupVersion}`,
})))
.collect(v => [...v]),
};
} catch (error) {
return {
callWasSuccessful: false,
error: error as Error,
};
}
return {
callWasSuccessful: true,
response: iter.chain(groups.values())
.flatMap((group) =>
group.versions.map((version) => ({
group: group.name,
path: `/apis/${version.groupVersion}`,
})),
)
.collect((v) => [...v]),
};
} catch (error) {
return {
callWasSuccessful: false,
error: error as Error,
};
}
},
orderNumber: 20,
};
},
injectionToken: requestApiVersionsInjectionToken,
injectionToken: apiVersionsRequesterInjectionToken,
});
export default requestNonCoreApiVersionsInjectable;
@@ -10,13 +10,13 @@ import type { DiContainer } from "@ogre-tools/injectable";
import { getDiForUnitTesting } from "../getDiForUnitTesting";
import type { K8sRequest } from "../k8s-request.injectable";
import k8sRequestInjectable from "../k8s-request.injectable";
import type { RequestApiVersions } from "./request-api-versions";
import type { ApiVersionsRequester } from "./api-versions-requester";
import requestNonCoreApiVersionsInjectable from "./request-non-core-api-versions.injectable";
describe("requestNonCoreApiVersions", () => {
let di: DiContainer;
let k8sRequestMock: AsyncFnMock<K8sRequest>;
let requestNonCoreApiVersions: RequestApiVersions;
let requestNonCoreApiVersions: ApiVersionsRequester;
beforeEach(() => {
di = getDiForUnitTesting();
@@ -28,10 +28,10 @@ describe("requestNonCoreApiVersions", () => {
});
describe("when called", () => {
let versionsRequest: ReturnType<RequestApiVersions>;
let versionsRequest: ReturnType<ApiVersionsRequester["request"]>;
beforeEach(() => {
versionsRequest = requestNonCoreApiVersions({ id: "some-cluster-id" });
versionsRequest = requestNonCoreApiVersions.request({ id: "some-cluster-id" });
});
it("should request all api groups", () => {