Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: simplify http handler test with reusable setup functions #342

Merged
merged 1 commit into from
Nov 12, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
329 changes: 160 additions & 169 deletions test/unit/service/httpHandler.spec.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,42 @@
"use strict";

const HttpHandler = () => require("../../../src/index").methods.httpHandler;

const MockLogger = () => Object.assign({
info: jest.fn(),
error: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
trace: jest.fn(),
});
const MockContext = () => Object.assign({
const MockContext = ({ action = jest.fn(), serve, settings } = {}) => Object.assign({
actions: {
rest: jest.fn(),
rest: action,
},
settings: require("../../../src/index").settings,
settings: { ...require("../../../src/index").settings, ...settings },
errorHandler: require("../../../src/index").methods.errorHandler,
logger: MockLogger(),
sendError: jest.fn(),
send404: jest.fn(),
corsHandler: jest.fn(() => false),
serve,
});

const MockRequest = () => Object.assign(jest.fn(), { headers: {} });
const MockRequest = ({ headers = {} } = {}) => Object.assign(jest.fn(), { headers });

const makeFakeError = (message, code) => {
const error = new Error(message);
error.code = code;
return error;
};

const setup = (headers) =>{
const handler = HttpHandler();
const req = MockRequest(headers);
const res = jest.fn();
const next = jest.fn();
return { handler, req, res, next };
};

describe("WebGateway", () => {
describe("methods", () => {
Expand All @@ -29,212 +45,187 @@ describe("WebGateway", () => {
expect(typeof HttpHandler()).toEqual("function");
});

it("sets $startTime of the request to the current process.hrtime()", () => {
const handler = HttpHandler();
const req = MockRequest();
const res = jest.fn();
const next = jest.fn();
const context = MockContext();
context.actions.rest.mockReturnValueOnce(Promise.resolve());

return handler.bind(context)(req, res, next).then(() => {
expect(req.$startTime[0]).toBeLessThanOrEqual(process.hrtime()[0]);
it("sets $startTime of the request to the current process.hrtime()", async () => {
const { handler, req, res, next } = setup();
const context = MockContext({
action: jest.fn().mockResolvedValueOnce()
});

await handler.bind(context)(req, res, next);

expect(req.$startTime[0]).toBeLessThanOrEqual(process.hrtime()[0]);
});

it("references the context via req.$service and res.$service", () => {
const handler = HttpHandler();
const req = MockRequest();
const res = jest.fn();
const next = jest.fn();
const context = MockContext();
context.actions.rest.mockReturnValueOnce(Promise.resolve());

return handler.bind(context)(req, res, next).then(() => {
expect(req.$service).toEqual(context);
expect(res.$service).toEqual(context);
it("references the context via req.$service and res.$service", async () => {
const { handler, req, res, next } = setup();
const context = MockContext({
action: jest.fn().mockResolvedValueOnce()
});
});

it("references the next middleware callback via req.$next", () => {
const handler = HttpHandler();
const req = MockRequest();
const res = jest.fn();
const next = jest.fn();
const context = MockContext();
context.actions.rest.mockReturnValueOnce(Promise.resolve());
await handler.bind(context)(req, res, next);

return handler.bind(context)(req, res, next).then(() => {
expect(req.$next).toEqual(next);
});
expect(req.$service).toEqual(context);
});

it("ensures that res.locals is an object if undefined", () => {
const handler = HttpHandler();
const req = MockRequest();
const res = jest.fn();
const next = jest.fn();
const context = MockContext();
context.actions.rest.mockReturnValueOnce(Promise.resolve());

return handler.bind(context)(req, res, next).then(() => {
expect(res.locals).toEqual({});
it("references the next middleware callback via req.$next", async () => {
const { handler, req, res, next } = setup();
const context = MockContext({
action: jest.fn().mockResolvedValueOnce()
});

await handler.bind(context)(req, res, next);

expect(req.$next).toEqual(next);
});

it("maintains the requestId of a \"x-request-id\" header if present", () => {
const handler = HttpHandler();
const req = MockRequest();
req.headers["x-request-id"] = "foobar";
const res = jest.fn();
const next = jest.fn();
const context = MockContext();
context.actions.rest.mockReturnValueOnce(Promise.resolve());

return handler.bind(context)(req, res, next).then(() => {
expect(context.actions.rest.mock.calls[0]).toEqual([{ req, res }, { requestID: "foobar" }]);
it("ensures that res.locals is an object if undefined", async () => {
const { handler, req, res, next } = setup();
const context = MockContext({
action: jest.fn().mockResolvedValueOnce()
});

await handler.bind(context)(req, res, next);

expect(res.locals).toEqual({});
});

it("maintains the requestId of a \"x-correlation-id\" header if present", () => {
const handler = HttpHandler();
const req = MockRequest();
req.headers["x-request-id"] = "foobar";
req.headers["x-correlation-id"] = "barfoo";
const res = jest.fn();
const next = jest.fn();
const context = MockContext();
context.actions.rest.mockReturnValueOnce(Promise.resolve());

return handler.bind(context)(req, res, next).then(() => {
expect(context.actions.rest.mock.calls[0]).toEqual([{ req, res }, { requestID: "barfoo" }]);
it("maintains the requestId of a \"x-request-id\" header if present", async () => {
const { handler, req, res, next } = setup({ headers: { "x-request-id": "foobar" } });
const context = MockContext({
action: jest.fn().mockResolvedValueOnce()
});
});

it("resolves if the rest.action did resolve with an object result", () => {
const handler = HttpHandler();
const req = MockRequest();
const res = jest.fn();
const next = jest.fn();
const context = MockContext();
context.actions.rest.mockReturnValueOnce(Promise.resolve({ foo: "bar" }));
await handler.bind(context)(req, res, next);

return handler.bind(context)(req, res, next).then(result => {
expect(context.send404.mock.calls.length).toEqual(0);
expect(context.actions.rest.mock.calls[0]).toEqual([{ req, res }, { requestID: "foobar" }]);
});

it("maintains the requestId of a \"x-correlation-id\" header if present", async () => {
const { handler, req, res, next } = setup({
headers: {
"x-request-id": "foobar",
"x-correlation-id": "barfoo"
}
});
const context = MockContext({
action: jest.fn().mockResolvedValueOnce()
});

await handler.bind(context)(req, res, next);

expect(context.actions.rest.mock.calls[0]).toEqual([{ req, res }, { requestID: "barfoo" }]);
});

it("sends a 404 response if the request could not be routed and serving static assets is not configured", () => {
const handler = HttpHandler();
const req = MockRequest();
const res = jest.fn();
const next = jest.fn();
const context = MockContext();
context.actions.rest.mockReturnValueOnce(Promise.resolve(null));
it("resolves if the rest.action did resolve with an object result", async () => {
const { handler, req, res, next } = setup();
const context = MockContext({
action: jest.fn().mockResolvedValueOnce({ foo: "bar" })
});

await handler.bind(context)(req, res, next);

return handler.bind(context)(req, res, next).then(result => {
expect(context.send404.mock.calls[0]).toEqual([req, res]);
expect(context.send404.mock.calls.length).toEqual(0);
});

it("sends a 404 response if the request could not be routed and serving static assets is not configured", async () => {
const { handler, req, res, next } = setup();
const context = MockContext({
action: jest.fn().mockResolvedValueOnce(null),
});

await handler.bind(context)(req, res, next);

expect(context.send404.mock.calls[0]).toEqual([req, res]);
});

it("resolves if the request could not be routed and instead a static asset was served", () => {
const handler = HttpHandler();
const req = MockRequest();
const res = jest.fn();
const next = jest.fn();
let context = MockContext();
context.serve = jest.fn();
context.actions.rest.mockReturnValueOnce(Promise.resolve(null));

return handler.bind(context)(req, res, next).then(() => {
expect(context.send404.mock.calls.length).toEqual(0);
expect(context.serve.mock.calls[0][0]).toEqual(req);
expect(context.serve.mock.calls[0][1]).toEqual(res);
it("resolves if the request could not be routed and instead a static asset was served", async () => {
const { handler, req, res, next } = setup();
let context = MockContext({
action: jest.fn().mockResolvedValueOnce(null),
serve: jest.fn(),
});

await handler.bind(context)(req, res, next);

expect(context.send404.mock.calls.length).toEqual(0);
expect(context.serve.mock.calls[0][0]).toEqual(req);
expect(context.serve.mock.calls[0][1]).toEqual(res);
});

it("responds with 404 if the request could not be routed and serving a static asset encountered an error", () => {
const handler = HttpHandler();
const req = MockRequest();
const res = jest.fn();
const next = jest.fn();
let context = MockContext();
it("responds with 404 if the request could not be routed and serving a static asset encountered an error", async () => {
const { handler, req, res, next } = setup();
let context = MockContext({
action: jest.fn().mockResolvedValueOnce(null),
serve: jest.fn(),
});
const error = new Error("Something went wrong while serving a static asset");
context.serve = jest.fn();
context.actions.rest.mockReturnValueOnce(Promise.resolve(null));

return handler.bind(context)(req, res, next).then(() => {
context.serve.mock.calls[0][2](error);
expect(context.send404.mock.calls[0]).toEqual([req, res]);
expect(context.logger.debug.mock.calls[0]).toEqual([error]);
});
await handler.bind(context)(req, res, next);
context.serve.mock.calls[0][2](error);

expect(context.send404.mock.calls[0]).toEqual([req, res]);
expect(context.logger.debug.mock.calls[0]).toEqual([error]);
});

it("logs and responds with an error if the rest action rejects", () => {
const handler = HttpHandler();
const req = MockRequest();
const res = jest.fn();
const next = jest.fn();
const context = MockContext();
let error = new Error("Something went wrong while invoking the rest action");
error.code = 503;
context.actions.rest.mockReturnValueOnce(Promise.reject(error));

return handler.bind(context)(req, res, next).then(() => {
expect(context.sendError.mock.calls[0]).toEqual([req, res, error]);
expect(context.logger.error.mock.calls[0]).toEqual([" Request error!", error.name, ":", error.message, "\n", error.stack, "\nData:", error.data]);
it("logs and responds with an error if the rest action rejects", async () => {
const { handler, req, res, next } = setup();
const error = makeFakeError("Something went wrong while invoking the rest action", 503);
const context = MockContext({
action: jest.fn().mockRejectedValueOnce(error)
});

await handler.bind(context)(req, res, next);

expect(context.sendError.mock.calls[0]).toEqual([req, res, error]);
expect(context.logger.error.mock.calls[0]).toEqual([" Request error!", error.name, ":", error.message, "\n", error.stack, "\nData:", error.data]);
});

it("responds with an error but does not log the error if the rest action rejects, the error code is 400 and settings.log4XXResponses is false", () => {
const handler = HttpHandler();
const req = MockRequest();
const res = jest.fn();
const next = jest.fn();
const context = MockContext();
context.settings.log4XXResponses = false;
let error = new Error("Something went wrong while invoking the rest action");
error.code = 400;
context.actions.rest.mockReturnValueOnce(Promise.reject(error));

return handler.bind(context)(req, res, next).then(() => {
expect(context.sendError.mock.calls[0]).toEqual([req, res, error]);
expect(context.logger.error.mock.calls.length).toEqual(0);
it("responds with an error but does not log the error if the rest action rejects, the error code is 400 and settings.log4XXResponses is false", async () => {
const { handler, req, res, next } = setup();
const error = makeFakeError("Something went wrong while invoking the rest action", 400);
const context = MockContext({
action: jest.fn().mockRejectedValueOnce(error),
settings: {
log4XXResponses: false
}
});

await handler.bind(context)(req, res, next);

expect(context.sendError.mock.calls[0]).toEqual([req, res, error]);
expect(context.logger.error.mock.calls.length).toEqual(0);
});

it("logs and responds with an error if the rest action rejects, the error code is 399 and settings.log4XXResponses is false", () => {
const handler = HttpHandler();
const req = MockRequest();
const res = jest.fn();
const next = jest.fn();
const context = MockContext();
context.settings.log4XXResponses = false;
let error = new Error("Something went wrong while invoking the rest action");
error.code = 399;
context.actions.rest.mockReturnValueOnce(Promise.reject(error));

return handler.bind(context)(req, res, next).then(() => {
expect(context.sendError.mock.calls[0]).toEqual([req, res, error]);
expect(context.logger.error.mock.calls.length).toEqual(1);
it("logs and responds with an error if the rest action rejects, the error code is 399 and settings.log4XXResponses is false", async () => {
const { handler, req, res, next } = setup();
const error = makeFakeError("Something went wrong while invoking the rest action", 399);
const context = MockContext({
action: jest.fn().mockRejectedValueOnce(error),
settings: {
log4XXResponses: false
}
});

await handler.bind(context)(req, res, next);

expect(context.sendError.mock.calls[0]).toEqual([req, res, error]);
expect(context.logger.error.mock.calls.length).toEqual(1);
});

it("logs and responds with an error if the rest action rejects, the error code is 500 and settings.log4XXResponses is false", () => {
const handler = HttpHandler();
const req = MockRequest();
const res = jest.fn();
const next = jest.fn();
const context = MockContext();
context.settings.log4XXResponses = false;
let error = new Error("Something went wrong while invoking the rest action");
error.code = 500;
context.actions.rest.mockReturnValueOnce(Promise.reject(error));

return handler.bind(context)(req, res, next).then(() => {
expect(context.sendError.mock.calls[0]).toEqual([req, res, error]);
expect(context.logger.error.mock.calls.length).toEqual(1);
it("logs and responds with an error if the rest action rejects, the error code is 500 and settings.log4XXResponses is false", async () => {
const { handler, req, res, next } = setup();
const error = makeFakeError("Something went wrong while invoking the rest action", 500);
const context = MockContext({
action: jest.fn().mockRejectedValueOnce(error),
settings: {
log4XXResponses: false
}
});

await handler.bind(context)(req, res, next);

expect(context.sendError.mock.calls[0]).toEqual([req, res, error]);
expect(context.logger.error.mock.calls.length).toEqual(1);
});
});
});
Expand Down
Loading