@getanthill/datastore
Version:
Event-Sourced Datastore
320 lines (268 loc) • 8.41 kB
text/typescript
import express from 'express';
import type { OpenAPIV3 } from 'openapi-types';
import OpenApiValidator from '@getanthill/api-validators/dist/openapi';
import { SPEC_FRAGMENT } from '../spec';
import { OpenAPIMiddleware } from './OpenApi';
describe('middleware/OpenApi', () => {
let builder = async () => SPEC_FRAGMENT as OpenAPIV3.Document;
beforeEach(() => {
jest.spyOn(express, 'Router');
});
afterEach(() => {
jest.restoreAllMocks();
});
it('returns an instance to configure the middlewares', () => {
const openApi = new OpenAPIMiddleware(
{
secret: 'api-docs',
specification: SPEC_FRAGMENT as OpenAPIV3.Document,
},
null,
);
expect(openApi).toBeInstanceOf(OpenAPIMiddleware);
openApi.spec = jest.fn().mockImplementation(() => () => null);
openApi.validator.validateRequestMiddleware = jest
.fn()
.mockImplementation(() => () => null);
openApi.validateResponseMiddleware = jest
.fn()
.mockImplementation(() => () => null);
openApi.registerInputValidation();
expect(express.Router).toHaveBeenCalledTimes(1);
expect(openApi.spec).toHaveBeenCalledTimes(1);
expect(openApi.validator.validateRequestMiddleware).toHaveBeenCalledWith(
true,
);
openApi.registerOutputValidation();
expect(express.Router).toHaveBeenCalledTimes(2);
expect(openApi.validateResponseMiddleware).toHaveBeenCalledWith();
});
it('throws an exception on invalid specification', () => {
let error;
try {
const openApi = new OpenAPIMiddleware(
{
secret: 'api-docs',
specification: {} as OpenAPIV3.Document,
},
null,
);
} catch (err) {
error = err;
}
expect(error).toEqual(
OpenApiValidator.ERRORS.OPENAPI_SPECIFICATION_VALIDATION_ERROR,
);
});
it('accepts invalid specification if dangerous flag is set to `true`', () => {
let error;
try {
const openApi = new OpenAPIMiddleware(
{
secret: 'api-docs',
specification: {} as OpenAPIV3.Document,
warnOnInvalidSpecificationOnly: true,
},
null,
);
} catch (err) {
error = err;
}
expect(error).toBeUndefined();
});
it('accepts a function to rebuild the API specification', () => {
const openApi = new OpenAPIMiddleware(
{
secret: 'api-docs',
specification: SPEC_FRAGMENT as OpenAPIV3.Document,
},
builder,
);
expect(openApi.builder).toEqual(builder);
});
it('allows to update the API specification on demand', async () => {
builder = async () =>
({
...SPEC_FRAGMENT,
openapi: '3.0.1',
}) as OpenAPIV3.Document;
const openApi = new OpenAPIMiddleware(
{
secret: 'api-docs',
specification: SPEC_FRAGMENT as OpenAPIV3.Document,
},
builder,
);
// @ts-ignore
expect(openApi.validator.specification).toMatchObject({
openapi: '3.0.0',
});
await openApi.update();
// @ts-ignore
expect(openApi.validator.specification).toMatchObject({
openapi: '3.0.1',
});
});
it('registers a new middleware to update the API spec if a builder has been provided', () => {
const openApi = new OpenAPIMiddleware(
{
secret: 'api-docs',
specification: SPEC_FRAGMENT as OpenAPIV3.Document,
},
builder,
);
const router = {
get: jest.fn().mockImplementation(() => router),
use: jest.fn().mockImplementation(() => router),
};
express.Router = jest.fn().mockImplementation(() => router);
openApi.validator.spec = jest.fn().mockImplementation(() => () => null);
openApi.validator.validateRequestMiddleware = jest
.fn()
.mockImplementation(() => () => null);
openApi.registerInputValidation();
expect(router.get).toHaveBeenCalledTimes(2);
expect(router.use).toHaveBeenCalledTimes(1);
});
describe('#spec', () => {
it('responds with the current models definitions', async () => {
const openApi = new OpenAPIMiddleware(
{
secret: 'api-docs',
specification: SPEC_FRAGMENT as OpenAPIV3.Document,
},
null,
);
const middleware = openApi.spec();
const req = {
method: 'GET',
path: '/api-docs',
query: {},
};
const res = {
set: jest.fn(),
send: jest.fn(),
};
const next = jest.fn();
// @ts-ignore
await middleware(req, res, next);
expect(res.set).toHaveBeenCalledWith('content-type', 'application/json');
expect(res.send.mock.calls[0][0]).toMatchSnapshot();
});
it('responds only with models requested in query params but without tags', async () => {
const openApi = new OpenAPIMiddleware(
{
secret: 'api-docs',
specification: SPEC_FRAGMENT as OpenAPIV3.Document,
},
null,
);
// @ts-ignore
jest
.spyOn(openApi.validator, 'getSpecification')
.mockImplementation(() => ({
paths: {
'/accounts/create': {},
'/profiles/create': {},
},
}));
const middleware = openApi.spec();
const req = {
method: 'GET',
path: '/api-docs',
query: {
models: ['accounts'],
},
};
const res = {
set: jest.fn(),
send: jest.fn(),
};
const next = jest.fn();
// @ts-ignore
await middleware(req, res, next);
expect(res.set).toHaveBeenCalledWith('content-type', 'application/json');
expect(res.send.mock.calls[0][0]).toMatchSnapshot();
});
it('responds only with models requested in query params', async () => {
const openApi = new OpenAPIMiddleware(
{
secret: 'api-docs',
specification: SPEC_FRAGMENT as OpenAPIV3.Document,
},
null,
);
// @ts-ignore
jest
.spyOn(openApi.validator, 'getSpecification')
.mockImplementation(() => ({
paths: {
'/accounts/create': {},
'/profiles/create': {},
},
tags: [
{
name: 'Users',
},
{
name: 'Accounts',
},
],
}));
const middleware = openApi.spec();
const req = {
method: 'GET',
path: '/api-docs',
query: {
models: ['accounts'],
},
};
const res = {
set: jest.fn(),
send: jest.fn(),
};
const next = jest.fn();
await middleware(req, res, next);
expect(res.set).toHaveBeenCalledWith('content-type', 'application/json');
expect(res.send.mock.calls[0][0]).toMatchSnapshot();
});
});
describe('#update', () => {
it('does not update the specification if there is neither build method in class instance nor parameter passed', async () => {
const openApi = new OpenAPIMiddleware(
{
secret: 'api-docs',
specification: SPEC_FRAGMENT as OpenAPIV3.Document,
},
null,
);
openApi.updateValidator = jest.fn().mockImplementation(() => () => null);
await openApi.update();
expect(openApi.updateValidator).toHaveBeenCalledTimes(0);
});
it('updates the specification if there is the build method in class instance but no parameter passed', async () => {
const openApi = new OpenAPIMiddleware(
{
secret: 'api-docs',
specification: SPEC_FRAGMENT as OpenAPIV3.Document,
},
builder,
);
openApi.updateValidator = jest.fn().mockImplementation(() => () => null);
await openApi.update();
expect(openApi.updateValidator).toHaveBeenCalledTimes(1);
});
it('updates the specification if there is no build method in class instance but a specification parameter passed', async () => {
const openApi = new OpenAPIMiddleware(
{
secret: 'api-docs',
specification: SPEC_FRAGMENT as OpenAPIV3.Document,
},
null,
);
openApi.updateValidator = jest.fn().mockImplementation(() => () => null);
await openApi.update(SPEC_FRAGMENT);
expect(openApi.updateValidator).toHaveBeenCalledTimes(1);
});
});
});