1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/src/main/router/router.test.ts
Sebastian Malton 900f02fd8c
Remove global version of appEventBus (#6096)
* 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>
2022-10-31 14:59:05 +02:00

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([]);
});
});
});
});
});