mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
1189 lines
34 KiB
TypeScript
1189 lines
34 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 { PassThrough } from "stream";
|
|
import { Deployment, Pod } from "@k8slens/kube-object";
|
|
import type Fetch from "@k8slens/node-fetch";
|
|
import type { AsyncFnMock } from "@async-fn/jest";
|
|
import asyncFn from "@async-fn/jest";
|
|
import { flushPromises } from "@k8slens/test-utils";
|
|
import type { IKubeWatchEvent } from "./kube-watch-event";
|
|
import type { KubeJsonApiDataFor, KubeJsonApiData } from "@k8slens/kube-object";
|
|
import { createMockResponseFromStream, createMockResponseFromString } from "./mock-responses";
|
|
import type { Logger } from "@k8slens/logger";
|
|
import { DeploymentApi, NamespaceApi, PodApi } from "./endpoints";
|
|
import { KubeJsonApi } from "./kube-json-api";
|
|
|
|
describe("KubeApi", () => {
|
|
let fetchMock: AsyncFnMock<typeof Fetch>;
|
|
let logger: Logger;
|
|
let kubeJsonApi: KubeJsonApi;
|
|
|
|
beforeEach(() => {
|
|
fetchMock = asyncFn();
|
|
|
|
logger = {
|
|
info: jest.fn(),
|
|
debug: jest.fn(),
|
|
error: jest.fn(),
|
|
} as any;
|
|
|
|
kubeJsonApi = new KubeJsonApi(
|
|
{
|
|
fetch: fetchMock,
|
|
logger,
|
|
},
|
|
{
|
|
apiBase: "/api-kube",
|
|
serverAddress: "http://127.0.0.1:9999",
|
|
},
|
|
{
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
},
|
|
);
|
|
});
|
|
|
|
describe("patching deployments", () => {
|
|
let api: DeploymentApi;
|
|
|
|
beforeEach(() => {
|
|
api = new DeploymentApi({
|
|
logger,
|
|
maybeKubeApi: kubeJsonApi,
|
|
});
|
|
});
|
|
|
|
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({
|
|
logger,
|
|
maybeKubeApi: kubeJsonApi,
|
|
});
|
|
});
|
|
|
|
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 () => {
|
|
await 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 () => {
|
|
await 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 () => {
|
|
await 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 (cluster scoped resource)", () => {
|
|
let api: NamespaceApi;
|
|
|
|
beforeEach(() => {
|
|
api = new NamespaceApi({
|
|
logger,
|
|
maybeKubeApi: kubeJsonApi,
|
|
});
|
|
});
|
|
|
|
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 () => {
|
|
await 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 () => {
|
|
await 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("watching pods", () => {
|
|
let api: PodApi;
|
|
let stream: PassThrough;
|
|
|
|
beforeEach(() => {
|
|
api = new PodApi({
|
|
logger,
|
|
maybeKubeApi: kubeJsonApi,
|
|
});
|
|
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({
|
|
logger,
|
|
maybeKubeApi: kubeJsonApi,
|
|
});
|
|
});
|
|
|
|
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({
|
|
logger,
|
|
maybeKubeApi: kubeJsonApi,
|
|
});
|
|
});
|
|
|
|
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({
|
|
logger,
|
|
maybeKubeApi: kubeJsonApi,
|
|
});
|
|
});
|
|
|
|
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([]);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|