@whook/whook
Version:
Build strong and efficient REST web services.
1,439 lines (1,438 loc) • 149 kB
JavaScript
/* eslint max-nested-callbacks: 0 */
import { describe, test, beforeEach, jest, expect } from '@jest/globals';
import streamtest from 'streamtest';
import { YHTTPError } from 'yhttperror';
import { YError } from 'yerror';
import initHTTPRouter from './httpRouter.js';
import initSchemaValidators from './schemaValidators.js';
import initQueryParserBuilder from './queryParserBuilder.js';
import { NodeEnv } from 'application-services';
import initErrorHandler from './errorHandler.js';
async function waitResponse(response, raw = true) {
if (!response.body) {
return response;
}
const [stream, resultPromise] = streamtest.toText();
response.body.pipe(stream);
const text = await resultPromise;
return Object.assign({}, response, {
body: raw ? text : JSON.parse(text),
});
}
function prepareTransaction(result = Promise.resolve()) {
const handler = jest.fn(() => null === result
? undefined
: result
? Promise.resolve(result)
: Promise.reject(new YError('E_NOT_SUPPOSED_TO_BE_HERE')));
const httpTransactionStart = jest.fn(async (buildResponse) => buildResponse());
const httpTransactionCatch = jest.fn(async (err) => {
throw YHTTPError.cast(err);
});
const httpTransactionEnd = jest.fn(async (_res) => _res && undefined);
const httpTransaction = jest.fn(async (req) => ({
request: {
url: req.url,
method: req.method.toLowerCase(),
headers: req.headers,
body: req,
},
transaction: {
start: httpTransactionStart,
catch: httpTransactionCatch,
end: httpTransactionEnd,
},
}));
const ROUTES_HANDLERS = {
ping: handler,
headUserAvatar: handler,
getUserAvatar: handler,
putUserAvatar: handler,
deleteUserAvatar: handler,
getUser: handler,
putUser: handler,
};
return {
ROUTES_HANDLERS,
httpTransaction: httpTransaction,
httpTransactionStart,
httpTransactionCatch,
httpTransactionEnd,
handler,
};
}
describe('initHTTPRouter', () => {
const ENV = { NODE_ENV: NodeEnv.Test };
const DEBUG_NODE_ENVS = ['test'];
const BASE_PATH = '/v1';
const API = {
openapi: '3.1.0',
info: {
version: '1.0.0',
title: 'Sample Swagger',
description: 'A sample Swagger file for testing purpose.',
},
servers: [
{
url: `http://{host}:{port}{basePath}`,
variables: {
host: {
default: 'localhost:1337',
},
basePath: {
default: '/v1',
},
},
},
],
paths: {
'/ping': {
head: {
operationId: 'ping',
summary: "Checks API's availability.",
responses: {
default: {
$ref: '#/components/responses/UnexpectedError',
},
'200': {
description: 'Pong',
content: {
'application/json': {
schema: {
type: 'object',
additionalProperties: false,
properties: {
pong: {
type: 'string',
enum: ['pong'],
},
},
},
},
},
},
},
},
},
'/users/{userId}/avatar': {
parameters: [
{
in: 'path',
name: 'userId',
required: true,
schema: {
$ref: '#/components/schemas/UserIdSchema',
},
},
{
in: 'query',
name: 'forFriendsUserId',
required: false,
schema: {
type: 'array',
items: {
$ref: '#/components/schemas/UserIdSchema',
},
},
},
{
in: 'header',
name: 'x-depth',
required: false,
schema: {
type: 'number',
enum: [0, 1, 2],
},
},
],
head: {
operationId: 'headUserAvatar',
summary: "Checks user's avatar existence.",
responses: {
'200': {
description: 'User avatar exists.',
},
'404': {
description: 'User avatar not found',
},
},
},
get: {
operationId: 'getUserAvatar',
summary: "Retrieve user's avatar.",
responses: {
default: {
$ref: '#/components/responses/UnexpectedError',
},
'200': {
description: 'User avatar found.',
content: {
'image/jpeg': {
schema: {
type: 'string',
format: 'binary',
},
},
},
},
'404': {
description: 'User avatar not found',
},
},
},
put: {
operationId: 'putUserAvatar',
summary: "Set user's avatar.",
parameters: [
{
in: 'header',
name: 'content-type',
required: true,
schema: {
$ref: '#/components/schemas/ContentType',
},
},
{
name: 'x-file-name',
in: 'header',
required: true,
schema: { type: 'string' },
},
{
name: 'x-file-size',
in: 'header',
required: true,
schema: { type: 'number' },
},
{
name: 'x-file-type',
in: 'header',
required: true,
schema: { type: 'string' },
},
],
requestBody: {
description: 'The input sentence',
required: true,
content: {
'image/jpeg': {
schema: {
$ref: '#/components/schemas/BinaryPayload',
},
},
'image/png': {
schema: {
$ref: '#/components/schemas/BinaryPayload',
},
},
},
},
responses: {
default: {
$ref: '#/components/responses/UnexpectedError',
},
'200': {
description: 'User avatar set.',
content: {
'image/jpeg': {
schema: {
type: 'string',
format: 'binary',
},
},
},
},
'404': {
description: 'User not found',
},
},
},
delete: {
operationId: 'deleteUserAvatar',
summary: "Ensure user's avatar is gone.",
responses: {
default: {
$ref: '#/components/responses/UnexpectedError',
},
'410': {
description: 'User avatar is gone.',
},
},
},
},
'/users/{userId}': {
get: {
operationId: 'getUser',
summary: 'Retrieve a user.',
parameters: [
{
in: 'path',
name: 'userId',
required: true,
schema: {
$ref: '#/components/schemas/UserIdSchema',
},
},
{
in: 'query',
name: 'extended',
required: true,
schema: {
type: 'boolean',
},
},
{
in: 'query',
name: 'archived',
schema: {
type: 'boolean',
},
},
],
responses: {
default: {
$ref: '#/components/responses/UnexpectedError',
},
'200': {
description: 'User found',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
id: {
type: 'number',
},
name: {
type: 'string',
},
},
},
},
'text/plain': {
schema: { type: 'string' },
},
},
},
'404': {
description: 'User not found',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/Error',
},
},
},
},
},
},
put: {
operationId: 'putUser',
summary: 'Upsert a user.',
requestBody: {
description: 'The input user',
required: true,
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/User',
},
},
'application/vnd.github+json': {
schema: {
$ref: '#/components/schemas/User',
},
},
},
},
parameters: [
{ $ref: '#/components/parameters/UserId' },
{
in: 'header',
name: 'Authorization',
required: true,
schema: {
type: 'string',
},
},
{
in: 'header',
name: 'Content-Type',
required: true,
schema: {
type: 'string',
},
},
],
responses: {
default: {
$ref: '#/components/responses/UnexpectedError',
},
'200': {
description: 'User updated',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
id: {
type: 'number',
},
name: {
type: 'string',
},
},
},
},
},
},
'400': {
$ref: '#/components/responses/BadRequest',
},
},
},
},
},
components: {
responses: {
BadRequest: {
description: 'Bad request',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/Error',
},
},
},
},
UnexpectedError: {
description: 'Unexpected error',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/Error',
},
},
},
},
},
parameters: {
UserId: {
in: 'path',
name: 'userId',
required: true,
schema: {
$ref: '#/components/schemas/UserIdSchema',
},
},
},
schemas: {
BinaryPayload: {
type: 'string',
format: 'binary',
},
UserIdSchema: {
type: 'number',
minimum: 0,
multipleOf: 1,
},
Error: {
type: 'object',
properties: {
transactionId: {
type: 'string',
},
code: {
type: 'string',
pattern: '^E_[a-zA-Z0-9_]+$',
},
},
},
User: {
type: 'object',
additionalProperties: false,
required: ['name'],
properties: {
name: {
type: 'string',
},
tree: {
$ref: '#/components/schemas/Recursive',
},
},
},
ContentType: {
type: 'string',
},
Recursive: {
type: 'object',
required: ['id', 'children'],
additionalProperties: false,
properties: {
id: { type: 'string' },
children: {
type: 'array',
items: { $ref: '#/components/schemas/Recursive' },
},
},
},
},
},
};
const DEFINITIONS = {
paths: API.paths,
components: API.components,
security: API.security,
configs: {},
};
const log = jest.fn();
const fakeValidator = () => true;
const schemaValidators = (() => fakeValidator);
const res = {};
beforeEach(() => {
log.mockReset();
});
test('should work', async () => {
const { httpTransaction, ROUTES_HANDLERS } = prepareTransaction();
const errorHandler = await initErrorHandler({
ENV,
DEBUG_NODE_ENVS,
ERRORS_DESCRIPTORS: {},
});
const queryParserBuilder = await initQueryParserBuilder({
API,
log,
});
const httpRouter = await initHTTPRouter({
ENV,
DEBUG_NODE_ENVS,
ROUTES_HANDLERS,
API,
DEFINITIONS,
BASE_PATH,
log,
httpTransaction,
errorHandler,
queryParserBuilder,
schemaValidators,
});
expect(typeof httpRouter.service).toEqual('function');
expect(httpRouter.fatalErrorPromise).toBeInstanceOf(Promise);
expect(log.mock.calls).toMatchInlineSnapshot(`
[
[
"warning",
"⌨️ - Initializing the basic query parser.",
],
[
"debug",
"🚦 - HTTP Router initialized.",
],
]
`);
});
describe('should fail', () => {
test('when the API parsing fails', async () => {
try {
const { httpTransaction, ROUTES_HANDLERS } = prepareTransaction();
const errorHandler = await initErrorHandler({
ENV,
DEBUG_NODE_ENVS,
ERRORS_DESCRIPTORS: {},
});
const queryParserBuilder = await initQueryParserBuilder({
API,
log,
});
await initHTTPRouter({
ENV,
DEBUG_NODE_ENVS,
ROUTES_HANDLERS,
API: {
info: API.info,
paths: {
'/lol': {
get: {},
},
},
},
log,
DEFINITIONS,
BASE_PATH,
httpTransaction,
errorHandler,
queryParserBuilder,
schemaValidators,
});
throw new YError('E_UNEXPECTED_SUCCESS');
}
catch (err) {
expect(err).toMatchInlineSnapshot(`[YError: E_NO_OPERATION_ID (/lol, get): E_NO_OPERATION_ID]`);
}
});
test('when operation id is lacking', async () => {
try {
const { httpTransaction, ROUTES_HANDLERS } = prepareTransaction();
const errorHandler = await initErrorHandler({
ENV,
DEBUG_NODE_ENVS,
ERRORS_DESCRIPTORS: {},
});
const queryParserBuilder = await initQueryParserBuilder({
API,
log,
});
await initHTTPRouter({
ENV,
DEBUG_NODE_ENVS,
ROUTES_HANDLERS,
API: {
openapi: API.openapi,
servers: API.servers,
info: API.info,
paths: {
'/lol': {
get: {},
},
},
},
log,
DEFINITIONS,
BASE_PATH,
httpTransaction,
errorHandler,
queryParserBuilder,
schemaValidators,
});
throw new YError('E_UNEXPECTED_SUCCESS');
}
catch (err) {
expect(err).toMatchInlineSnapshot(`[YError: E_NO_OPERATION_ID (/lol, get): E_NO_OPERATION_ID]`);
}
});
test('when operation path is bad', async () => {
try {
const errorHandler = await initErrorHandler({
ENV,
DEBUG_NODE_ENVS,
ERRORS_DESCRIPTORS: {},
});
const { httpTransaction, ROUTES_HANDLERS } = prepareTransaction();
const queryParserBuilder = await initQueryParserBuilder({
API,
log,
});
await initHTTPRouter({
ENV,
DEBUG_NODE_ENVS,
ROUTES_HANDLERS,
API: {
openapi: API.openapi,
servers: API.servers,
info: API.info,
paths: {
['lol']: {
get: {},
},
},
},
log,
DEFINITIONS,
BASE_PATH,
httpTransaction,
errorHandler,
queryParserBuilder,
schemaValidators,
});
throw new YError('E_UNEXPECTED_SUCCESS');
}
catch (err) {
expect(err).toMatchInlineSnapshot(`[YError: E_BAD_PATH (lol): E_BAD_PATH]`);
}
});
test('when a path parameter is lacking', async () => {
try {
const { httpTransaction, handler } = prepareTransaction();
const errorHandler = await initErrorHandler({
ENV,
DEBUG_NODE_ENVS,
ERRORS_DESCRIPTORS: {},
});
const queryParserBuilder = await initQueryParserBuilder({
API,
log,
});
await initHTTPRouter({
ENV,
DEBUG_NODE_ENVS,
ROUTES_HANDLERS: {
lol: handler,
},
API: {
openapi: API.openapi,
servers: API.servers,
info: API.info,
paths: {
'/{lol}': {
get: {
operationId: 'lol',
},
},
},
},
log,
DEFINITIONS,
BASE_PATH,
httpTransaction,
errorHandler,
queryParserBuilder,
schemaValidators,
});
throw new YError('E_UNEXPECTED_SUCCESS');
}
catch (err) {
expect(err).toMatchInlineSnapshot(`[YError: E_UNDECLARED_PATH_PARAMETER (/v1/{lol}, {lol}): E_UNDECLARED_PATH_PARAMETER]`);
}
});
test('when operation handler is lacking', async () => {
try {
const { httpTransaction } = prepareTransaction();
const errorHandler = await initErrorHandler({
ENV,
DEBUG_NODE_ENVS,
ERRORS_DESCRIPTORS: {},
});
const queryParserBuilder = await initQueryParserBuilder({
API,
log,
});
await initHTTPRouter({
ENV,
DEBUG_NODE_ENVS,
ROUTES_HANDLERS: {},
API,
log,
DEFINITIONS,
BASE_PATH,
httpTransaction,
errorHandler,
queryParserBuilder,
schemaValidators,
});
throw new YError('E_UNEXPECTED_SUCCESS');
}
catch (err) {
expect(err).toMatchInlineSnapshot(`[YError: E_NO_HANDLER (ping): E_NO_HANDLER]`);
}
});
test('when an operation has no name', async () => {
try {
const errorHandler = await initErrorHandler({
ENV,
DEBUG_NODE_ENVS,
ERRORS_DESCRIPTORS: {},
});
const { httpTransaction, ROUTES_HANDLERS } = prepareTransaction();
const queryParserBuilder = await initQueryParserBuilder({
API,
log,
});
await initHTTPRouter({
ENV,
DEBUG_NODE_ENVS,
ROUTES_HANDLERS,
API: {
openapi: API.openapi,
servers: API.servers,
info: API.info,
paths: {
'/lol': {
get: {
operationId: 'ping',
parameters: [
{
in: 'query',
schema: {
type: 'string',
},
},
],
},
},
},
},
log,
DEFINITIONS,
BASE_PATH,
httpTransaction,
errorHandler,
queryParserBuilder,
schemaValidators,
});
throw new YError('E_UNEXPECTED_SUCCESS');
}
catch (err) {
expect(err).toMatchInlineSnapshot(`[YError: E_BAD_PARAMETER_NAME ([object Object]): E_BAD_PARAMETER_NAME]`);
}
});
test('when an operation has no schema', async () => {
try {
const errorHandler = await initErrorHandler({
ENV,
DEBUG_NODE_ENVS,
ERRORS_DESCRIPTORS: {},
});
const { httpTransaction, ROUTES_HANDLERS } = prepareTransaction();
const queryParserBuilder = await initQueryParserBuilder({
API,
log,
});
await initHTTPRouter({
ENV,
DEBUG_NODE_ENVS,
ROUTES_HANDLERS,
API: {
openapi: API.openapi,
servers: API.servers,
info: API.info,
paths: {
'/lol': {
get: {
operationId: 'ping',
parameters: [
{
name: 'lol',
in: 'query',
},
],
},
},
},
},
log,
DEFINITIONS,
BASE_PATH,
httpTransaction,
errorHandler,
queryParserBuilder,
schemaValidators,
});
throw new YError('E_UNEXPECTED_SUCCESS');
}
catch (err) {
expect(err).toMatchInlineSnapshot(`[YError: E_PARAMETER_WITHOUT_SCHEMA (lol): E_PARAMETER_WITHOUT_SCHEMA]`);
}
});
test('when an operation has no in value', async () => {
try {
const errorHandler = await initErrorHandler({
ENV,
DEBUG_NODE_ENVS,
ERRORS_DESCRIPTORS: {},
});
const { httpTransaction, ROUTES_HANDLERS } = prepareTransaction();
const queryParserBuilder = await initQueryParserBuilder({
API,
log,
});
await initHTTPRouter({
ENV,
DEBUG_NODE_ENVS,
ROUTES_HANDLERS,
API: {
openapi: API.openapi,
servers: API.servers,
info: API.info,
paths: {
'/lol': {
get: {
operationId: 'ping',
parameters: [
{
name: 'lol',
schema: {
type: 'string',
},
},
],
},
},
},
},
log,
DEFINITIONS,
BASE_PATH,
httpTransaction,
errorHandler,
queryParserBuilder,
schemaValidators,
});
throw new YError('E_UNEXPECTED_SUCCESS');
}
catch (err) {
expect(err).toMatchInlineSnapshot(`[YError: E_UNSUPPORTED_PARAMETER_DEFINITION (lol, in, ): E_UNSUPPORTED_PARAMETER_DEFINITION]`);
}
});
});
describe('httpRouter', () => {
describe('HEAD', () => {
test('should work with an existing route', async () => {
const { httpTransaction, httpTransactionStart, httpTransactionCatch, httpTransactionEnd, handler, ROUTES_HANDLERS, } = prepareTransaction({
status: 200,
headers: {
'content-type': 'image/jpeg',
},
});
const errorHandler = await initErrorHandler({
ENV,
DEBUG_NODE_ENVS,
ERRORS_DESCRIPTORS: {},
});
const queryParserBuilder = await initQueryParserBuilder({
API,
log,
});
const httpRouter = await initHTTPRouter({
ENV,
DEBUG_NODE_ENVS,
ROUTES_HANDLERS,
API,
log,
DEFINITIONS,
BASE_PATH,
httpTransaction,
errorHandler,
queryParserBuilder,
schemaValidators,
});
const req = {
method: 'HEAD',
url: '/v1/users/1/avatar',
headers: {},
};
log.mockReset();
await httpRouter.service(req, res);
expect(handler).toBeCalled();
expect(httpTransaction).toBeCalled();
expect(httpTransactionStart).toBeCalled();
expect(httpTransactionCatch).not.toBeCalled();
expect(httpTransactionEnd).toBeCalled();
const response = await waitResponse(httpTransactionEnd.mock.calls[0][0]);
expect({
response,
handlerCalls: handler.mock.calls,
}).toMatchInlineSnapshot(`
{
"handlerCalls": [
[
{
"body": undefined,
"cookies": {},
"headers": {
"x-depth": undefined,
},
"path": {
"userId": 1,
},
"query": {
"forFriendsUserId": undefined,
},
},
{
"config": {},
"method": "head",
"operation": {
"operationId": "headUserAvatar",
"responses": {
"200": {
"description": "User avatar exists.",
},
"404": {
"description": "User avatar not found",
},
},
"summary": "Checks user's avatar existence.",
},
"path": "/v1/users/1/avatar",
},
],
],
"response": {
"headers": {
"content-type": "image/jpeg",
},
"status": 200,
},
}
`);
});
test('should work with an existing GET route', async () => {
const { httpTransaction, httpTransactionStart, httpTransactionCatch, httpTransactionEnd, handler, ROUTES_HANDLERS, } = prepareTransaction({
status: 200,
headers: {
'content-type': 'application/json',
},
body: {
id: 1,
name: 'John Doe',
},
});
const errorHandler = await initErrorHandler({
ENV,
DEBUG_NODE_ENVS,
ERRORS_DESCRIPTORS: {},
});
const queryParserBuilder = await initQueryParserBuilder({
API,
log,
});
const httpRouter = await initHTTPRouter({
ENV,
DEBUG_NODE_ENVS,
ROUTES_HANDLERS,
API,
log,
DEFINITIONS,
BASE_PATH,
httpTransaction,
errorHandler,
queryParserBuilder,
schemaValidators,
});
const req = {
method: 'HEAD',
url: '/v1/users/1?extended=true',
headers: {},
};
log.mockReset();
await httpRouter.service(req, res);
expect(handler).toBeCalled();
expect(httpTransaction).toBeCalled();
expect(httpTransactionStart).toBeCalled();
expect(httpTransactionCatch).not.toBeCalled();
expect(httpTransactionEnd).toBeCalled();
const response = await waitResponse(httpTransactionEnd.mock.calls[0][0]);
expect({
response,
handlerCalls: handler.mock.calls,
}).toMatchInlineSnapshot(`
{
"handlerCalls": [
[
{
"body": undefined,
"cookies": {},
"headers": {},
"path": {
"userId": 1,
},
"query": {
"archived": undefined,
"extended": true,
},
},
{
"config": {},
"method": "head",
"operation": {
"operationId": "getUser",
"parameters": [
{
"in": "path",
"name": "userId",
"required": true,
"schema": {
"$ref": "#/components/schemas/UserIdSchema",
},
},
{
"in": "query",
"name": "extended",
"required": true,
"schema": {
"type": "boolean",
},
},
{
"in": "query",
"name": "archived",
"schema": {
"type": "boolean",
},
},
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"id": {
"type": "number",
},
"name": {
"type": "string",
},
},
"type": "object",
},
},
"text/plain": {
"schema": {
"type": "string",
},
},
},
"description": "User found",
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error",
},
},
},
"description": "User not found",
},
"default": {
"$ref": "#/components/responses/UnexpectedError",
},
},
"summary": "Retrieve a user.",
},
"path": "/v1/users/1",
},
],
],
"response": {
"body": undefined,
"headers": {
"content-type": "application/json",
},
"status": 200,
},
}
`);
});
test('should work with a */* accept header', async () => {
const { httpTransaction, httpTransactionStart, httpTransactionCatch, httpTransactionEnd, handler, ROUTES_HANDLERS, } = prepareTransaction({
status: 200,
headers: {
'content-type': 'application/json',
},
body: {
id: 1,
name: 'John Doe',
},
});
const errorHandler = await initErrorHandler({
ENV,
DEBUG_NODE_ENVS,
ERRORS_DESCRIPTORS: {},
});
const queryParserBuilder = await initQueryParserBuilder({
API,
log,
});
const httpRouter = await initHTTPRouter({
ENV,
DEBUG_NODE_ENVS,
ROUTES_HANDLERS,
API,
log,
DEFINITIONS,
BASE_PATH,
httpTransaction,
errorHandler,
queryParserBuilder,
schemaValidators,
});
const req = {
method: 'HEAD',
url: '/v1/users/1?extended=true',
headers: {
accept: 'text/html,image/webp,image/png,*/*;q=0.8',
},
};
log.mockReset();
await httpRouter.service(req, res);
expect(handler).toBeCalled();
expect(httpTransaction).toBeCalled();
expect(httpTransactionStart).toBeCalled();
expect(httpTransactionCatch).not.toBeCalled();
expect(httpTransactionEnd).toBeCalled();
const response = await waitResponse(httpTransactionEnd.mock.calls[0][0]);
expect({
response,
handlerCalls: handler.mock.calls,
}).toMatchInlineSnapshot(`
{
"handlerCalls": [
[
{
"body": undefined,
"cookies": {},
"headers": {},
"path": {
"userId": 1,
},
"query": {
"archived": undefined,
"extended": true,
},
},
{
"config": {},
"method": "head",
"operation": {
"operationId": "getUser",
"parameters": [
{
"in": "path",
"name": "userId",
"required": true,
"schema": {
"$ref": "#/components/schemas/UserIdSchema",
},
},
{
"in": "query",
"name": "extended",
"required": true,
"schema": {
"type": "boolean",
},
},
{
"in": "query",
"name": "archived",
"schema": {
"type": "boolean",
},
},
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"id": {
"type": "number",
},
"name": {
"type": "string",
},
},
"type": "object",
},
},
"text/plain": {
"schema": {
"type": "string",
},
},
},
"description": "User found",
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error",
},
},
},
"description": "User not found",
},
"default": {
"$ref": "#/components/responses/UnexpectedError",
},
},
"summary": "Retrieve a user.",
},
"path": "/v1/users/1",
},
],
],
"response": {
"body": undefined,
"headers": {
"content-type": "application/json",
},
"status": 200,
},
}
`);
});
});
describe('GET', () => {
describe('should work', () => {
test('with an existing stringified route', async () => {
const { httpTransaction, httpTransactionStart, httpTransactionCatch, httpTransactionEnd, handler, ROUTES_HANDLERS, } = prepareTransaction({
status: 200,
headers: {
'content-type': 'application/json',
},
body: {
id: 1,
name: 'John Doe',
},
});
const errorHandler = await initErrorHandler({
ENV,
DEBUG_NODE_ENVS,
ERRORS_DESCRIPTORS: {},
});
const queryParserBuilder = await initQueryParserBuilder({
API,
log,
});
const httpRouter = await initHTTPRouter({
ENV,
DEBUG_NODE_ENVS,
ROUTES_HANDLERS,
API,
log,
DEFINITIONS,
BASE_PATH,
httpTransaction,
errorHandler,
queryParserBuilder,
schemaValidators,
});
const req = {
method: 'GET',
url: '/v1/users/1?extended=true',
headers: {},
};
log.mockReset();
await httpRouter.service(req, res);
expect(handler).toBeCalled();
expect(httpTransaction).toBeCalled();
expect(httpTransactionStart).toBeCalled();
expect(httpTransactionCatch).not.toBeCalled();
expect(httpTransactionEnd).toBeCalled();
const response = await waitResponse(httpTransactionEnd.mock.calls[0][0]);
expect({
response,
handlerCalls: handler.mock.calls,
}).toMatchInlineSnapshot(`
{
"handlerCalls": [
[
{
"body": undefined,
"cookies": {},
"headers": {},
"path": {
"userId": 1,
},
"query": {
"archived": undefined,
"extended": true,
},
},
{
"config": {},
"method": "get",
"operation": {
"operationId": "getUser",
"parameters": [
{
"in": "path",
"name": "userId",
"required": true,
"schema": {
"$ref": "#/components/schemas/UserIdSchema",
},
},
{
"in": "query",
"name": "extended",
"required": true,
"schema": {
"type": "boolean",
},
},
{
"in": "query",
"name": "archived",
"schema": {
"type": "boolean",
},
},
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"id": {
"type": "number",
},
"name": {
"type": "string",
},
},
"type": "object",
},
},
"text/plain": {
"schema": {
"type": "string",
},
},
},
"description": "User found",
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error",
},
},
},
"description": "User not found",
},
"default": {
"$ref": "#/components/responses/UnexpectedError",
},
},
"summary": "Retrieve a user.",
},
"path": "/v1/users/1",
},
],
],
"response": {
"body": "{"id":1,"name":"John Doe"}",
"headers": {
"content-type": "application/json",
},
"status": 200,
},
}
`);
});
test('with an existing streamed route', async () => {
const { httpTransaction, httpTransactionStart, httpTransactionCatch, httpTransactionEnd, handler, ROUTES_HANDLERS, } = prepareTransaction({
status: 200,
headers: {
'content-type': 'image/jpeg',
},
body: streamtest.fromChunks([
Buffer.from('he'),
Buffer.from('llo'),
]),
});
const errorHandler = await initErrorHandler({
ENV,
DEBUG_NODE_ENVS,
ERRORS_DESCRIPTORS: {},
});
const queryParserBuilder = await initQueryParserBuilder({
API,