UNPKG

@getanthill/datastore

Version:

Event-Sourced Datastore

320 lines (268 loc) 8.41 kB
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); }); }); });