Skip to content

Commit

Permalink
feature: init campaign application controller (podkrepi-bg#647)
Browse files Browse the repository at this point in the history
- add controller using nest generator
- add tests for the initial controller methods
- add @podkrepi-bg/testing module and autoSpy exported from it
  • Loading branch information
gparlakov committed Jun 30, 2024
1 parent efdc309 commit 0c5e79f
Show file tree
Hide file tree
Showing 12 changed files with 277 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -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<CampaignApplicationService>

beforeEach(async () => {
service = autoSpy(CampaignApplicationService)

const module: TestingModule = await Test.createTestingModule({
controllers: [CampaignApplicationController],
providers: [{ provide: CampaignApplicationService, useValue: service }],
}).compile()

controller = module.get<CampaignApplicationController>(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', {})
})
})
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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 {}
Original file line number Diff line number Diff line change
@@ -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>(CampaignApplicationService)
})

it('should be defined', () => {
expect(service).toBeDefined()
})
})
26 changes: 26 additions & 0 deletions apps/api/src/campaign-application/campaign-application.service.ts
Original file line number Diff line number Diff line change
@@ -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`
}
}
Original file line number Diff line number Diff line change
@@ -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' }] },
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/swagger'
import { CreateCampaignApplicationDto } from './create-campaign-application.dto'

export class UpdateCampaignApplicationDto extends PartialType(CreateCampaignApplicationDto) {}
52 changes: 52 additions & 0 deletions libs/testing/src/auto-spy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/** Create an object with methods that are autoSpy-ed to use as mock dependency */
export function autoSpy<T>(obj: new (...args: any[]) => T): SpyOf<T> {
const res: SpyOf<T> = {} 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<keyof T>),
...(Object.getOwnPropertyNames(obj.prototype) as Array<keyof T>),
])

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> = T & {
[k in keyof T]: T[k] extends (...args: any[]) => infer R ? T[k] & jest.Mock<R> : T[k]
}
1 change: 1 addition & 0 deletions libs/testing/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './auto-spy'
9 changes: 9 additions & 0 deletions libs/testing/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"declaration": true,
"outDir": "../../dist/libs/testing"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
}
16 changes: 15 additions & 1 deletion nest-cli.json
Original file line number Diff line number Diff line change
@@ -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
}
}
3 changes: 2 additions & 1 deletion tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down

0 comments on commit 0c5e79f

Please sign in to comment.