mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
* Remove global version of appEventBus Signed-off-by: Sebastian Malton <sebastian@malton.name> * Introduce a temporary but better shape of ExecFileInjectable error Signed-off-by: Sebastian Malton <sebastian@malton.name> Signed-off-by: Sebastian Malton <sebastian@malton.name>
311 lines
10 KiB
TypeScript
311 lines
10 KiB
TypeScript
/**
|
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
*/
|
|
|
|
import routerInjectable, { routeInjectionToken } from "./router.injectable";
|
|
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
|
import type { Router } from "./router";
|
|
import type { Cluster } from "../../common/cluster/cluster";
|
|
import { Request } from "mock-http";
|
|
import { getInjectable } from "@ogre-tools/injectable";
|
|
import type { AsyncFnMock } from "@async-fn/jest";
|
|
import asyncFn from "@async-fn/jest";
|
|
import parseRequestInjectable from "./parse-request.injectable";
|
|
import { contentTypes } from "./router-content-types";
|
|
import mockFs from "mock-fs";
|
|
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
|
import type { Route } from "./route";
|
|
import type { SetRequired } from "type-fest";
|
|
import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable";
|
|
import kubectlBinaryNameInjectable from "../kubectl/binary-name.injectable";
|
|
import kubectlDownloadingNormalizedArchInjectable from "../kubectl/normalized-arch.injectable";
|
|
import fsInjectable from "../../common/fs/fs.injectable";
|
|
import { runInAction } from "mobx";
|
|
|
|
describe("router", () => {
|
|
let router: Router;
|
|
let routeHandlerMock: AsyncFnMock<() => any>;
|
|
|
|
beforeEach(async () => {
|
|
routeHandlerMock = asyncFn();
|
|
|
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
|
|
|
mockFs();
|
|
di.permitSideEffects(fsInjectable);
|
|
|
|
di.override(parseRequestInjectable, () => () => Promise.resolve({
|
|
payload: "some-payload",
|
|
mime: "some-mime",
|
|
}));
|
|
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
|
di.override(kubectlBinaryNameInjectable, () => "kubectl");
|
|
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
|
|
di.override(normalizedPlatformInjectable, () => "darwin");
|
|
|
|
const injectable = getInjectable({
|
|
id: "some-route",
|
|
|
|
instantiate: () => ({
|
|
method: "get",
|
|
path: "/some-path",
|
|
handler: routeHandlerMock,
|
|
} as Route<any, string>),
|
|
|
|
injectionToken: routeInjectionToken,
|
|
});
|
|
|
|
runInAction(() => {
|
|
di.register(injectable);
|
|
});
|
|
|
|
router = di.inject(routerInjectable);
|
|
});
|
|
|
|
afterEach(() => {
|
|
mockFs.restore();
|
|
});
|
|
|
|
describe("when navigating to the route", () => {
|
|
let actualPromise: Promise<boolean>;
|
|
let clusterStub: Cluster;
|
|
let requestStub: SetRequired<Request, "url" | "method">;
|
|
let responseStub: any;
|
|
|
|
beforeEach(() => {
|
|
requestStub = new Request({
|
|
url: "/some-path",
|
|
method: "get",
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
}) as SetRequired<Request, "url" | "method">;
|
|
|
|
responseStub = { end: jest.fn(), setHeader: jest.fn(), write: jest.fn(), statusCode: undefined };
|
|
|
|
clusterStub = {} as Cluster;
|
|
|
|
actualPromise = router.route(clusterStub, requestStub, responseStub);
|
|
});
|
|
|
|
it("calls handler with the request", () => {
|
|
expect(routeHandlerMock).toHaveBeenCalledWith({
|
|
cluster: clusterStub,
|
|
params: {},
|
|
path: "/some-path",
|
|
payload: "some-payload",
|
|
query: expect.any(URLSearchParams),
|
|
raw: { req: requestStub, res: responseStub },
|
|
});
|
|
});
|
|
|
|
it("given no content-type is specified, when handler resolves, resolves with JSON", async () => {
|
|
await routeHandlerMock.resolve({ response: "some-response-from-route-handler" });
|
|
|
|
await actualPromise;
|
|
|
|
expect(responseStub.setHeader.mock.calls).toEqual([
|
|
["Content-Type", "application/json"],
|
|
]);
|
|
});
|
|
|
|
it("given JSON content-type is specified, when handler resolves with object, resolves with JSON", async () => {
|
|
await routeHandlerMock.resolve({ response: { some: "object" }});
|
|
|
|
await actualPromise;
|
|
|
|
expect(responseStub.end).toHaveBeenCalledWith('{"some":"object"}');
|
|
});
|
|
|
|
describe("when handler resolves without any result", () => {
|
|
beforeEach(async () => {
|
|
await routeHandlerMock.resolve(undefined);
|
|
|
|
await actualPromise;
|
|
});
|
|
|
|
it("resolves as plain text", () => {
|
|
expect(responseStub.setHeader.mock.calls).toEqual([["Content-Type", "text/plain"]]);
|
|
});
|
|
|
|
it("resolves with status code for no content", async () => {
|
|
expect(responseStub.statusCode).toBe(204);
|
|
});
|
|
|
|
it("resolves without content", async () => {
|
|
expect(responseStub.end.mock.calls).toEqual([[]]);
|
|
});
|
|
});
|
|
|
|
describe("when handler rejects", () => {
|
|
beforeEach(async () => {
|
|
await routeHandlerMock.reject(new Error("some-error"));
|
|
|
|
await actualPromise;
|
|
});
|
|
|
|
it("resolves as plain text", () => {
|
|
expect(responseStub.setHeader.mock.calls).toEqual([["Content-Type", "text/plain"]]);
|
|
});
|
|
|
|
it('resolves with "500" status code', () => {
|
|
expect(responseStub.statusCode).toBe(500);
|
|
});
|
|
|
|
it("resolves with error", () => {
|
|
expect(responseStub.end).toHaveBeenCalledWith("Error: some-error");
|
|
});
|
|
});
|
|
|
|
[
|
|
{ contentType: "text/plain", contentTypeObject: contentTypes.txt },
|
|
{ contentType: "application/json", contentTypeObject: contentTypes.json },
|
|
{ contentType: "text/html", contentTypeObject: contentTypes.html },
|
|
{ contentType: "text/css", contentTypeObject: contentTypes.css },
|
|
{ contentType: "image/gif", contentTypeObject: contentTypes.gif },
|
|
{ contentType: "image/jpeg", contentTypeObject: contentTypes.jpg },
|
|
{ contentType: "image/png", contentTypeObject: contentTypes.png },
|
|
{ contentType: "image/svg+xml", contentTypeObject: contentTypes.svg },
|
|
{ contentType: "application/javascript", contentTypeObject: contentTypes.js },
|
|
{ contentType: "font/woff2", contentTypeObject: contentTypes.woff2 },
|
|
{ contentType: "font/ttf", contentTypeObject: contentTypes.ttf },
|
|
].forEach(scenario => {
|
|
describe(`given content type is "${scenario.contentType}", when handler resolves with response`, () => {
|
|
beforeEach(async () => {
|
|
await routeHandlerMock.resolve({ response: "some-response", contentType: scenario.contentTypeObject });
|
|
|
|
await actualPromise;
|
|
});
|
|
|
|
it("has content type specific headers", () => {
|
|
expect(responseStub.setHeader.mock.calls).toEqual([
|
|
["Content-Type", scenario.contentType],
|
|
]);
|
|
});
|
|
|
|
it("defaults to successful status code", () => {
|
|
expect(responseStub.statusCode).toBe(200);
|
|
});
|
|
|
|
it("has response as body", () => {
|
|
expect(responseStub.end).toHaveBeenCalledWith("some-response");
|
|
});
|
|
});
|
|
|
|
it(`given content type is "${scenario.contentType}", when handler resolves with success and custom status code, defaults to "200" as status code`, async () => {
|
|
await routeHandlerMock.resolve({
|
|
response: "some-response",
|
|
statusCode: 204,
|
|
contentType: scenario.contentTypeObject,
|
|
});
|
|
|
|
await actualPromise;
|
|
|
|
expect(responseStub.statusCode).toBe(204);
|
|
});
|
|
|
|
it(`given content type is "${scenario.contentType}", when handler resolves with success but without status code, defaults to "200" as status code`, async () => {
|
|
await routeHandlerMock.resolve({
|
|
response: "some-response",
|
|
contentType: scenario.contentTypeObject,
|
|
});
|
|
|
|
await actualPromise;
|
|
|
|
expect(responseStub.statusCode).toBe(200);
|
|
});
|
|
|
|
|
|
it(`given content type is "${scenario.contentType}", when handler resolves without response, has no body`, async () => {
|
|
await routeHandlerMock.resolve({
|
|
response: undefined,
|
|
contentType: scenario.contentTypeObject,
|
|
});
|
|
|
|
await actualPromise;
|
|
|
|
expect(responseStub.end.mock.calls).toEqual([[]]);
|
|
});
|
|
|
|
it(`given content type is "${scenario.contentType}", when handler resolves with error, has error as body`, async () => {
|
|
await routeHandlerMock.resolve({
|
|
error: "some-error",
|
|
contentType: scenario.contentTypeObject,
|
|
});
|
|
|
|
await actualPromise;
|
|
|
|
expect(responseStub.end).toHaveBeenCalledWith("some-error");
|
|
});
|
|
|
|
it(`given content type is "${scenario.contentType}", when handler resolves with error and status code, has custom status code`, async () => {
|
|
await routeHandlerMock.resolve({
|
|
error: "some-error",
|
|
statusCode: 414,
|
|
contentType: scenario.contentTypeObject,
|
|
});
|
|
|
|
await actualPromise;
|
|
|
|
expect(responseStub.statusCode).toBe(414);
|
|
});
|
|
|
|
it(`given content type is "${scenario.contentType}", when handler resolves with error but without status code, defaults to "400" as status code`, async () => {
|
|
await routeHandlerMock.resolve({
|
|
error: "some-error",
|
|
contentType: scenario.contentTypeObject,
|
|
});
|
|
|
|
await actualPromise;
|
|
|
|
expect(responseStub.statusCode).toBe(400);
|
|
});
|
|
|
|
it(`given content type is "${scenario.contentType}", when handler resolves custom headers, resolves with content type specific headers and custom headers`, async () => {
|
|
await routeHandlerMock.resolve({
|
|
response: "irrelevant",
|
|
|
|
headers: {
|
|
"Content-Type": "some-content-type-to-be-overridden",
|
|
"Some-Header": "some-header-value",
|
|
},
|
|
|
|
contentType: scenario.contentTypeObject,
|
|
});
|
|
|
|
await actualPromise;
|
|
|
|
expect(responseStub.setHeader.mock.calls).toEqual([
|
|
["Content-Type", scenario.contentType],
|
|
["Some-Header", "some-header-value"],
|
|
]);
|
|
|
|
});
|
|
|
|
describe(`given content type is "${scenario.contentType}", when handler resolves with binary content`, () => {
|
|
let responseBufferStub: Buffer;
|
|
|
|
beforeEach(async () => {
|
|
responseBufferStub = Buffer.from("some-binary-content");
|
|
|
|
await routeHandlerMock.resolve({
|
|
response: responseBufferStub,
|
|
contentType: scenario.contentTypeObject,
|
|
});
|
|
|
|
await actualPromise;
|
|
});
|
|
|
|
it("writes binary content to response", () => {
|
|
expect(responseStub.write).toHaveBeenCalledWith(responseBufferStub);
|
|
});
|
|
|
|
it("does not end with the response", () => {
|
|
expect(responseStub.end.mock.calls[0]).toEqual([]);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|