UNPKG

routing-controllers-openapi

Version:

Runtime OpenAPI v3 spec generation for routing-controllers

371 lines (352 loc) 10.1 kB
// tslint:disable:no-implicit-dependencies no-submodule-imports const { defaultMetadataStorage } = require('class-transformer/cjs/storage') import { validationMetadatasToSchemas } from 'class-validator-jsonschema' import _merge from 'lodash.merge' import { getMetadataArgsStorage } from 'routing-controllers' import { expressToOpenAPIPath, getFullPath, getOperationId, parseRoutes, routingControllersToSpec, } from '../src' import { getRequestBody } from '../src/generateSpec' import { RootController, UserPostsController, UsersController, } from './fixtures/controllers' // Construct OpenAPI spec: const storage = getMetadataArgsStorage() const options = { controllers: [UsersController, UserPostsController], routePrefix: '/api', } const routes = parseRoutes(storage, options) describe('index', () => { it('generates an OpenAPI spec from routing-controllers metadata', () => { // Include component schemas parsed with class-validator-jsonschema: const schemas = validationMetadatasToSchemas({ classTransformerMetadataStorage: defaultMetadataStorage, refPointerPrefix: '#/components/schemas/', }) const spec = routingControllersToSpec(storage, options, { components: { schemas, securitySchemes: { basicAuth: { scheme: 'basic', type: 'http', }, bearerAuth: { scheme: 'bearer', type: 'http', }, }, }, info: { title: 'My app', version: '1.2.0' }, }) expect(spec).toEqual(require('./fixtures/spec.json')) }) it('parses actions in declared order from controller metadata', () => { const actions = routes.map((d) => d.action) expect(actions).toEqual([ { method: 'listUsers', route: '/', target: UsersController, type: 'get', }, { method: 'listUsersInRange', route: '/:from-:to', target: UsersController, type: 'get', }, { method: 'getUser', route: '/:userId?', target: UsersController, type: 'get', }, { method: 'createUser', route: '/', target: UsersController, type: 'post', }, { method: 'createUserWithType', route: '/withType', target: UsersController, type: 'post', }, { method: 'createManyUsers', route: '/', target: UsersController, type: 'put', }, { method: 'createNestedUsers', route: '/nested', target: UsersController, type: 'post', }, { method: 'createUserPost', route: '/:userId/posts', target: UsersController, type: 'post', }, { method: 'deleteUsersByVersion', route: '/:version(v?\\d{1}|all)', target: UsersController, type: 'delete', }, { method: 'putUserDefault', route: undefined, target: UsersController, type: 'put', }, { method: 'putUserAvatar', route: '/:userId/avatar', target: UsersController, type: 'put', }, { method: 'getUserPost', route: '/:postId', target: UserPostsController, type: 'get', }, { method: 'patchUserPost', route: '/:postId', target: UserPostsController, type: 'patch', }, { method: 'createUserPostImages', route: '/:postId/images', target: UserPostsController, type: 'post', }, { method: 'getDefaultPath', route: undefined, target: RootController, type: 'get', }, { method: 'getStringPath', route: '/stringPath', target: RootController, type: 'get', }, ]) }) it('gets full OpenAPI-formatted paths', () => { const route = _merge({}, routes[0]) expect(getFullPath(route)).toEqual('/api/users/') route.options.routePrefix = undefined expect(getFullPath(route)).toEqual('/users/') route.controller.route = '' expect(getFullPath(route)).toEqual('/') route.action.route = '/all' expect(getFullPath(route)).toEqual('/all') }) it('converts Express paths into OpenAPI paths', () => { expect(expressToOpenAPIPath('')).toEqual('') expect(expressToOpenAPIPath('/')).toEqual('/') expect(expressToOpenAPIPath('123')).toEqual('123') expect(expressToOpenAPIPath('/users')).toEqual('/users') expect(expressToOpenAPIPath('/users/:userId')).toEqual('/users/{userId}') expect(expressToOpenAPIPath('/users/:userId/:from-:to')).toEqual( '/users/{userId}/{from}-{to}' ) expect(expressToOpenAPIPath('/users/:userId/:limit?')).toEqual( '/users/{userId}/{limit}' ) expect(expressToOpenAPIPath('/users/:userId(\\d+)')).toEqual( '/users/{userId}' ) expect(expressToOpenAPIPath('/users/:type(user|admin)')).toEqual( '/users/{type}' ) }) it('gets OpenAPI Operation IDs', () => { const route = _merge({}, routes[0]) expect(getOperationId(route)).toEqual('UsersController.listUsers') route.action.target = class AnotherController {} route.action.method = 'anotherMethod' expect(getOperationId(route)).toEqual('AnotherController.anotherMethod') }) }) describe('getRequestBody', () => { it('parse a single `body` metadata item into a single `object` schema', () => { const route = routes.find((d) => d.action.method === 'createUser')! expect(route).toBeDefined() expect(getRequestBody(route)).toEqual({ content: { 'application/json': { schema: { $ref: '#/components/schemas/CreateUserBody', }, }, }, description: 'CreateUserBody', required: false, }) }) it('parse a single `body` metadata item of array type into a single `object` schema', () => { const route = routes.find((d) => d.action.method === 'createManyUsers')! expect(route).toBeDefined() expect(getRequestBody(route)).toEqual({ content: { 'application/json': { schema: { items: { $ref: '#/components/schemas/CreateUserBody', }, type: 'array', }, }, }, description: 'CreateUserBody', required: true, }) }) it('parse a single `body-param` metadata item into a single `object` schema', () => { const route = routes.find((d) => d.action.method === 'patchUserPost')! expect(route).toBeDefined() expect(getRequestBody(route)).toEqual({ content: { 'application/json': { schema: { properties: { token: { type: 'string', }, }, required: [], type: 'object', }, }, }, }) }) it('combine multiple `body-param` metadata items into a single `object` schema', () => { const route = routes.find((d) => d.action.method === 'putUserDefault')! expect(route).toBeDefined() expect(getRequestBody(route)).toEqual({ content: { 'application/json': { schema: { properties: { limit: { type: 'number', }, query: { $ref: '#/components/schemas/UserQuery', }, token: { type: 'string', }, }, required: ['token'], type: 'object', }, }, }, }) }) it('wrap `body` and `body-param` metadata items under a single `allOf` schema', () => { const route = routes.find((d) => d.action.method === 'createUserPost')! expect(route).toBeDefined() expect(getRequestBody(route)).toEqual({ content: { 'application/json': { schema: { allOf: [ { $ref: '#/components/schemas/CreatePostBody', }, { properties: { token: { type: 'string', }, }, required: [], type: 'object', }, ], }, }, }, description: 'CreatePostBody', required: true, }) }) it('parse a single `UploadedFile` metadata into a single `object` schema under content-type `multipart/form-data`', () => { const route = routes.find((d) => d.action.method === 'putUserAvatar')! expect(route).toBeDefined() expect(getRequestBody(route)).toEqual({ content: { 'multipart/form-data': { schema: { properties: { image: { format: 'binary', type: 'string', }, }, required: [], type: 'object', }, }, }, }) }) it('wrap `body` and others metadata containing `UploadedFiles` items under a single `allOf` schema under content-type `multipart/form-data`', () => { const route = routes.find( (d) => d.action.method === 'createUserPostImages' )! expect(route).toBeDefined() expect(getRequestBody(route)).toEqual({ content: { 'multipart/form-data': { schema: { allOf: [ { $ref: '#/components/schemas/CreateUserPostImagesBody', }, { properties: { images: { items: { format: 'binary', type: 'string', }, type: 'array', }, token: { type: 'string', }, }, required: [], type: 'object', }, ], }, }, }, description: 'CreateUserPostImagesBody', required: true, }) }) })