diff --git a/backend/sites/src/app/controllers/site.controller.ts b/backend/sites/src/app/controllers/site.controller.ts index 996bda8d..ea64def2 100644 --- a/backend/sites/src/app/controllers/site.controller.ts +++ b/backend/sites/src/app/controllers/site.controller.ts @@ -1,6 +1,6 @@ -import { Controller, Get } from '@nestjs/common'; +import { Controller, Get, Param } from '@nestjs/common'; import { Unprotected } from 'nest-keycloak-connect'; -import { FetchSiteResponse } from '../dto/response/fetchSiteResponse'; +import { FetchSiteDetail, FetchSiteResponse } from '../dto/response/fetchSiteResponse'; import { SiteService } from '../services/site.service'; @Controller('site') @@ -11,7 +11,7 @@ export class SiteController { * Get all sites * @returns all sites */ - @Get('/') async getSubmission(): Promise { + @Get('/') async getAllSites(): Promise { const sites = await this.siteService.findAll(); if (sites?.data.length == 0) { @@ -23,4 +23,20 @@ export class SiteController { return sites; } + /** + * Get site by site Id + * @returns site matching the site id + */ + @Get('/:siteId') async getSiteBySiteId(@Param('siteId') siteId): Promise { + const site = await this.siteService.findSiteBySiteId(siteId); + + if (!site) { + return Promise.reject({ + statusCode: 404, + message: 'Site data not found', + }); + } + + return site; + } } diff --git a/backend/sites/src/app/dto/response/fetchSiteResponse.ts b/backend/sites/src/app/dto/response/fetchSiteResponse.ts index 74ab6629..f5eb1c8e 100644 --- a/backend/sites/src/app/dto/response/fetchSiteResponse.ts +++ b/backend/sites/src/app/dto/response/fetchSiteResponse.ts @@ -11,3 +11,14 @@ export class FetchSiteResponse extends BaseHttpResponse { @Field(() => [Sites]) data: Sites[]; } + +/** + * Class for returing fetch site response from graphql services + */ +@ObjectType() +export class FetchSiteDetail extends BaseHttpResponse { + @Field(() => Sites) + data: Sites; +} + + diff --git a/backend/sites/src/app/resolvers/site.resolver.spec.ts b/backend/sites/src/app/resolvers/site.resolver.spec.ts index e25cff2c..65498d55 100644 --- a/backend/sites/src/app/resolvers/site.resolver.spec.ts +++ b/backend/sites/src/app/resolvers/site.resolver.spec.ts @@ -24,6 +24,7 @@ describe('SiteResolver', () => { return result; }), searchSites: jest.fn(), + findSiteBySiteId: jest.fn(), }, }, ], @@ -46,31 +47,59 @@ describe('SiteResolver', () => { expect(siteService.findAll).toHaveBeenCalled(); }); - it('should call siteService.searchSites with the provided searchParam', () => { - const searchParam = 'example'; - siteResolver.searchSites(searchParam); - expect(siteService.searchSites).toHaveBeenCalledWith(searchParam); - }); + describe('searchSites', () => { + it('should call siteService.searchSites with the provided searchParam', () => { + const searchParam = 'example'; + siteResolver.searchSites(searchParam); + expect(siteService.searchSites).toHaveBeenCalledWith(searchParam); + }); + + it('site search matches a search parameter', async () => { + const searchParam = '123'; + const expectedFilteredSites = [sampleSites[0]]; // Only Site 1 matches the searchParam + (siteService.searchSites as jest.Mock).mockResolvedValue(expectedFilteredSites); + + const result: Sites[] = await siteResolver.searchSites(searchParam); - it('site search matches a search parameter', async () => { - const searchParam = '123'; - const expectedFilteredSites = [sampleSites[0]]; // Only Site 1 matches the searchParam - (siteService.searchSites as jest.Mock).mockResolvedValue(expectedFilteredSites); + expect(siteService.searchSites).toHaveBeenCalledWith(searchParam); + expect(result).toEqual(expectedFilteredSites); + }); - const result: Sites[] = await siteResolver.searchSites(searchParam); + it('site search has no matches with the search parameter', async () => { + const searchParam = 'example'; + const expectedFilteredSites: Sites[] = []; + (siteService.searchSites as jest.Mock).mockResolvedValue(expectedFilteredSites); - expect(siteService.searchSites).toHaveBeenCalledWith(searchParam); - expect(result).toEqual(expectedFilteredSites); + const result: Sites[] = await siteResolver.searchSites(searchParam); + + expect(siteService.searchSites).toHaveBeenCalledWith(searchParam); + expect(result).toEqual(expectedFilteredSites); + }); }); - it('site search has no matches with the search parameter', async () => { - const searchParam = 'example'; - const expectedFilteredSites: Sites[] = []; - (siteService.searchSites as jest.Mock).mockResolvedValue(expectedFilteredSites); + describe('findSiteBySiteId', () => { + it('should call siteService.findSiteBySiteId with the provided siteId', () => { + const siteId = '123'; + siteResolver.findSiteBySiteId(siteId); + expect(siteService.findSiteBySiteId).toHaveBeenCalledWith(siteId); + }); + + it('finds a matching site id', async () => { + const expectedResult = [sampleSites[0]]; + const siteId = '123'; + + (siteService.findSiteBySiteId as jest.Mock).mockResolvedValue(expectedResult); + const result = await siteResolver.findSiteBySiteId(siteId); + expect(result).toEqual(expectedResult); + }); - const result: Sites[] = await siteResolver.searchSites(searchParam); + it('has no matches with the site Id parameter', async () => { + const expectedResult = undefined + const siteId = '111'; - expect(siteService.searchSites).toHaveBeenCalledWith(searchParam); - expect(result).toEqual(expectedFilteredSites); + (siteService.findSiteBySiteId as jest.Mock).mockResolvedValue(expectedResult); + const result = await siteResolver.findSiteBySiteId(siteId); + expect(result).toEqual(expectedResult); + }); }); }); diff --git a/backend/sites/src/app/resolvers/site.resolver.ts b/backend/sites/src/app/resolvers/site.resolver.ts index a000ba8d..e07d2e82 100644 --- a/backend/sites/src/app/resolvers/site.resolver.ts +++ b/backend/sites/src/app/resolvers/site.resolver.ts @@ -31,4 +31,10 @@ export class SiteResolver { searchSites(@Args('searchParam', { type: () => String }) searchParam: string) { return this.siteService.searchSites(searchParam); } + + @Roles({ roles: ['site-admin'], mode: RoleMatchingMode.ANY }) + @Query(() => Sites, { name: 'findSiteBySiteId' }) + findSiteBySiteId(@Args('siteId', { type: () => String }) siteId: string) { + return this.siteService.findSiteBySiteId(siteId); + } } diff --git a/backend/sites/src/app/services/site.service.spec.ts b/backend/sites/src/app/services/site.service.spec.ts index 0b433a2d..b4b0215c 100644 --- a/backend/sites/src/app/services/site.service.spec.ts +++ b/backend/sites/src/app/services/site.service.spec.ts @@ -3,9 +3,11 @@ import { SiteService } from './site.service'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Repository, SelectQueryBuilder } from 'typeorm'; import { Sites } from '../entities/sites.entity'; +import { FetchSiteDetail } from '../dto/response/fetchSiteResponse'; +import { sampleSites } from '../mockData/site.mockData'; describe('SiteService', () => { - let service: SiteService; + let siteService: SiteService; let siteRepository: Repository; beforeEach(async () => { @@ -30,13 +32,16 @@ describe('SiteService', () => { { id: '121', commonName: 'duncan' }, { id: '222', commonName: 'vancouver' }]), })), + findOneOrFail: jest.fn(() => { + return { id: '123', region_name: 'victoria' }; + }), }, }, ], }).compile(); - service = module.get(SiteService); + siteService = module.get(SiteService); siteRepository = module.get>(getRepositoryToken(Sites)); }); @@ -45,58 +50,86 @@ describe('SiteService', () => { }); it('Atleast one site should be returned', async () => { - const sites = await service.findAll(); + const sites = await siteService.findAll(); expect(sites.data.length).toBe(3); }); + describe('searchSites', () => { + it('site search matches a search parameter', async () => { + // Arrange + const searchParam = 'v'; + const expectedResult = [ + { id: '123', commonName: 'victoria' }, + { id: '222', commonName: 'vancouver' }]; // Example result + const whereMock = jest.fn().mockReturnThis(); + const orWhereMock = jest.fn().mockReturnThis(); + const getManyMock = jest.fn().mockResolvedValue(expectedResult); + const siteRepositoryFindSpy = jest.spyOn(siteRepository, 'createQueryBuilder').mockReturnValue({ + where: whereMock, + orWhere: orWhereMock, + getMany: getManyMock, + } as unknown as SelectQueryBuilder); + + // Act + const result = await siteService.searchSites(searchParam); + + // Assert + expect(siteRepositoryFindSpy).toHaveBeenCalledWith('sites'); + expect(whereMock).toHaveBeenCalledWith(expect.any(String), { searchParam: `%${searchParam}%` }); + expect(orWhereMock).toHaveBeenCalledTimes(7); // Number of orWhere calls + expect(getManyMock).toHaveBeenCalled(); + expect(result).toEqual(expectedResult); + }); + + it('site search has no matches with the search parameter', async () => { + // Arrange + const searchParam = 'xyz'; + const expectedResult = []; // Example result + const whereMock = jest.fn().mockReturnThis(); + const orWhereMock = jest.fn().mockReturnThis(); + const getManyMock = jest.fn().mockResolvedValue(expectedResult); + const siteRepositoryFindSpy = jest.spyOn(siteRepository, 'createQueryBuilder').mockReturnValue({ + where: whereMock, + orWhere: orWhereMock, + getMany: getManyMock, + } as unknown as SelectQueryBuilder); + + // Act + const result = await siteService.searchSites(searchParam); - it('site search matches a search parameter', async () => { - // Arrange - const searchParam = 'v'; - const expectedResult = [ - { id: '123', commonName: 'victoria' }, - { id: '222', commonName: 'vancouver' }]; // Example result - const whereMock = jest.fn().mockReturnThis(); - const orWhereMock = jest.fn().mockReturnThis(); - const getManyMock = jest.fn().mockResolvedValue(expectedResult); - const siteRepositoryFindSpy = jest.spyOn(siteRepository, 'createQueryBuilder').mockReturnValue({ - where: whereMock, - orWhere: orWhereMock, - getMany: getManyMock, - } as unknown as SelectQueryBuilder); - - // Act - const result = await service.searchSites(searchParam); - - // Assert - expect(siteRepositoryFindSpy).toHaveBeenCalledWith('sites'); - expect(whereMock).toHaveBeenCalledWith(expect.any(String), { searchParam: `%${searchParam}%` }); - expect(orWhereMock).toHaveBeenCalledTimes(7); // Number of orWhere calls - expect(getManyMock).toHaveBeenCalled(); - expect(result).toEqual(expectedResult); + // Assert + expect(siteRepositoryFindSpy).toHaveBeenCalledWith('sites'); + expect(whereMock).toHaveBeenCalledWith(expect.any(String), { searchParam: `%${searchParam}%` }); + expect(orWhereMock).toHaveBeenCalledTimes(7); // Number of orWhere calls + expect(getManyMock).toHaveBeenCalled(); + expect(result).toEqual(expectedResult); + }); }); - it('site search has no matches with the search parameter', async () => { - // Arrange - const searchParam = 'xyz'; - const expectedResult = []; // Example result - const whereMock = jest.fn().mockReturnThis(); - const orWhereMock = jest.fn().mockReturnThis(); - const getManyMock = jest.fn().mockResolvedValue(expectedResult); - const siteRepositoryFindSpy = jest.spyOn(siteRepository, 'createQueryBuilder').mockReturnValue({ - where: whereMock, - orWhere: orWhereMock, - getMany: getManyMock, - } as unknown as SelectQueryBuilder); - - // Act - const result = await service.searchSites(searchParam); - - // Assert - expect(siteRepositoryFindSpy).toHaveBeenCalledWith('sites'); - expect(whereMock).toHaveBeenCalledWith(expect.any(String), { searchParam: `%${searchParam}%` }); - expect(orWhereMock).toHaveBeenCalledTimes(7); // Number of orWhere calls - expect(getManyMock).toHaveBeenCalled(); - expect(result).toEqual(expectedResult); + describe('findSiteBySiteId', () => { + it('should call findOneOrFail method of the repository with the provided siteId', async () => { + const siteId = '123'; + await siteService.findSiteBySiteId(siteId); + expect(siteRepository.findOneOrFail).toHaveBeenCalledWith({ where: { id: siteId } }); + }); + + it('should return the site when findOneOrFail method of the repository resolves', async () => { + const siteId = '123'; + const expectedResult: FetchSiteDetail = { httpStatusCode: 200, data: sampleSites[0] }; + (siteRepository.findOneOrFail as jest.Mock).mockResolvedValue(expectedResult); + + const result = await siteService.findSiteBySiteId(siteId); + + expect(result).toBeInstanceOf(FetchSiteDetail); + expect(result.httpStatusCode).toBe(200); + expect(result.data).toEqual(expectedResult); + }); + + it('should throw an error when findOneOrFail method of the repository rejects', async () => { + const siteId = '111'; + const error = new Error('Site not found'); + (siteRepository.findOneOrFail as jest.Mock).mockRejectedValue(error); + await expect(siteService.findSiteBySiteId(siteId)).rejects.toThrowError(error); + }); }); }); diff --git a/backend/sites/src/app/services/site.service.ts b/backend/sites/src/app/services/site.service.ts index 86bcda4b..57980360 100644 --- a/backend/sites/src/app/services/site.service.ts +++ b/backend/sites/src/app/services/site.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { FetchSiteResponse } from '../dto/response/fetchSiteResponse'; +import { FetchSiteDetail, FetchSiteResponse } from '../dto/response/fetchSiteResponse'; import { Sites } from '../entities/sites.entity'; /** @@ -48,4 +48,19 @@ export class SiteService { return sites; } + + /** + * Find sites by its ID + * @param siteId site Id + * @returns a single site matching the site ID + */ + async findSiteBySiteId(siteId: string) { + const response = new FetchSiteDetail(); + + response.httpStatusCode = 200; + + response.data = await this.siteRepository.findOneOrFail({ where: { id: siteId } }); + + return response; + } }