From e55fe2b00445545461b002aeb38ede73d3f13d3f Mon Sep 17 00:00:00 2001 From: Georgi Parlakov Date: Thu, 20 Jun 2024 20:03:56 +0300 Subject: [PATCH] feature: init campaign application controller - add controller using nest generator - add tests for the initial controller methods - add @podkrepi-bg/testing module and autoSpy exported from it --- .../campaign-application.controller.spec.ts | 71 +++++++++++++++++++ .../campaign-application.controller.ts | 32 +++++++++ .../campaign-application.module.ts | 9 +++ .../campaign-application.service.spec.ts | 18 +++++ .../campaign-application.service.ts | 26 +++++++ .../dto/create-campaign-application.dto.ts | 38 ++++++++++ .../dto/update-campaign-application.dto.ts | 4 ++ libs/testing/src/auto-spy.ts | 52 ++++++++++++++ libs/testing/src/index.ts | 1 + libs/testing/tsconfig.lib.json | 9 +++ nest-cli.json | 16 ++++- tsconfig.base.json | 3 +- 12 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 apps/api/src/campaign-application/campaign-application.controller.spec.ts create mode 100644 apps/api/src/campaign-application/campaign-application.controller.ts create mode 100644 apps/api/src/campaign-application/campaign-application.module.ts create mode 100644 apps/api/src/campaign-application/campaign-application.service.spec.ts create mode 100644 apps/api/src/campaign-application/campaign-application.service.ts create mode 100644 apps/api/src/campaign-application/dto/create-campaign-application.dto.ts create mode 100644 apps/api/src/campaign-application/dto/update-campaign-application.dto.ts create mode 100644 libs/testing/src/auto-spy.ts create mode 100644 libs/testing/src/index.ts create mode 100644 libs/testing/tsconfig.lib.json diff --git a/apps/api/src/campaign-application/campaign-application.controller.spec.ts b/apps/api/src/campaign-application/campaign-application.controller.spec.ts new file mode 100644 index 000000000..37bc44cdb --- /dev/null +++ b/apps/api/src/campaign-application/campaign-application.controller.spec.ts @@ -0,0 +1,71 @@ +import { Test, TestingModule } from '@nestjs/testing' +import { CampaignApplicationController } from './campaign-application.controller' +import { CampaignApplicationService } from './campaign-application.service' +import { SpyOf, autoSpy } from '@podkrepi-bg/testing' + +describe('CampaignApplicationController', () => { + let controller: CampaignApplicationController + let service: SpyOf + + beforeEach(async () => { + service = autoSpy(CampaignApplicationService) + + const module: TestingModule = await Test.createTestingModule({ + controllers: [CampaignApplicationController], + providers: [{ provide: CampaignApplicationService, useValue: service }], + }).compile() + + controller = module.get(CampaignApplicationController) + }) + + it('should be defined', () => { + expect(controller).toBeDefined() + }) + + it('when create called it should delegate to the service create', () => { + // arrange + // act + controller.create({ + acceptTermsAndConditions: true, + personalInformationProcessingAccepted: true, + transparencyTermsAccepted: true, + title: 'new ', + toEntity: jest.fn(), + }) + + // assert + expect(service.create).toHaveBeenCalledWith({ + acceptTermsAndConditions: true, + personalInformationProcessingAccepted: true, + transparencyTermsAccepted: true, + title: 'new ', + toEntity: expect.any(Function), + }) + }) + + it('when findAll called it should delegate to the service findAll', () => { + // arrange + // act + controller.findAll() + + // assert + expect(service.findAll).toHaveBeenCalledWith() + }) + it('when findOne called it should delegate to the service findOne', () => { + // arrange + // act + controller.findOne('id') + + // assert + expect(service.findOne).toHaveBeenCalledWith('id') + }) + + it('when update called it should delegate to the service update', () => { + // arrange + // act + controller.update('1', {}) + + // assert + expect(service.update).toHaveBeenCalledWith('1', {}) + }) +}) diff --git a/apps/api/src/campaign-application/campaign-application.controller.ts b/apps/api/src/campaign-application/campaign-application.controller.ts new file mode 100644 index 000000000..4efdbc6d0 --- /dev/null +++ b/apps/api/src/campaign-application/campaign-application.controller.ts @@ -0,0 +1,32 @@ +import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common' +import { CampaignApplicationService } from './campaign-application.service' +import { CreateCampaignApplicationDto } from './dto/create-campaign-application.dto' +import { UpdateCampaignApplicationDto } from './dto/update-campaign-application.dto' + +@Controller('campaign-application') +export class CampaignApplicationController { + constructor(private readonly campaignApplicationService: CampaignApplicationService) {} + + @Post() + create(@Body() createCampaignApplicationDto: CreateCampaignApplicationDto) { + return this.campaignApplicationService.create(createCampaignApplicationDto) + } + + @Get() + findAll() { + return this.campaignApplicationService.findAll() + } + + @Get(':id') + findOne(@Param('id') id: string) { + return this.campaignApplicationService.findOne(id) + } + + @Patch(':id') + update( + @Param('id') id: string, + @Body() updateCampaignApplicationDto: UpdateCampaignApplicationDto, + ) { + return this.campaignApplicationService.update(id, updateCampaignApplicationDto) + } +} diff --git a/apps/api/src/campaign-application/campaign-application.module.ts b/apps/api/src/campaign-application/campaign-application.module.ts new file mode 100644 index 000000000..e574e3f79 --- /dev/null +++ b/apps/api/src/campaign-application/campaign-application.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common' +import { CampaignApplicationService } from './campaign-application.service' +import { CampaignApplicationController } from './campaign-application.controller' + +@Module({ + controllers: [CampaignApplicationController], + providers: [CampaignApplicationService], +}) +export class CampaignApplicationModule {} diff --git a/apps/api/src/campaign-application/campaign-application.service.spec.ts b/apps/api/src/campaign-application/campaign-application.service.spec.ts new file mode 100644 index 000000000..42c9cb5ee --- /dev/null +++ b/apps/api/src/campaign-application/campaign-application.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing' +import { CampaignApplicationService } from './campaign-application.service' + +describe('CampaignApplicationService', () => { + let service: CampaignApplicationService + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [CampaignApplicationService], + }).compile() + + service = module.get(CampaignApplicationService) + }) + + it('should be defined', () => { + expect(service).toBeDefined() + }) +}) diff --git a/apps/api/src/campaign-application/campaign-application.service.ts b/apps/api/src/campaign-application/campaign-application.service.ts new file mode 100644 index 000000000..a3964cc5c --- /dev/null +++ b/apps/api/src/campaign-application/campaign-application.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common' +import { CreateCampaignApplicationDto } from './dto/create-campaign-application.dto' +import { UpdateCampaignApplicationDto } from './dto/update-campaign-application.dto' + +@Injectable() +export class CampaignApplicationService { + create(createCampaignApplicationDto: CreateCampaignApplicationDto) { + return 'This action adds a new campaignApplication' + } + + findAll() { + return `This action returns all campaignApplication` + } + + findOne(id: string) { + return `This action returns a #${id} campaignApplication` + } + + update(id: string, updateCampaignApplicationDto: UpdateCampaignApplicationDto) { + return `This action updates a #${id} campaignApplication` + } + + remove(id: string) { + return `This action removes a #${id} campaignApplication` + } +} diff --git a/apps/api/src/campaign-application/dto/create-campaign-application.dto.ts b/apps/api/src/campaign-application/dto/create-campaign-application.dto.ts new file mode 100644 index 000000000..1e3fe9a96 --- /dev/null +++ b/apps/api/src/campaign-application/dto/create-campaign-application.dto.ts @@ -0,0 +1,38 @@ +import { ApiProperty } from '@nestjs/swagger' +import { Prisma } from '@prisma/client' +import { Expose } from 'class-transformer' +import { IsString } from 'class-validator' + +@Expose() +export class CreateCampaignApplicationDto { + @ApiProperty() + @Expose() + @IsString() + title: string + + @ApiProperty() + @Expose() + acceptTermsAndConditions: boolean + + @ApiProperty() + @Expose() + transparencyTermsAccepted: boolean + + @ApiProperty() + @Expose() + personalInformationProcessingAccepted: boolean + + public toEntity(): Prisma.CampaignApplicationCreateInput { + return { + campaignName: this.title, + amount: '', + beneficiary: '', + goal: '', + organizerBeneficiaryRel: '', + organizerName: '', + category: 'others', + organizer: { connect: { id: 'id', personId: '' } }, + documents: { connect: [{ id: '1' }] }, + } + } +} diff --git a/apps/api/src/campaign-application/dto/update-campaign-application.dto.ts b/apps/api/src/campaign-application/dto/update-campaign-application.dto.ts new file mode 100644 index 000000000..89a2a95e5 --- /dev/null +++ b/apps/api/src/campaign-application/dto/update-campaign-application.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger' +import { CreateCampaignApplicationDto } from './create-campaign-application.dto' + +export class UpdateCampaignApplicationDto extends PartialType(CreateCampaignApplicationDto) {} diff --git a/libs/testing/src/auto-spy.ts b/libs/testing/src/auto-spy.ts new file mode 100644 index 000000000..926eae422 --- /dev/null +++ b/libs/testing/src/auto-spy.ts @@ -0,0 +1,52 @@ +/** Create an object with methods that are autoSpy-ed to use as mock dependency */ +export function autoSpy(obj: new (...args: any[]) => T): SpyOf { + const res: SpyOf = {} as any + + // turns out that in target:es2015 the methods attached to the prototype are not enumerable so Object.keys returns []. So to workaround that and keep some backwards compatibility - merge with ownPropertyNames - that disregards the enumerable property. + // the Set remove duplicate entries + const keys = new Set([ + ...(Object.keys(obj.prototype) as Array), + ...(Object.getOwnPropertyNames(obj.prototype) as Array), + ]) + + keys.forEach((key) => { + if (typeof key === 'string') { + ;(res[key] as any) = jest.fn() + } + }) + + return res +} + +/** + * Keeps the types of properties of a type but assigns type of jest.Mock to the methods. + * That way the methods can be mocked and examined for calls. + * + * @example + * + * class Service { + * property: string; + * method(): string { + * return 'test' + * }; + * } + * + * it('should carry the types (only methods should be mocked)', () => { + * // arrange + * const ser = autoSpy(Service); + * // this line would show a typescript error were it not for the type- can't assign string to jest.Mock type + * ser.property = 'for the test'; + * ser.method.mockReturnValue('test'); + * + * // act + * const res = ser.method(); + * + * // assert + * expect(ser.method).toHaveBeenCalled(); + * expect(res).toBe('test'); + * }) + * + */ +export type SpyOf = T & { + [k in keyof T]: T[k] extends (...args: any[]) => infer R ? T[k] & jest.Mock : T[k] +} diff --git a/libs/testing/src/index.ts b/libs/testing/src/index.ts new file mode 100644 index 000000000..9ca93e9e8 --- /dev/null +++ b/libs/testing/src/index.ts @@ -0,0 +1 @@ +export * from './auto-spy' diff --git a/libs/testing/tsconfig.lib.json b/libs/testing/tsconfig.lib.json new file mode 100644 index 000000000..90acf38c9 --- /dev/null +++ b/libs/testing/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": true, + "outDir": "../../dist/libs/testing" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +} diff --git a/nest-cli.json b/nest-cli.json index fbb5aa021..1ed8cbf00 100644 --- a/nest-cli.json +++ b/nest-cli.json @@ -1,3 +1,17 @@ { - "sourceRoot": "apps/api/src" + "sourceRoot": "apps/api/src", + "projects": { + "testing": { + "type": "library", + "root": "libs/testing", + "entryFile": "index", + "sourceRoot": "libs/testing/src", + "compilerOptions": { + "tsConfigPath": "libs/testing/tsconfig.lib.json" + } + } + }, + "compilerOptions": { + "webpack": true + } } diff --git a/tsconfig.base.json b/tsconfig.base.json index 15df1939b..b230dcb8a 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -16,7 +16,8 @@ "skipDefaultLibCheck": true, "baseUrl": ".", "paths": { - "@podkrepi-bg/podkrepi-types": ["libs/podkrepi-types/src/index.ts"] + "@podkrepi-bg/podkrepi-types": ["libs/podkrepi-types/src/index.ts"], + "@podkrepi-bg/testing": ["libs/testing/src/index.ts"] } }, "exclude": ["node_modules", "tmp"]