mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
* Add check for incomplete SelfSubjectRulesReview to fix GKE Signed-off-by: Sebastian Malton <sebastian@malton.name> * Adding namespaced for KubeApiResource Signed-off-by: Sebastian Malton <sebastian@malton.name> * Refactoring of AuthorizationNamespaceReview Signed-off-by: Sebastian Malton <sebastian@malton.name> * Removing dead code Signed-off-by: Sebastian Malton <sebastian@malton.name> * Refactoring ListApiResources Signed-off-by: Sebastian Malton <sebastian@malton.name> * Extract ClusterContext into deps for KubeObjectStore to fix circular import Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix remaining type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix crash in frame by consolidating setup into runnables Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix type errors and remove dead code Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix core resources not showing up Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix namespaces not being shown Signed-off-by: Sebastian Malton <sebastian@malton.name> * Simplify ClusterContext to remove something only NamespaceStore needs Signed-off-by: Sebastian Malton <sebastian@malton.name> * Make sure the public API doesn't change Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix lint Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fixing namespace-select-filter tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix other tests requiring overrides Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix kludge in cluster-frame tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix remaining test failures Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix integration test due to incorrect casting Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix integration test and kube watches not working at all Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix secret details test Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix lint Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix non-ApplicationBuilder tests by adding overrides Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix crash due to trying to read hostedCluster too soon Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix crash due to timing issues - Make injectable phases more explicit for renderer Signed-off-by: Sebastian Malton <sebastian@malton.name> Signed-off-by: Sebastian Malton <sebastian@malton.name>
1233 lines
37 KiB
TypeScript
1233 lines
37 KiB
TypeScript
/**
|
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
*/
|
|
import type { KubeApiWatchCallback } from "../kube-api";
|
|
import { KubeApi } from "../kube-api";
|
|
import type { KubeJsonApi, KubeJsonApiData } from "../kube-json-api";
|
|
import { PassThrough } from "stream";
|
|
import { Deployment, DeploymentApi, NamespaceApi, Pod, PodApi } from "../endpoints";
|
|
import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting";
|
|
import type { Fetch } from "../../fetch/fetch.injectable";
|
|
import fetchInjectable from "../../fetch/fetch.injectable";
|
|
import type { CreateKubeApiForRemoteCluster } from "../create-kube-api-for-remote-cluster.injectable";
|
|
import createKubeApiForRemoteClusterInjectable from "../create-kube-api-for-remote-cluster.injectable";
|
|
import type { AsyncFnMock } from "@async-fn/jest";
|
|
import asyncFn from "@async-fn/jest";
|
|
import { flushPromises } from "../../test-utils/flush-promises";
|
|
import createKubeJsonApiInjectable from "../create-kube-json-api.injectable";
|
|
import type { IKubeWatchEvent } from "../kube-watch-event";
|
|
import type { KubeJsonApiDataFor } from "../kube-object";
|
|
import AbortController from "abort-controller";
|
|
import setupAutoRegistrationInjectable from "../../../renderer/before-frame-starts/runnables/setup-auto-registration.injectable";
|
|
import { createMockResponseFromStream, createMockResponseFromString } from "../../../test-utils/mock-responses";
|
|
import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable";
|
|
import directoryForUserDataInjectable from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
|
import createClusterInjectable from "../../../main/create-cluster/create-cluster.injectable";
|
|
import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable";
|
|
import directoryForKubeConfigsInjectable from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
|
|
|
describe("createKubeApiForRemoteCluster", () => {
|
|
let createKubeApiForRemoteCluster: CreateKubeApiForRemoteCluster;
|
|
let fetchMock: AsyncFnMock<Fetch>;
|
|
|
|
beforeEach(async () => {
|
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
|
|
|
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
|
|
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
|
|
di.override(storesAndApisCanBeCreatedInjectable, () => true);
|
|
|
|
const createCluster = di.inject(createClusterInjectable);
|
|
|
|
di.override(hostedClusterInjectable, () => createCluster({
|
|
contextName: "some-context-name",
|
|
id: "some-cluster-id",
|
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
|
}, {
|
|
clusterServerUrl: "https://localhost:8080",
|
|
}));
|
|
|
|
fetchMock = asyncFn();
|
|
di.override(fetchInjectable, () => fetchMock);
|
|
|
|
createKubeApiForRemoteCluster = di.inject(createKubeApiForRemoteClusterInjectable);
|
|
});
|
|
|
|
it("builds api client for KubeObject", async () => {
|
|
const api = createKubeApiForRemoteCluster({
|
|
cluster: {
|
|
server: "https://127.0.0.1:6443",
|
|
},
|
|
user: {
|
|
token: "daa",
|
|
},
|
|
}, Pod);
|
|
|
|
expect(api).toBeInstanceOf(KubeApi);
|
|
});
|
|
|
|
describe("when building for remote cluster with specific constructor", () => {
|
|
let api: PodApi;
|
|
|
|
beforeEach(() => {
|
|
api = createKubeApiForRemoteCluster({
|
|
cluster: {
|
|
server: "https://127.0.0.1:6443",
|
|
},
|
|
user: {
|
|
token: "daa",
|
|
},
|
|
}, Pod, PodApi);
|
|
});
|
|
|
|
it("uses the constructor", () => {
|
|
expect(api).toBeInstanceOf(PodApi);
|
|
});
|
|
|
|
describe("when calling list without namespace", () => {
|
|
let listRequest: Promise<Pod[] | null>;
|
|
|
|
beforeEach(async () => {
|
|
listRequest = api.list();
|
|
|
|
// This is required because of how JS promises work
|
|
await flushPromises();
|
|
});
|
|
|
|
it("should request pods from default namespace", () => {
|
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
|
"https://127.0.0.1:6443/api/v1/pods",
|
|
{
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
method: "get",
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("when request resolves with data", () => {
|
|
beforeEach(async () => {
|
|
await fetchMock.resolveSpecific(
|
|
["https://127.0.0.1:6443/api/v1/pods"],
|
|
createMockResponseFromString("https://127.0.0.1:6443/api/v1/pods", JSON.stringify({
|
|
kind: "PodList",
|
|
apiVersion: "v1",
|
|
metadata:{
|
|
resourceVersion: "452899",
|
|
},
|
|
items: [],
|
|
})),
|
|
);
|
|
});
|
|
|
|
it("resolves the list call", async () => {
|
|
expect(await listRequest).toEqual([]);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("KubeApi", () => {
|
|
let request: KubeJsonApi;
|
|
let fetchMock: AsyncFnMock<Fetch>;
|
|
|
|
beforeEach(async () => {
|
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
|
|
|
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
|
|
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
|
|
di.override(storesAndApisCanBeCreatedInjectable, () => true);
|
|
|
|
const createCluster = di.inject(createClusterInjectable);
|
|
|
|
di.override(hostedClusterInjectable, () => createCluster({
|
|
contextName: "some-context-name",
|
|
id: "some-cluster-id",
|
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
|
}, {
|
|
clusterServerUrl: "https://localhost:8080",
|
|
}));
|
|
|
|
fetchMock = asyncFn();
|
|
di.override(fetchInjectable, () => fetchMock);
|
|
|
|
const createKubeJsonApi = di.inject(createKubeJsonApiInjectable);
|
|
|
|
request = createKubeJsonApi({
|
|
serverAddress: `http://127.0.0.1:9999`,
|
|
apiBase: "/api-kube",
|
|
});
|
|
|
|
const setupAutoRegistration = di.inject(setupAutoRegistrationInjectable);
|
|
|
|
setupAutoRegistration.run();
|
|
});
|
|
|
|
describe("patching deployments", () => {
|
|
let api: DeploymentApi;
|
|
|
|
beforeEach(() => {
|
|
api = new DeploymentApi({
|
|
request,
|
|
});
|
|
});
|
|
|
|
describe("when patching a resource without providing a strategy", () => {
|
|
let patchRequest: Promise<Deployment | null>;
|
|
|
|
beforeEach(async () => {
|
|
patchRequest = api.patch({ name: "test", namespace: "default" }, {
|
|
spec: { replicas: 2 },
|
|
});
|
|
|
|
// This is needed because of how JS promises work
|
|
await flushPromises();
|
|
});
|
|
|
|
it("requests a patch using strategic merge", () => {
|
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
|
"http://127.0.0.1:9999/api-kube/apis/apps/v1/namespaces/default/deployments/test",
|
|
{
|
|
headers: {
|
|
"content-type": "application/strategic-merge-patch+json",
|
|
},
|
|
method: "patch",
|
|
body: JSON.stringify({ spec: { replicas: 2 }}),
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("when the patch request resolves with data", () => {
|
|
beforeEach(async () => {
|
|
await fetchMock.resolveSpecific(
|
|
["http://127.0.0.1:9999/api-kube/apis/apps/v1/namespaces/default/deployments/test"],
|
|
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/apps/v1/namespaces/default/deployments/test", JSON.stringify({
|
|
apiVersion: "v1",
|
|
kind: "Deployment",
|
|
metadata: {
|
|
name: "test",
|
|
namespace: "default",
|
|
resourceVersion: "1",
|
|
uid: "12345",
|
|
},
|
|
spec: {
|
|
replicas: 2,
|
|
},
|
|
})),
|
|
);
|
|
});
|
|
|
|
it("resolves the patch call", async () => {
|
|
expect(await patchRequest).toBeInstanceOf(Deployment);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when patching a resource using json patch", () => {
|
|
let patchRequest: Promise<Deployment | null>;
|
|
|
|
beforeEach(async () => {
|
|
patchRequest = api.patch({ name: "test", namespace: "default" }, [
|
|
{ op: "replace", path: "/spec/replicas", value: 2 },
|
|
], "json");
|
|
|
|
// This is needed because of how JS promises work
|
|
await flushPromises();
|
|
});
|
|
|
|
it("requests a patch using json merge", () => {
|
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
|
"http://127.0.0.1:9999/api-kube/apis/apps/v1/namespaces/default/deployments/test",
|
|
{
|
|
headers: {
|
|
"content-type": "application/json-patch+json",
|
|
},
|
|
method: "patch",
|
|
body: JSON.stringify([
|
|
{ op: "replace", path: "/spec/replicas", value: 2 },
|
|
]),
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("when the patch request resolves with data", () => {
|
|
beforeEach(async () => {
|
|
await fetchMock.resolveSpecific(
|
|
["http://127.0.0.1:9999/api-kube/apis/apps/v1/namespaces/default/deployments/test"],
|
|
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/apps/v1/namespaces/default/deployments/test", JSON.stringify({
|
|
apiVersion: "v1",
|
|
kind: "Deployment",
|
|
metadata: {
|
|
name: "test",
|
|
namespace: "default",
|
|
resourceVersion: "1",
|
|
uid: "12345",
|
|
},
|
|
spec: {
|
|
replicas: 2,
|
|
},
|
|
})),
|
|
);
|
|
});
|
|
|
|
it("resolves the patch call", async () => {
|
|
expect(await patchRequest).toBeInstanceOf(Deployment);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when patching a resource using merge patch", () => {
|
|
let patchRequest: Promise<Deployment | null>;
|
|
|
|
beforeEach(async () => {
|
|
patchRequest = api.patch(
|
|
{ name: "test", namespace: "default" },
|
|
{ metadata: { annotations: { provisioned: "True" }}},
|
|
"merge",
|
|
);
|
|
|
|
// This is needed because of how JS promises work
|
|
await flushPromises();
|
|
});
|
|
|
|
it("requests a patch using json merge", () => {
|
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
|
"http://127.0.0.1:9999/api-kube/apis/apps/v1/namespaces/default/deployments/test",
|
|
{
|
|
headers: {
|
|
"content-type": "application/merge-patch+json",
|
|
},
|
|
method: "patch",
|
|
body: JSON.stringify({ metadata: { annotations: { provisioned: "True" }}}),
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("when the patch request resolves with data", () => {
|
|
beforeEach(async () => {
|
|
await fetchMock.resolveSpecific(
|
|
["http://127.0.0.1:9999/api-kube/apis/apps/v1/namespaces/default/deployments/test"],
|
|
createMockResponseFromString("http://127.0.0.1:9999/api-kube/apis/apps/v1/namespaces/default/deployments/test", JSON.stringify({
|
|
apiVersion: "v1",
|
|
kind: "Deployment",
|
|
metadata: {
|
|
name: "test",
|
|
namespace: "default",
|
|
resourceVersion: "1",
|
|
uid: "12345",
|
|
annotations: {
|
|
provisioned: "True",
|
|
},
|
|
},
|
|
})),
|
|
);
|
|
});
|
|
|
|
it("resolves the patch call", async () => {
|
|
expect(await patchRequest).toBeInstanceOf(Deployment);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("deleting pods (namespace scoped resource)", () => {
|
|
let api: PodApi;
|
|
|
|
beforeEach(() => {
|
|
api = new PodApi({
|
|
request,
|
|
});
|
|
});
|
|
|
|
describe("when deleting by just name", () => {
|
|
let deleteRequest: Promise<KubeJsonApiData>;
|
|
|
|
beforeEach(async () => {
|
|
deleteRequest = api.delete({ name: "foo" });
|
|
|
|
// This is required for how JS promises work
|
|
await flushPromises();
|
|
});
|
|
|
|
it("requests deleting pod in default namespace", () => {
|
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
|
"http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background",
|
|
{
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
method: "delete",
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("when request resolves", () => {
|
|
beforeEach(async () => {
|
|
fetchMock.resolveSpecific(
|
|
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background"],
|
|
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background", "{}"),
|
|
);
|
|
});
|
|
|
|
it("resolves the call", async () => {
|
|
expect(await deleteRequest).toBeDefined();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when deleting by name and empty namespace", () => {
|
|
let deleteRequest: Promise<KubeJsonApiData>;
|
|
|
|
beforeEach(async () => {
|
|
deleteRequest = api.delete({ name: "foo", namespace: "" });
|
|
|
|
// This is required for how JS promises work
|
|
await flushPromises();
|
|
});
|
|
|
|
it("requests deleting pod in default namespace", () => {
|
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
|
"http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background",
|
|
{
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
method: "delete",
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("when request resolves", () => {
|
|
beforeEach(async () => {
|
|
fetchMock.resolveSpecific(
|
|
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background"],
|
|
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background", "{}"),
|
|
);
|
|
});
|
|
|
|
it("resolves the call", async () => {
|
|
expect(await deleteRequest).toBeDefined();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when deleting by name and namespace", () => {
|
|
let deleteRequest: Promise<KubeJsonApiData>;
|
|
|
|
beforeEach(async () => {
|
|
deleteRequest = api.delete({ name: "foo", namespace: "test" });
|
|
|
|
// This is required for how JS promises work
|
|
await flushPromises();
|
|
});
|
|
|
|
it("requests deleting pod in given namespace", () => {
|
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
|
"http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo?propagationPolicy=Background",
|
|
{
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
method: "delete",
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("when request resolves", () => {
|
|
beforeEach(async () => {
|
|
fetchMock.resolveSpecific(
|
|
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo?propagationPolicy=Background"],
|
|
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo?propagationPolicy=Background", "{}"),
|
|
);
|
|
});
|
|
|
|
it("resolves the call", async () => {
|
|
expect(await deleteRequest).toBeDefined();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("deleting namespaces (cluser scoped resource)", () => {
|
|
let api: NamespaceApi;
|
|
|
|
beforeEach(() => {
|
|
api = new NamespaceApi({
|
|
request,
|
|
});
|
|
});
|
|
|
|
describe("when deleting by just name", () => {
|
|
let deleteRequest: Promise<KubeJsonApiData>;
|
|
|
|
beforeEach(async () => {
|
|
deleteRequest = api.delete({ name: "foo" });
|
|
|
|
// This is required for how JS promises work
|
|
await flushPromises();
|
|
});
|
|
|
|
it("requests deleting Namespace without namespace", () => {
|
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
|
"http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background",
|
|
{
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
method: "delete",
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("when request resolves", () => {
|
|
beforeEach(async () => {
|
|
fetchMock.resolveSpecific(
|
|
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background"],
|
|
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background", "{}"),
|
|
);
|
|
});
|
|
|
|
it("resolves the call", async () => {
|
|
expect(await deleteRequest).toBeDefined();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when deleting by name and empty namespace", () => {
|
|
let deleteRequest: Promise<KubeJsonApiData>;
|
|
|
|
beforeEach(async () => {
|
|
deleteRequest = api.delete({ name: "foo", namespace: "" });
|
|
|
|
// This is required for how JS promises work
|
|
await flushPromises();
|
|
});
|
|
|
|
it("requests deleting Namespace without namespace", () => {
|
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
|
"http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background",
|
|
{
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
method: "delete",
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("when request resolves", () => {
|
|
beforeEach(async () => {
|
|
fetchMock.resolveSpecific(
|
|
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background"],
|
|
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background", "{}"),
|
|
);
|
|
});
|
|
|
|
it("resolves the call", async () => {
|
|
expect(await deleteRequest).toBeDefined();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when deleting by name and namespace", () => {
|
|
it("rejects request", () => {
|
|
expect(api.delete({ name: "foo", namespace: "test" })).rejects.toBeDefined();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("watching pods", () => {
|
|
let api: PodApi;
|
|
let stream: PassThrough;
|
|
|
|
beforeEach(() => {
|
|
api = new PodApi({
|
|
request,
|
|
});
|
|
stream = new PassThrough();
|
|
});
|
|
|
|
afterEach(() => {
|
|
stream.end();
|
|
stream.destroy();
|
|
});
|
|
|
|
describe("when watching in a namespace", () => {
|
|
let stopWatch: () => void;
|
|
let callback: jest.MockedFunction<KubeApiWatchCallback>;
|
|
|
|
beforeEach(async () => {
|
|
callback = jest.fn();
|
|
stopWatch = api.watch({
|
|
namespace: "kube-system",
|
|
callback,
|
|
});
|
|
|
|
await flushPromises();
|
|
});
|
|
|
|
it("requests the watch", () => {
|
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
|
"http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=&timeoutSeconds=600",
|
|
{
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
method: "get",
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("when the request resolves with a stream", () => {
|
|
beforeEach(async () => {
|
|
await fetchMock.resolveSpecific(
|
|
([url, init]) => {
|
|
const isMatch = url === "http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=&timeoutSeconds=600";
|
|
|
|
if (isMatch) {
|
|
init?.signal?.addEventListener("abort", () => {
|
|
stream.destroy();
|
|
});
|
|
}
|
|
|
|
return isMatch;
|
|
},
|
|
createMockResponseFromStream("http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=&timeoutSeconds=600", stream),
|
|
);
|
|
});
|
|
|
|
describe("when some data comes back on the stream", () => {
|
|
beforeEach(() => {
|
|
stream.emit("data", `${JSON.stringify({
|
|
type: "ADDED",
|
|
object: {
|
|
apiVersion: "v1",
|
|
kind: "Pod",
|
|
metadata: {
|
|
name: "foobar",
|
|
namespace: "kube-system",
|
|
resourceVersion: "1",
|
|
uid: "123456",
|
|
},
|
|
},
|
|
} as IKubeWatchEvent<KubeJsonApiDataFor<Pod>>)}\n`);
|
|
});
|
|
|
|
it("calls the callback with the data", () => {
|
|
expect(callback).toBeCalledWith(
|
|
{
|
|
type: "ADDED",
|
|
object: {
|
|
apiVersion: "v1",
|
|
kind: "Pod",
|
|
metadata: {
|
|
name: "foobar",
|
|
namespace: "kube-system",
|
|
resourceVersion: "1",
|
|
selfLink: "/api/v1/namespaces/kube-system/pods/foobar",
|
|
uid: "123456",
|
|
},
|
|
},
|
|
},
|
|
null,
|
|
);
|
|
});
|
|
|
|
describe("when stopping the watch", () => {
|
|
beforeEach(() => {
|
|
stopWatch();
|
|
});
|
|
|
|
it("closes the stream", () => {
|
|
expect(stream.destroyed).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when watching in a namespace with an abort controller provided", () => {
|
|
let callback: jest.MockedFunction<KubeApiWatchCallback>;
|
|
let abortController: AbortController;
|
|
|
|
beforeEach(async () => {
|
|
callback = jest.fn();
|
|
abortController = new AbortController();
|
|
api.watch({
|
|
namespace: "kube-system",
|
|
callback,
|
|
abortController,
|
|
});
|
|
|
|
await flushPromises();
|
|
});
|
|
|
|
it("requests the watch", () => {
|
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
|
"http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=&timeoutSeconds=600",
|
|
{
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
method: "get",
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("when the request resolves with a stream", () => {
|
|
beforeEach(async () => {
|
|
await fetchMock.resolveSpecific(
|
|
([url, init]) => {
|
|
const isMatch = url === "http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=&timeoutSeconds=600";
|
|
|
|
if (isMatch) {
|
|
init?.signal?.addEventListener("abort", () => {
|
|
stream.destroy();
|
|
});
|
|
}
|
|
|
|
return isMatch;
|
|
},
|
|
createMockResponseFromStream("http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=&timeoutSeconds=600", stream),
|
|
);
|
|
});
|
|
|
|
describe("when some data comes back on the stream", () => {
|
|
beforeEach(() => {
|
|
stream.emit("data", `${JSON.stringify({
|
|
type: "ADDED",
|
|
object: {
|
|
apiVersion: "v1",
|
|
kind: "Pod",
|
|
metadata: {
|
|
name: "foobar",
|
|
namespace: "kube-system",
|
|
resourceVersion: "1",
|
|
uid: "123456",
|
|
},
|
|
},
|
|
} as IKubeWatchEvent<KubeJsonApiDataFor<Pod>>)}\n`);
|
|
});
|
|
|
|
it("calls the callback with the data", () => {
|
|
expect(callback).toBeCalledWith(
|
|
{
|
|
type: "ADDED",
|
|
object: {
|
|
apiVersion: "v1",
|
|
kind: "Pod",
|
|
metadata: {
|
|
name: "foobar",
|
|
namespace: "kube-system",
|
|
resourceVersion: "1",
|
|
selfLink: "/api/v1/namespaces/kube-system/pods/foobar",
|
|
uid: "123456",
|
|
},
|
|
},
|
|
},
|
|
null,
|
|
);
|
|
});
|
|
|
|
describe("when stopping the watch via the controller", () => {
|
|
beforeEach(() => {
|
|
abortController.abort();
|
|
});
|
|
|
|
it("closes the stream", () => {
|
|
expect(stream.destroyed).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when watching in a namespace with a timeout", () => {
|
|
let stopWatch: () => void;
|
|
let callback: jest.MockedFunction<KubeApiWatchCallback>;
|
|
|
|
beforeEach(async () => {
|
|
callback = jest.fn();
|
|
stopWatch = api.watch({
|
|
namespace: "kube-system",
|
|
callback,
|
|
timeout: 60,
|
|
});
|
|
|
|
await flushPromises();
|
|
});
|
|
|
|
it("requests the watch", () => {
|
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
|
"http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=&timeoutSeconds=60",
|
|
{
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
method: "get",
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("when the request resolves with a stream", () => {
|
|
beforeEach(async () => {
|
|
await fetchMock.resolveSpecific(
|
|
([url, init]) => {
|
|
const isMatch = url === "http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=&timeoutSeconds=60";
|
|
|
|
if (isMatch) {
|
|
init?.signal?.addEventListener("abort", () => {
|
|
stream.destroy();
|
|
});
|
|
}
|
|
|
|
return isMatch;
|
|
},
|
|
createMockResponseFromStream("http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=&timeoutSeconds=60", stream),
|
|
);
|
|
});
|
|
|
|
describe("when some data comes back on the stream", () => {
|
|
beforeEach(() => {
|
|
stream.emit("data", `${JSON.stringify({
|
|
type: "ADDED",
|
|
object: {
|
|
apiVersion: "v1",
|
|
kind: "Pod",
|
|
metadata: {
|
|
name: "foobar",
|
|
namespace: "kube-system",
|
|
resourceVersion: "1",
|
|
uid: "123456",
|
|
},
|
|
},
|
|
} as IKubeWatchEvent<KubeJsonApiDataFor<Pod>>)}\n`);
|
|
});
|
|
|
|
it("calls the callback with the data", () => {
|
|
expect(callback).toBeCalledWith(
|
|
{
|
|
type: "ADDED",
|
|
object: {
|
|
apiVersion: "v1",
|
|
kind: "Pod",
|
|
metadata: {
|
|
name: "foobar",
|
|
namespace: "kube-system",
|
|
resourceVersion: "1",
|
|
selfLink: "/api/v1/namespaces/kube-system/pods/foobar",
|
|
uid: "123456",
|
|
},
|
|
},
|
|
},
|
|
null,
|
|
);
|
|
});
|
|
|
|
describe("when stopping the watch", () => {
|
|
beforeEach(() => {
|
|
stopWatch();
|
|
});
|
|
|
|
it("closes the stream", () => {
|
|
expect(stream.destroyed).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("when the watch ends", () => {
|
|
beforeEach(() => {
|
|
stream.end();
|
|
});
|
|
|
|
it("requests a new watch", () => {
|
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
|
"http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=&timeoutSeconds=60",
|
|
{
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
method: "get",
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("when stopping the watch", () => {
|
|
beforeEach(() => {
|
|
stopWatch();
|
|
});
|
|
|
|
it("closes the stream", () => {
|
|
expect(stream.destroyed).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("creating pods", () => {
|
|
let api: PodApi;
|
|
|
|
beforeEach(() => {
|
|
api = new PodApi({
|
|
request,
|
|
});
|
|
});
|
|
|
|
describe("when creating a pod", () => {
|
|
let createRequest: Promise<Pod | null>;
|
|
|
|
beforeEach(async () => {
|
|
createRequest = api.create({
|
|
name: "foobar",
|
|
namespace: "default",
|
|
}, {
|
|
metadata: {
|
|
labels: {
|
|
foo: "bar",
|
|
},
|
|
},
|
|
spec: {
|
|
containers: [
|
|
{
|
|
name: "web",
|
|
image: "nginx",
|
|
ports: [
|
|
{
|
|
name: "web",
|
|
containerPort: 80,
|
|
protocol: "TCP",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
// This is required because of how JS promises work
|
|
await flushPromises();
|
|
});
|
|
|
|
it("should request to create a pod with full descriptor", () => {
|
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
|
"http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods",
|
|
{
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
method: "post",
|
|
body: JSON.stringify({
|
|
metadata: {
|
|
labels: {
|
|
foo: "bar",
|
|
},
|
|
name: "foobar",
|
|
namespace: "default",
|
|
},
|
|
spec: {
|
|
containers: [{
|
|
name: "web",
|
|
image: "nginx",
|
|
ports: [{
|
|
name: "web",
|
|
containerPort: 80,
|
|
protocol: "TCP",
|
|
}],
|
|
}],
|
|
},
|
|
kind: "Pod",
|
|
apiVersion: "v1",
|
|
}),
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("when request resolves with data", () => {
|
|
beforeEach(async () => {
|
|
await fetchMock.resolveSpecific(
|
|
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods"],
|
|
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods", JSON.stringify({
|
|
kind: "Pod",
|
|
apiVersion: "v1",
|
|
metadata: {
|
|
name: "foobar",
|
|
namespace: "default",
|
|
labels: {
|
|
foo: "bar",
|
|
},
|
|
resourceVersion: "1",
|
|
uid: "123456798",
|
|
},
|
|
spec: {
|
|
containers: [{
|
|
name: "web",
|
|
image: "nginx",
|
|
ports: [{
|
|
name: "web",
|
|
containerPort: 80,
|
|
protocol: "TCP",
|
|
}],
|
|
}],
|
|
},
|
|
})),
|
|
);
|
|
});
|
|
|
|
it("call should resolve in a Pod instance", async () => {
|
|
expect(await createRequest).toBeInstanceOf(Pod);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("updating pods", () => {
|
|
let api: PodApi;
|
|
|
|
beforeEach(() => {
|
|
api = new PodApi({
|
|
request,
|
|
});
|
|
});
|
|
|
|
describe("when updating a pod", () => {
|
|
let updateRequest: Promise<Pod | null>;
|
|
|
|
beforeEach(async () => {
|
|
updateRequest = api.update({
|
|
name: "foobar",
|
|
namespace: "default",
|
|
}, {
|
|
kind: "Pod",
|
|
apiVersion: "v1",
|
|
metadata: {
|
|
labels: {
|
|
foo: "bar",
|
|
},
|
|
},
|
|
spec: {
|
|
containers: [{
|
|
name: "web",
|
|
image: "nginx",
|
|
ports: [{
|
|
name: "web",
|
|
containerPort: 80,
|
|
protocol: "TCP",
|
|
}],
|
|
}],
|
|
},
|
|
});
|
|
|
|
await flushPromises();
|
|
});
|
|
|
|
it("should request that the pod is updated", () => {
|
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
|
"http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foobar",
|
|
{
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
method: "put",
|
|
body: JSON.stringify({
|
|
kind: "Pod",
|
|
apiVersion: "v1",
|
|
metadata: {
|
|
labels: {
|
|
foo: "bar",
|
|
},
|
|
name: "foobar",
|
|
namespace: "default",
|
|
},
|
|
spec: {
|
|
containers: [{
|
|
name: "web",
|
|
image: "nginx",
|
|
ports: [{
|
|
name: "web",
|
|
containerPort: 80,
|
|
protocol: "TCP",
|
|
}],
|
|
}],
|
|
},
|
|
}),
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("when the request resolves with data", () => {
|
|
beforeEach(async () => {
|
|
await fetchMock.resolveSpecific(
|
|
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foobar"],
|
|
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foobar", JSON.stringify({
|
|
kind: "Pod",
|
|
apiVersion: "v1",
|
|
metadata: {
|
|
name: "foobar",
|
|
namespace: "default",
|
|
labels: {
|
|
foo: "bar",
|
|
},
|
|
resourceVersion: "1",
|
|
uid: "123456798",
|
|
},
|
|
spec: {
|
|
containers: [{
|
|
name: "web",
|
|
image: "nginx",
|
|
ports: [{
|
|
name: "web",
|
|
containerPort: 80,
|
|
protocol: "TCP",
|
|
}],
|
|
}],
|
|
},
|
|
})),
|
|
);
|
|
});
|
|
|
|
it("the call should resolve to a Pod", async () => {
|
|
expect(await updateRequest).toBeInstanceOf(Pod);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("listing pods", () => {
|
|
let api: PodApi;
|
|
|
|
beforeEach(() => {
|
|
api = new PodApi({
|
|
request,
|
|
});
|
|
});
|
|
|
|
describe("when listing pods with no descriptor", () => {
|
|
let listRequest: Promise<Pod[] | null>;
|
|
|
|
beforeEach(async () => {
|
|
listRequest = api.list();
|
|
|
|
await flushPromises();
|
|
});
|
|
|
|
it("should request that the pods from all namespaces", () => {
|
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
|
"http://127.0.0.1:9999/api-kube/api/v1/pods",
|
|
{
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
method: "get",
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("when the request resolves with empty data", () => {
|
|
beforeEach(async () => {
|
|
await fetchMock.resolveSpecific(
|
|
["http://127.0.0.1:9999/api-kube/api/v1/pods"],
|
|
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/pods", JSON.stringify({
|
|
kind: "PodList",
|
|
apiVersion: "v1",
|
|
metadata: {},
|
|
items: [],
|
|
})),
|
|
);
|
|
});
|
|
|
|
it("the call should resolve to an empty list", async () => {
|
|
expect(await listRequest).toEqual([]);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when listing pods with descriptor with namespace=''", () => {
|
|
let listRequest: Promise<Pod[] | null>;
|
|
|
|
beforeEach(async () => {
|
|
listRequest = api.list({
|
|
namespace: "",
|
|
});
|
|
|
|
await flushPromises();
|
|
});
|
|
|
|
it("should request that the pods from all namespaces", () => {
|
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
|
"http://127.0.0.1:9999/api-kube/api/v1/pods",
|
|
{
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
method: "get",
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("when the request resolves with empty data", () => {
|
|
beforeEach(async () => {
|
|
await fetchMock.resolveSpecific(
|
|
["http://127.0.0.1:9999/api-kube/api/v1/pods"],
|
|
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/pods", JSON.stringify({
|
|
kind: "PodList",
|
|
apiVersion: "v1",
|
|
metadata: {},
|
|
items: [],
|
|
})),
|
|
);
|
|
});
|
|
|
|
it("the call should resolve to an empty list", async () => {
|
|
expect(await listRequest).toEqual([]);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when listing pods with descriptor with namespace='default'", () => {
|
|
let listRequest: Promise<Pod[] | null>;
|
|
|
|
beforeEach(async () => {
|
|
listRequest = api.list({
|
|
namespace: "default",
|
|
});
|
|
|
|
await flushPromises();
|
|
});
|
|
|
|
it("should request that the pods from just the default namespace", () => {
|
|
expect(fetchMock.mock.lastCall).toMatchObject([
|
|
"http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods",
|
|
{
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
method: "get",
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("when the request resolves with empty data", () => {
|
|
beforeEach(async () => {
|
|
await fetchMock.resolveSpecific(
|
|
["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods"],
|
|
createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods", JSON.stringify({
|
|
kind: "PodList",
|
|
apiVersion: "v1",
|
|
metadata: {},
|
|
items: [],
|
|
})),
|
|
);
|
|
});
|
|
|
|
it("the call should resolve to an empty list", async () => {
|
|
expect(await listRequest).toEqual([]);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|