generated from bcgov/quickstart-openshift
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* Add fsp-tracker module and service code. * Remove unnecessary code. * Use PositiveIntPipe * Minor rename folders/files. * Minor method argument adjustment. * Refactoring. * Add tests * Fix data return type. Minor variable renamed. --------- Co-authored-by: Derek Roberts <[email protected]>
- Loading branch information
1 parent
571d1ed
commit c35e2c4
Showing
8 changed files
with
292 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { ProjectsByFspExternalModule } from '@src/app/modules/external/projects-by-fsp/projects-by-fsp.module'; | ||
|
||
/** | ||
* "External" module provides external facing APIs for other system to | ||
* interface with FOM. | ||
*/ | ||
@Module({ | ||
imports: [ | ||
ProjectsByFspExternalModule | ||
] | ||
}) | ||
export class ExternalModule {} |
39 changes: 39 additions & 0 deletions
39
api/src/app/modules/external/projects-by-fsp/projects-by-fsp.controller.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { ArgumentMetadata, BadRequestException, Controller, Get, HttpStatus, Injectable, PipeTransform, Query } from '@nestjs/common'; | ||
import { ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger'; | ||
import { ProjectByFspResponse as ProjectsByFspResponse } from '@src/app/modules/external/projects-by-fsp/projects-by-fsp.dto'; | ||
import { ProjectsByFspService } from '@src/app/modules/external/projects-by-fsp/projects-by-fsp.service'; | ||
import { PinoLogger } from 'nestjs-pino'; | ||
|
||
// Custom pipe transformer for controller parameter conversion. | ||
@Injectable() | ||
export class PositiveIntPipe implements PipeTransform<number, number> { | ||
transform(value: any, metadata: ArgumentMetadata) { | ||
|
||
if(/^\d+$/.test(value)) { | ||
const intValue = parseInt(value); | ||
if ( intValue > 0) { | ||
return value; | ||
} | ||
} | ||
|
||
throw new BadRequestException('Value must be positive integer.'); | ||
} | ||
} | ||
|
||
@ApiTags("external") | ||
@Controller("external") | ||
export class ProjectsByFspController { | ||
constructor( | ||
private readonly service: ProjectsByFspService, | ||
private readonly _logger: PinoLogger) { | ||
} | ||
|
||
@Get("fom-by-fsp") | ||
@ApiQuery({ name: 'fspId', required: true}) | ||
@ApiResponse({ status: HttpStatus.OK, type: [ProjectsByFspResponse] }) | ||
async findByFsp( | ||
@Query('fspId', PositiveIntPipe) fspId: number | ||
): Promise<ProjectsByFspResponse[]> { | ||
return this.service.findByFspId(fspId); | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
api/src/app/modules/external/projects-by-fsp/projects-by-fsp.dto.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; | ||
import { ForestClientResponse } from "@src/app/modules/forest-client/forest-client.dto"; | ||
|
||
export class ProjectByFspResponse { | ||
|
||
@ApiProperty() | ||
fomId: number; | ||
|
||
@ApiProperty() | ||
name: string; | ||
|
||
@ApiProperty() | ||
fspId: number; | ||
|
||
@ApiPropertyOptional() | ||
forestClient: ForestClientResponse; | ||
} |
18 changes: 18 additions & 0 deletions
18
api/src/app/modules/external/projects-by-fsp/projects-by-fsp.module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { TypeOrmModule } from '@nestjs/typeorm'; | ||
import { ProjectsByFspController } from '@src/app/modules/external/projects-by-fsp/projects-by-fsp.controller'; | ||
import { ProjectsByFspService } from '@src/app/modules/external/projects-by-fsp/projects-by-fsp.service'; | ||
import { ForestClientModule } from '@src/app/modules/forest-client/forest-client.module'; | ||
import { Project } from '@src/app/modules/project/project.entity'; | ||
import { ProjectModule } from '@src/app/modules/project/project.module'; | ||
|
||
@Module({ | ||
imports: [ | ||
TypeOrmModule.forFeature([Project]), | ||
ProjectModule, | ||
ForestClientModule | ||
], | ||
controllers: [ProjectsByFspController], | ||
providers: [ProjectsByFspService] | ||
}) | ||
export class ProjectsByFspExternalModule {} |
135 changes: 135 additions & 0 deletions
135
api/src/app/modules/external/projects-by-fsp/projects-by-fsp.service.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import { Test, TestingModule } from "@nestjs/testing"; | ||
import { getRepositoryToken } from "@nestjs/typeorm"; | ||
import { ProjectByFspResponse } from "@src/app/modules/external/projects-by-fsp/projects-by-fsp.dto"; | ||
import { ProjectsByFspService } from "@src/app/modules/external/projects-by-fsp/projects-by-fsp.service"; | ||
import { ForestClientService } from "@src/app/modules/forest-client/forest-client.service"; | ||
import { Project } from "@src/app/modules/project/project.entity"; | ||
import { RecursivePartial } from "@src/core/utils"; | ||
import { PinoLogger } from "nestjs-pino"; | ||
import { DataSource, Repository } from "typeorm"; | ||
|
||
describe('ProjectsByFspService', () => { | ||
let service: ProjectsByFspService; | ||
let forestClientService: ForestClientService; | ||
let repository: Repository<Project>; | ||
let createQueryBuilderMock: any; | ||
|
||
// service and dependencies setup. | ||
beforeAll(async () => { | ||
const moduleRef: TestingModule = await Test.createTestingModule({ | ||
providers: provideDependencyMock() | ||
}).compile(); | ||
|
||
forestClientService = moduleRef.get<ForestClientService>(ForestClientService); | ||
service = moduleRef.get<ProjectsByFspService>(ProjectsByFspService); | ||
repository = moduleRef.get(getRepositoryToken(Project)); | ||
|
||
createQueryBuilderMock = { | ||
leftJoinAndSelect: () => createQueryBuilderMock, | ||
where: () => createQueryBuilderMock, | ||
andWhere: () => createQueryBuilderMock, | ||
orWhere: () => createQueryBuilderMock, | ||
addOrderBy: () => createQueryBuilderMock, | ||
limit: () => createQueryBuilderMock, | ||
getMany: () => [], // default empty | ||
}; | ||
|
||
}); | ||
|
||
// This is prerequisite to make sure services/repository are setup for testing. | ||
it('Services/repository for testing should be defined', () => { | ||
expect(service).toBeDefined(); | ||
expect(forestClientService).toBeDefined(); | ||
expect(repository).toBeDefined(); | ||
}); | ||
|
||
describe('findByFspId', () => { | ||
it('returns empty array when no fspId param', async () => { | ||
expect(await service.findByFspId(null)).toEqual([]); | ||
}); | ||
|
||
it('returns empty array when query builder result is empty.', async () => { | ||
// use default createQueryBuilderMock | ||
const createQueryBuilderSpy = jest.spyOn(repository, 'createQueryBuilder').mockImplementation(() => createQueryBuilderMock); | ||
const fspIdWithNoFom = 999; | ||
expect(await service.findByFspId(fspIdWithNoFom)).toEqual([]); | ||
expect(createQueryBuilderSpy).toHaveBeenCalled(); | ||
}); | ||
|
||
it('returns correct result when query builder finds records.', async () => { | ||
const foundProjects = getSimpleProjectResponseData(); | ||
createQueryBuilderMock.getMany = jest.fn().mockReturnValue(foundProjects); | ||
const createQueryBuilderSpy = jest.spyOn(repository, 'createQueryBuilder').mockImplementation(() => createQueryBuilderMock); | ||
const forestClientServiceConvertEntitySpy = jest.spyOn(forestClientService, 'convertEntity'); | ||
const fspIdWithFom = 11; | ||
const result = await service.findByFspId(fspIdWithFom); | ||
expect(result.length).toEqual(getSimpleProjectResponseData().length); | ||
expect(result[0]).toBeInstanceOf(ProjectByFspResponse) | ||
expect(createQueryBuilderSpy).toHaveBeenCalled(); | ||
expect(forestClientServiceConvertEntitySpy).toHaveBeenCalled(); | ||
expect(result[0].fspId).toEqual(foundProjects[0].fspId) | ||
}); | ||
}); | ||
|
||
}); | ||
|
||
export class ProjectRepositoryFake { | ||
public createQueryBuilder(): void { | ||
// This is intentional for empty body. | ||
} | ||
} | ||
|
||
function provideDependencyMock(): Array<any> { | ||
const dependencyMock = | ||
[ ProjectsByFspService, | ||
{ | ||
provide: getRepositoryToken(Project), | ||
useClass: ProjectRepositoryFake | ||
}, | ||
{ | ||
provide: PinoLogger, | ||
useValue: { | ||
info: jest.fn((x) => x), | ||
debug: jest.fn((x) => x), | ||
setContext: jest.fn((x) => x), | ||
} | ||
}, | ||
{ | ||
provide: ForestClientService, | ||
useValue: { | ||
convertEntity: jest.fn() | ||
} | ||
}, | ||
{ | ||
provide: DataSource, | ||
useValue: { | ||
getRepository: jest.fn() | ||
} | ||
} | ||
]; | ||
return dependencyMock; | ||
} | ||
|
||
function getSimpleProjectResponseData(): RecursivePartial<Project>[] { | ||
const data = [ | ||
{ | ||
"id": 1, | ||
"name": "Project #1", | ||
"fspId": 11, | ||
"forestClient": { | ||
"id": "00001012", | ||
"name": "CLIENT #1" | ||
} | ||
}, | ||
{ | ||
"id": 2, | ||
"name": "Project #2", | ||
"fspId": 11, | ||
"forestClient": { | ||
"id": "00001015", | ||
"name": "CLIENT #2" | ||
} | ||
} | ||
] | ||
return data; | ||
} |
58 changes: 58 additions & 0 deletions
58
api/src/app/modules/external/projects-by-fsp/projects-by-fsp.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { ProjectByFspResponse } from "@api-modules/external/projects-by-fsp/projects-by-fsp.dto"; | ||
import { Project } from "@api-modules/project/project.entity"; | ||
import { DataService } from "@core"; | ||
import { Injectable } from "@nestjs/common"; | ||
import { InjectRepository } from "@nestjs/typeorm"; | ||
import { ForestClientService } from "@src/app/modules/forest-client/forest-client.service"; | ||
import { ProjectFindCriteria } from "@src/app/modules/project/project.service"; | ||
import { PinoLogger } from "nestjs-pino"; | ||
import { Repository } from "typeorm"; | ||
import _ = require('lodash'); | ||
|
||
@Injectable() | ||
export class ProjectsByFspService extends DataService<Project, Repository<Project>, ProjectByFspResponse> { | ||
|
||
constructor( | ||
@InjectRepository(Project) | ||
repository: Repository<Project>, | ||
private forestClientService: ForestClientService, | ||
logger: PinoLogger | ||
) { | ||
super(repository, new Project(), logger); | ||
} | ||
|
||
async findByFspId(fspId: number):Promise<ProjectByFspResponse[]> { | ||
if (_.isNil(fspId)) { | ||
return [] | ||
} | ||
const findCriteria: ProjectFindCriteria = new ProjectFindCriteria(); | ||
findCriteria.fspId = fspId; | ||
this.logger.debug('Find criteria: %o', findCriteria); | ||
|
||
const query = this.repository.createQueryBuilder("p") | ||
.leftJoinAndSelect("p.forestClient", "forestClient") | ||
.addOrderBy('p.project_id', 'DESC'); | ||
findCriteria.applyFindCriteria(query); | ||
query.limit(2500); // Can't use take(). Limit # of results to avoid system strain. | ||
|
||
const queryResults:Project[] = await query.getMany(); | ||
if (queryResults && queryResults.length > 0) { | ||
this.logger.debug(`${queryResults.length} project(s) found.`); | ||
return queryResults.map(project => this.convertEntity(project)); | ||
} | ||
this.logger.debug('No result found.'); | ||
return []; | ||
} | ||
|
||
convertEntity(entity: Project): ProjectByFspResponse { | ||
const response = new ProjectByFspResponse(); | ||
response.fomId = entity.id | ||
response.name = entity.name | ||
response.fspId = entity.fspId; | ||
if (entity.forestClient != null) { | ||
response.forestClient = this.forestClientService.convertEntity(entity.forestClient); | ||
} | ||
return response; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters