From d06ceb5a19f4cd32f4a28ed585973b84125303db Mon Sep 17 00:00:00 2001 From: jiho-kr Date: Tue, 4 Jun 2024 13:48:32 +0900 Subject: [PATCH] fix: If a name is defined, should be used the defined name (#564) --- .../exclude-swagger-resonse-name.spec.ts | 152 ++++++++++++++++++ src/lib/crud.route.factory.ts | 8 +- 2 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 spec/exclude-swagger/exclude-swagger-resonse-name.spec.ts diff --git a/spec/exclude-swagger/exclude-swagger-resonse-name.spec.ts b/spec/exclude-swagger/exclude-swagger-resonse-name.spec.ts new file mode 100644 index 0000000..ff9faa8 --- /dev/null +++ b/spec/exclude-swagger/exclude-swagger-resonse-name.spec.ts @@ -0,0 +1,152 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { Controller, Module, type INestApplication } from '@nestjs/common'; +import { OmitType, PickType } from '@nestjs/swagger'; +import { Test } from '@nestjs/testing'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { Crud } from '../../src/lib/crud.decorator'; +import { CrudController } from '../../src/lib/interface'; +import { BaseEntity } from '../base/base.entity'; +import { BaseService } from '../base/base.service'; +import { TestHelper } from '../test.helper'; + +import type { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; +import type { DenormalizedDoc } from '@nestjs/swagger/dist/interfaces/denormalized-doc.interface'; +import type { TestingModule } from '@nestjs/testing'; + +class OmitTypeDto extends OmitType(BaseEntity, ['description']) {} +class PickTypeDto extends PickType(BaseEntity, ['name']) {} + +@Crud({ + entity: BaseEntity, + routes: { + recover: { swagger: { hide: true } }, + readOne: { swagger: { response: OmitTypeDto } }, + create: { swagger: { body: PickTypeDto } }, + update: { swagger: { response: OmitTypeDto } }, + }, +}) +@Controller('exclude-swagger') +export class ExcludeSwaggerController implements CrudController { + constructor(public readonly crudService: BaseService) {} +} + +@Module({ + imports: [TypeOrmModule.forFeature([BaseEntity])], + controllers: [ExcludeSwaggerController], + providers: [BaseService], +}) +export class ExcludeSwaggerModule {} + +describe('exclude swagger > defined name', () => { + let app: INestApplication; + let controller: ExcludeSwaggerController; + let routeSet: Record; + + beforeAll(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [ExcludeSwaggerModule, TestHelper.getTypeOrmMysqlModule([BaseEntity])], + }).compile(); + + app = moduleFixture.createNestApplication(); + controller = moduleFixture.get(ExcludeSwaggerController); + + await app.init(); + + routeSet = TestHelper.getSwaggerExplorer({ + instance: controller, + metatype: ExcludeSwaggerController, + } as InstanceWrapper); + }); + + afterAll(async () => { + await TestHelper.dropTypeOrmEntityTables(); + await app?.close(); + }); + + it('should not generate recover route in swagger', async () => { + const recover = 'post /exclude-swagger/{id}/recover'; + expect(routeSet[recover]).toBeUndefined(); + }); + + it('Should be changed swagger readOne response interface', () => { + const readOne = 'get /exclude-swagger/{id}'; + expect(routeSet[readOne].responses).toEqual({ + '200': { + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/OmitTypeDto' }, + }, + }, + description: 'Fetch one entity from Base table', + }, + '400': { + description: 'Entity that does not exist', + }, + '422': { + description: 'Invalid field', + }, + }); + expect(routeSet[readOne].root?.method).toEqual('get'); + expect(routeSet[readOne].root?.summary).toEqual("Read one from 'Base' Table"); + expect(routeSet[readOne].root?.description).toEqual("Fetch one entity in 'Base' Table"); + }); + + it('Should be changed swagger update response interface', () => { + const update = 'patch /exclude-swagger/{id}'; + expect(routeSet[update].responses).toEqual({ + '200': { + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/OmitTypeDto' }, + }, + }, + description: 'Updated ok', + }, + '404': { + description: 'Not found entity', + }, + '422': { + description: 'Invalid field', + }, + }); + expect(routeSet[update].root?.method).toEqual('patch'); + expect(routeSet[update].root?.summary).toEqual("update one in 'Base' Table"); + expect(routeSet[update].root?.description).toEqual("Update on entity in 'Base' Table"); + }); + + it('Should be changed swagger Create request body interface', () => { + const create = 'post /exclude-swagger'; + expect(routeSet[create].root).toEqual({ + method: 'post', + path: '/exclude-swagger', + operationId: 'reservedCreate', + summary: "create one to 'Base' Table", + description: "Create an entity in 'Base' Table", + parameters: [], + requestBody: { + description: 'CreateBaseDto', + required: true, + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/PickTypeDto', + anyOf: [ + { + $ref: '#/components/schemas/PickTypeDto', + }, + { + items: { + $ref: '#/components/schemas/PickTypeDto', + }, + type: 'array', + }, + ], + type: 'object', + }, + }, + }, + }, + }); + }); +}); diff --git a/src/lib/crud.route.factory.ts b/src/lib/crud.route.factory.ts index 0f2c299..dba44c6 100644 --- a/src/lib/crud.route.factory.ts +++ b/src/lib/crud.route.factory.ts @@ -352,9 +352,11 @@ export class CrudRouteFactory { } if (CRUD_POLICY[method].useBody) { const bodyType = (() => { - const routeConfig = this.crudOptions.routes?.[method]; - if (routeConfig?.swagger?.body) { - return this.generalTypeGuard(routeConfig.swagger.body, method, 'body'); + const customBody = this.crudOptions.routes?.[method]?.swagger?.body; + if (customBody) { + return ['PickTypeClass', 'OmitTypeClass'].includes(customBody.name) + ? this.generalTypeGuard(customBody, method, 'body') + : customBody; } if (method === Method.SEARCH) { return RequestSearchDto;