1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/packages/core/src/features/cluster/refresh-accessibility-technical.test.ts
Sebastian Malton 49f0a1af9c
Remove BaseStore<T> and replace with composition of dependencies (#7002)
* Rename ExtensionStore -> BaseExtensionStore
- The name was too close to ExtensionsStore

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

* Move ExtensionsStore to new format

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

* Move ClusterStore to new format

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

* Move UserStore to new format

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

* Cleanup types to remove multiple cast locations

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

* Move HotbarStore to new format

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

* Move WeblinkStore to new format

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

* Move FileSystemProvisionerStore to new format

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

* Update snapshots

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

* Clean up impl and rename to better describe intent

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

* Fix remaining type errors

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

* Fully split apart the enabled extensions storage

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

* Fully split apart the clusters storage

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

* Fully split apart the hotbar storage

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

* Fully split apart the weblinks storage

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

* Fully split apart the user preferences storage

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

* Fix crashing

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

* Fix tests and snapshots

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

* Fix integration test failures

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

* Improve typing to prevent errors in the future.

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

* Cleanup @k8slens/messaging and friends

- To fix type errors

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

* Fix lint issue

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

* Fix type errors

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

* Fix global override not being complete enough causing tests to fail

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

* Bump memory for unit tests on CI

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

* Attempt to fix memory issue on CI again

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

* Fixup test because of new injectables

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

* Upgrade Jest

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

* Fix unit tests falling over

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

* Back out jest config change

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

* Remove console log

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

* Update snapshot

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

* Fix tests by matching equality instead of snapshots

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

* Fix tests by forcing specific snapshot style

- Ubuntu CI seems to format arrays in snapshots differently than macOS locally

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

---------

Signed-off-by: Sebastian Malton <sebastian@malton.name>
2023-03-28 17:54:25 +03:00

636 lines
23 KiB
TypeScript

/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import type { AuthorizationV1Api, CoreV1Api, V1APIGroupList, V1APIVersions, V1NamespaceList, V1SelfSubjectAccessReview, V1SelfSubjectRulesReview } from "@kubernetes/client-node";
import type { Cluster } from "../../common/cluster/cluster";
import createAuthorizationApiInjectable from "../../common/cluster/create-authorization-api.injectable";
import writeJsonFileInjectable from "../../common/fs/write-json-file.injectable";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
import type { PartialDeep } from "type-fest";
import { anyObject } from "jest-mock-extended";
import createCoreApiInjectable from "../../common/cluster/create-core-api.injectable";
import type { K8sRequest } from "../../main/k8s-request.injectable";
import k8sRequestInjectable from "../../main/k8s-request.injectable";
import type { DetectClusterMetadata } from "../../main/cluster-detectors/detect-cluster-metadata.injectable";
import detectClusterMetadataInjectable from "../../main/cluster-detectors/detect-cluster-metadata.injectable";
import type { ClusterConnection } from "../../main/cluster/cluster-connection.injectable";
import clusterConnectionInjectable from "../../main/cluster/cluster-connection.injectable";
import type { KubeAuthProxy } from "../../main/kube-auth-proxy/create-kube-auth-proxy.injectable";
import createKubeAuthProxyInjectable from "../../main/kube-auth-proxy/create-kube-auth-proxy.injectable";
import type { Mocked } from "../../test-utils/mock-interface";
import { flushPromises } from "@k8slens/test-utils";
import addClusterInjectable from "./storage/common/add.injectable";
describe("Refresh Cluster Accessibility Technical Tests", () => {
let builder: ApplicationBuilder;
let createSelfSubjectRulesReviewMock: AsyncFnMock<AuthorizationV1Api["createSelfSubjectRulesReview"]>;
let createSelfSubjectAccessReviewMock: AsyncFnMock<AuthorizationV1Api["createSelfSubjectAccessReview"]>;
let listNamespaceMock: AsyncFnMock<CoreV1Api["listNamespace"]>;
let k8sRequestMock: AsyncFnMock<K8sRequest>;
let detectClusterMetadataMock: AsyncFnMock<DetectClusterMetadata>;
let kubeAuthProxyMock: Mocked<KubeAuthProxy>;
beforeEach(async () => {
builder = getApplicationBuilder();
const mainDi = builder.mainDi;
mainDi.override(broadcastMessageInjectable, () => async () => {});
kubeAuthProxyMock = {
apiPrefix: "/some-api-prefix",
port: 0,
exit: jest.fn(),
run: asyncFn(),
};
mainDi.override(createKubeAuthProxyInjectable, () => () => kubeAuthProxyMock);
detectClusterMetadataMock = asyncFn();
mainDi.override(detectClusterMetadataInjectable, () => detectClusterMetadataMock);
k8sRequestMock = asyncFn();
mainDi.override(k8sRequestInjectable, () => k8sRequestMock);
createSelfSubjectRulesReviewMock = asyncFn();
createSelfSubjectAccessReviewMock = asyncFn();
mainDi.override(createAuthorizationApiInjectable, () => () => ({
createSelfSubjectRulesReview: createSelfSubjectRulesReviewMock,
createSelfSubjectAccessReview: createSelfSubjectAccessReviewMock,
} as any));
listNamespaceMock = asyncFn();
mainDi.override(createCoreApiInjectable, () => () => ({
listNamespace: listNamespaceMock,
} as any));
await builder.render();
});
describe("given a cluster with no configured preferences", () => {
let cluster: Cluster;
let clusterConnection: ClusterConnection;
let refreshPromise: Promise<void>;
beforeEach(async () => {
const mainDi = builder.mainDi;
const addCluster = mainDi.inject(addClusterInjectable);
const writeJsonFile = mainDi.inject(writeJsonFileInjectable);
await writeJsonFile("/some-kube-config-path", {
apiVersion: "v1",
kind: "Config",
clusters: [{
name: "some-cluster-name",
cluster: {
server: "https://localhost:8989",
},
}],
users: [{
name: "some-user-name",
}],
contexts: [{
name: "some-cluster-context",
context: {
user: "some-user-name",
cluster: "some-cluster-name",
},
}],
});
cluster = addCluster({
contextName: "some-cluster-context",
id: "some-cluster-id",
kubeConfigPath: "/some-kube-config-path",
});
clusterConnection = mainDi.inject(clusterConnectionInjectable, cluster);
refreshPromise = clusterConnection.refreshAccessibilityAndMetadata();
});
it("starts kubeAuthProxy", () => {
expect(kubeAuthProxyMock.run).toBeCalled();
});
describe("when kubeAuthProxy has started running and its port is found", () => {
beforeEach(async () => {
kubeAuthProxyMock.port = 1235;
await kubeAuthProxyMock.run.resolve();
await flushPromises();
});
it("requests if cluster has admin permissions", async () => {
expect(createSelfSubjectAccessReviewMock).toBeCalledWith(anyObject({
spec: {
namespace: "kube-system",
resource: "*",
verb: "create",
},
}));
});
describe.each([ true, false ])("when cluster admin request resolves to %p", (isAdmin) => {
beforeEach(async () => {
await createSelfSubjectAccessReviewMock.resolve({
body: {
status: {
allowed: isAdmin,
},
} as PartialDeep<V1SelfSubjectAccessReview>,
} as any);
});
it("requests if cluster has global watch permissions", () => {
expect(createSelfSubjectAccessReviewMock).toBeCalledWith(anyObject({
spec: {
verb: "watch",
resource: "*",
},
}));
});
describe.each([ true, false ])("when cluster global watch request resolves with %p", (globalWatch) => {
beforeEach(async () => {
await createSelfSubjectAccessReviewMock.resolve({
body: {
status: {
allowed: globalWatch,
},
} as PartialDeep<V1SelfSubjectAccessReview>,
} as any);
});
it("requests namespaces", () => {
expect(listNamespaceMock).toBeCalled();
});
describe("when list namespaces resolves", () => {
beforeEach(async () => {
await listNamespaceMock.resolve(listNamespaceResponse);
});
it("requests core api versions", () => {
expect(k8sRequestMock).toBeCalledWith(
anyObject({ id: "some-cluster-id" }),
"/api",
);
});
describe("when core api versions request resolves", () => {
beforeEach(async () => {
await k8sRequestMock.resolve({
serverAddressByClientCIDRs: [],
versions: [
"v1",
],
} as V1APIVersions);
});
it("requests non-core api resource kinds", () => {
expect(k8sRequestMock).toBeCalledWith(
anyObject({ id: "some-cluster-id" }),
"/apis",
);
});
describe("when non-core api resource kinds request resolves", () => {
beforeEach(async () => {
await k8sRequestMock.resolve(nonCoreApiResponse);
});
it("requests specific resource kinds in core", () => {
expect(k8sRequestMock).toBeCalledWith(
anyObject({ id: "some-cluster-id" }),
"/api/v1",
);
});
describe("when core specific resource kinds request resolves", () => {
beforeEach(async () => {
await k8sRequestMock.resolve(coreApiKindsResponse);
});
it("requests specific resources kinds from the first non-core response", () => {
expect(k8sRequestMock).toBeCalledWith(
anyObject({ id: "some-cluster-id" }),
"/apis/node.k8s.io/v1",
);
});
describe("when first specific resource kinds request resolves", () => {
beforeEach(async () => {
await k8sRequestMock.resolve(nodeK8sIoKindsResponse);
});
it("requests specific resources kinds from the second non-core response", () => {
expect(k8sRequestMock).toBeCalledWith(
anyObject({ id: "some-cluster-id" }),
"/apis/discovery.k8s.io/v1",
);
});
describe("when second specific resource kinds request resolves", () => {
beforeEach(async () => {
await k8sRequestMock.resolve(discoveryK8sIoKindsResponse);
});
it("requests namespace list permissions for 'default' namespace", () => {
expect(createSelfSubjectRulesReviewMock).toBeCalledWith(anyObject({
spec: {
namespace: "default",
},
}));
});
describe("when the permissions are incomplete", () => {
beforeEach(async () => {
await createSelfSubjectRulesReviewMock.resolve(defaultIncompletePermissions);
});
it("requests namespace list permissions for 'my-namespace' namespace", () => {
expect(createSelfSubjectRulesReviewMock).toBeCalledWith(anyObject({
spec: {
namespace: "my-namespace",
},
}));
});
describe("when the permissions request for 'my-namespace' resolves as empty", () => {
beforeEach(async () => {
await createSelfSubjectRulesReviewMock.resolve(emptyPermissions);
});
it("requests cluster metadata", () => {
expect(detectClusterMetadataMock).toBeCalledWith(anyObject({ id: "some-cluster-id" }));
});
describe("when cluster metadata request resolves", () => {
beforeEach(async () => {
await detectClusterMetadataMock.resolve({});
});
it("allows the call to refreshAccessibilityAndMetadata to resolve", async () => {
await refreshPromise;
});
it("should have the cluster displaying 'pods'", () => {
expect(cluster.resourcesToShow.has("pods")).toBe(true);
});
it("should have the cluster displaying 'namespaces'", () => {
expect(cluster.resourcesToShow.has("namespaces")).toBe(true);
});
});
});
describe.skip("when the permissions are incomplete", () => {});
describe.skip("when the permissions resolve to a single entry with 'list' verb", () => {});
describe.skip("when the permissions resolve to multiple entries with the 'list' verb not on the first entry", () => {});
});
describe("when the permissions resolve to an empty list", () => {
beforeEach(async () => {
await createSelfSubjectRulesReviewMock.resolve(emptyPermissions);
});
it("requests namespace list permissions for 'my-namespace' namespace", () => {
expect(createSelfSubjectRulesReviewMock).toBeCalledWith(anyObject({
spec: {
namespace: "my-namespace",
},
}));
});
describe("when the permissions request for 'my-namespace' resolves as empty", () => {
beforeEach(async () => {
await createSelfSubjectRulesReviewMock.resolve(emptyPermissions);
});
it("requests cluster metadata", () => {
expect(detectClusterMetadataMock).toBeCalledWith(anyObject({ id: "some-cluster-id" }));
});
describe("when cluster metadata request resolves", () => {
beforeEach(async () => {
await detectClusterMetadataMock.resolve({});
});
it("allows the call to refreshAccessibilityAndMetadata to resolve", async () => {
await refreshPromise;
});
it("should have the cluster displaying 'pods'", () => {
expect(cluster.resourcesToShow.has("pods")).toBe(false);
});
it("should have the cluster not displaying 'namespaces'", () => {
expect(cluster.resourcesToShow.has("namespaces")).toBe(false);
});
});
});
describe.skip("when the permissions are incomplete", () => {});
describe.skip("when the permissions resolve to a single entry with 'list' verb", () => {});
describe.skip("when the permissions resolve to multiple entries with the 'list' verb not on the first entry", () => {});
});
describe("when the permissions resolve to a single entry with 'list' verb", () => {
beforeEach(async () => {
await createSelfSubjectRulesReviewMock.resolve(defaultSingleListPermissions);
});
it("requests namespace list permissions for 'my-namespace' namespace", () => {
expect(createSelfSubjectRulesReviewMock).toBeCalledWith(anyObject({
spec: {
namespace: "my-namespace",
},
}));
});
describe("when the permissions request for 'my-namespace' resolves as empty", () => {
beforeEach(async () => {
await createSelfSubjectRulesReviewMock.resolve(emptyPermissions);
});
it("requests cluster metadata", () => {
expect(detectClusterMetadataMock).toBeCalledWith(anyObject({ id: "some-cluster-id" }));
});
describe("when cluster metadata request resolves", () => {
beforeEach(async () => {
await detectClusterMetadataMock.resolve({});
});
it("allows the call to refreshAccessibilityAndMetadata to resolve", async () => {
await refreshPromise;
});
it("should have the cluster displaying 'pods'", () => {
expect(cluster.resourcesToShow.has("pods")).toBe(true);
});
it("should have the cluster not displaying 'namespaces'", () => {
expect(cluster.resourcesToShow.has("namespaces")).toBe(false);
});
});
});
describe.skip("when the permissions are incomplete", () => {});
describe.skip("when the permissions resolve to a single entry with 'list' verb", () => {});
describe.skip("when the permissions resolve to multiple entries with the 'list' verb not on the first entry", () => {});
});
describe("when the permissions resolve to multiple entries with the 'list' verb not on the first entry", () => {
beforeEach(async () => {
await createSelfSubjectRulesReviewMock.resolve(defaultMultipleListPermissions);
});
it("requests namespace list permissions for 'my-namespace' namespace", () => {
expect(createSelfSubjectRulesReviewMock).toBeCalledWith(anyObject({
spec: {
namespace: "my-namespace",
},
}));
});
describe("when the permissions request for 'my-namespace' resolves as empty", () => {
beforeEach(async () => {
await createSelfSubjectRulesReviewMock.resolve(emptyPermissions);
});
it("requests cluster metadata", () => {
expect(detectClusterMetadataMock).toBeCalledWith(anyObject({ id: "some-cluster-id" }));
});
describe("when cluster metadata request resolves", () => {
beforeEach(async () => {
await detectClusterMetadataMock.resolve({});
});
it("allows the call to refreshAccessibilityAndMetadata to resolve", async () => {
await refreshPromise;
});
it("should have the cluster displaying 'pods'", () => {
expect(cluster.resourcesToShow.has("pods")).toBe(true);
});
it("should have the cluster not displaying 'namespaces'", () => {
expect(cluster.resourcesToShow.has("namespaces")).toBe(false);
});
});
});
describe.skip("when the permissions are incomplete", () => {});
describe.skip("when the permissions resolve to a single entry with 'list' verb", () => {});
describe.skip("when the permissions resolve to multiple entries with the 'list' verb not on the first entry", () => {});
});
});
describe.skip("when second specific resource kinds rejects", () => {});
});
});
describe.skip("when first specific resource kinds rejects", () => {});
});
});
});
});
});
});
});
});
const nonCoreApiResponse = {
groups: [
{
name: "node.k8s.io",
versions: [
{
groupVersion: "node.k8s.io/v1",
version: "v1",
},
],
preferredVersion: {
groupVersion: "node.k8s.io/v1",
version: "v1",
},
},
{
name: "discovery.k8s.io",
versions: [
{
groupVersion: "discovery.k8s.io/v1",
version: "v1",
},
],
preferredVersion: {
groupVersion: "discovery.k8s.io/v1",
version: "v1",
},
},
],
} as V1APIGroupList;
const listNamespaceResponse = {
body: {
items: [
{
metadata: {
name: "default",
},
},
{
metadata: {
name: "my-namespace",
},
},
],
} as PartialDeep<V1NamespaceList>,
} as Awaited<ReturnType<CoreV1Api["listNamespace"]>>;
const coreApiKindsResponse = {
kind: "APIResourceList",
groupVersion: "v1",
resources: [
{
name: "namespaces",
singularName: "",
namespaced: false,
kind: "Namespace",
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"],
shortNames: ["ns"],
storageVersionHash: "Q3oi5N2YM8M=",
},
{
name: "pods",
singularName: "",
namespaced: true,
kind: "Pod",
verbs: [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch",
],
shortNames: ["po"],
categories: ["all"],
storageVersionHash: "xPOwRZ+Yhw8=",
},
{
name: "pods/attach",
singularName: "",
namespaced: true,
kind: "PodAttachOptions",
verbs: ["create", "get"],
},
],
};
const nodeK8sIoKindsResponse = {
kind: "APIResourceList",
apiVersion: "v1",
groupVersion: "node.k8s.io/v1",
resources: [
{
name: "runtimeclasses",
singularName: "",
namespaced: false,
kind: "RuntimeClass",
verbs: [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch",
],
storageVersionHash: "WQTu1GL3T2Q=",
},
],
};
const discoveryK8sIoKindsResponse = {
kind: "APIResourceList",
apiVersion: "v1",
groupVersion: "discovery.k8s.io/v1",
resources: [
{
name: "endpointslices",
singularName: "",
namespaced: true,
kind: "EndpointSlice",
verbs: [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch",
],
storageVersionHash: "Nx3SIv6I0mE=",
},
],
};
type CreateSelfSubjectRulesReviewRes = Awaited<ReturnType<AuthorizationV1Api["createSelfSubjectRulesReview"]>>;
const defaultIncompletePermissions = {
body: {
status: {
incomplete: true,
},
} as PartialDeep<V1SelfSubjectRulesReview>,
} as CreateSelfSubjectRulesReviewRes;
const emptyPermissions = {
body: {
status: {
resourceRules: [],
},
} as PartialDeep<V1SelfSubjectRulesReview>,
} as CreateSelfSubjectRulesReviewRes;
const defaultSingleListPermissions = {
body: {
status: {
resourceRules: [{
apiGroups: [""],
resources: ["pods"],
verbs: ["list"],
}],
},
} as PartialDeep<V1SelfSubjectRulesReview>,
} as CreateSelfSubjectRulesReviewRes;
const defaultMultipleListPermissions = {
body: {
status: {
resourceRules: [
{
apiGroups: [""],
resources: ["pods"],
verbs: ["get"],
},
{
apiGroups: [""],
resources: ["pods"],
verbs: ["list"],
},
],
},
} as PartialDeep<V1SelfSubjectRulesReview>,
} as CreateSelfSubjectRulesReviewRes;