From 6660d9783c8862450681bdcacac90eebf9d936bb Mon Sep 17 00:00:00 2001 From: Graham Stewart Date: Tue, 29 Aug 2023 10:07:30 -0700 Subject: [PATCH] Added tests for critters and lookups paths --- .../paths/critter-data/critters/post.test.ts | 62 +++++++++++++++++ api/src/paths/critter-data/critters/post.ts | 12 +++- .../critter-data/critters/{critterId}.test.ts | 68 +++++++++++++++++++ .../critter-data/critters/{critterId}.ts | 11 ++- .../paths/critter-data/lookups/{key}.test.ts | 53 +++++++++++++++ api/src/paths/critter-data/lookups/{key}.ts | 12 +++- 6 files changed, 210 insertions(+), 8 deletions(-) create mode 100644 api/src/paths/critter-data/critters/post.test.ts create mode 100644 api/src/paths/critter-data/critters/{critterId}.test.ts create mode 100644 api/src/paths/critter-data/lookups/{key}.test.ts diff --git a/api/src/paths/critter-data/critters/post.test.ts b/api/src/paths/critter-data/critters/post.test.ts new file mode 100644 index 0000000000..525a3795b5 --- /dev/null +++ b/api/src/paths/critter-data/critters/post.test.ts @@ -0,0 +1,62 @@ +import Ajv from 'ajv'; +import chai, { expect } from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import { CritterbaseService, IBulkCreate } from '../../../services/critterbase-service'; +import { getRequestHandlerMocks } from '../../../__mocks__/db'; +import * as createCritter from './post'; + +chai.use(sinonChai); + +describe('paths/critter-data/critters/post', () => { + const ajv = new Ajv(); + + it('is valid openapi v3 schema', () => { + expect(ajv.validateSchema((createCritter.POST.apiDoc as unknown) as object)).to.be.true; + }); + + const payload: IBulkCreate = { + critters: [], + captures: [], + mortality: [], + locations: [], + markings: [], + measurement_quantitative: [], + measurement_qualitative: [], + family: [] + }; + + describe('createCritter', () => { + afterEach(() => { + sinon.restore(); + }); + it('should succeed', async () => { + const mockCreateCritter = sinon.stub(CritterbaseService.prototype, 'createCritter').resolves({ count: 0 }); + const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); + mockReq.body = payload; + const requestHandler = createCritter.createCritter(); + + await requestHandler(mockReq, mockRes, mockNext); + + expect(mockCreateCritter.calledOnce).to.be.true; + expect(mockCreateCritter).calledWith(payload); + expect(mockRes.statusValue).to.equal(200); + expect(mockRes.json.calledWith({ count: 0 })).to.be.true; + }); + it('should fail', async () => { + const mockError = new Error('mock error'); + const mockCreateCritter = sinon.stub(CritterbaseService.prototype, 'createCritter').rejects(mockError); + + const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); + const requestHandler = createCritter.createCritter(); + + try { + await requestHandler(mockReq, mockRes, mockNext); + expect.fail(); + } catch (actualError) { + expect(actualError).to.equal(mockError); + expect(mockCreateCritter.calledOnce).to.be.true; + } + }); + }); +}); diff --git a/api/src/paths/critter-data/critters/post.ts b/api/src/paths/critter-data/critters/post.ts index c859ff261e..9fb8379848 100644 --- a/api/src/paths/critter-data/critters/post.ts +++ b/api/src/paths/critter-data/critters/post.ts @@ -3,9 +3,10 @@ import { Operation } from 'express-openapi'; import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../constants/roles'; import { authorizeRequestHandler } from '../../../request-handlers/security/authorization'; import { CritterbaseService, ICritterbaseUser } from '../../../services/critterbase-service'; +import { getLogger } from '../../../utils/logger'; // TODO: Put this all into an existing endpoint - +const defaultLog = getLogger('paths/critter-data/critters') export const POST: Operation = [ authorizeRequestHandler((req) => { return { @@ -150,7 +151,12 @@ export function createCritter(): RequestHandler { }; const cb = new CritterbaseService(user); - const result = await cb.createCritter(req.body); - return res.status(200).json(result); + try { + const result = await cb.createCritter(req.body); + return res.status(200).json(result); + } catch (error) { + defaultLog.error({ label: 'createCritter', message: 'error', error }); + throw error; + } }; } diff --git a/api/src/paths/critter-data/critters/{critterId}.test.ts b/api/src/paths/critter-data/critters/{critterId}.test.ts new file mode 100644 index 0000000000..8bab9ca13b --- /dev/null +++ b/api/src/paths/critter-data/critters/{critterId}.test.ts @@ -0,0 +1,68 @@ +import Ajv from 'ajv'; +import chai, { expect } from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import { CritterbaseService } from '../../../services/critterbase-service'; +import { getRequestHandlerMocks } from '../../../__mocks__/db'; +import * as critter from './{critterId}'; + +chai.use(sinonChai); + +describe('paths/critter-data/critters/{critterId}', () => { + const ajv = new Ajv(); + + it('is valid openapi v3 schema', () => { + expect(ajv.validateSchema((critter.GET.apiDoc as unknown) as object)).to.be.true; + }); + + const mockCritter = { + critter_id: 'asdf', + wlh_id: '17-10748', + animal_id: '6', + sex: 'Female', + taxon: 'Caribou', + collection_units: [ + { + category_name: 'Population Unit', + unit_name: 'Itcha-Ilgachuz', + collection_unit_id: '0284c4ca-a279-4135-b6ef-d8f4f8c3d1e6', + collection_category_id: '9dcf05a8-9bfe-421b-b487-ce65299441ca' + } + ], + mortality_timestamp: new Date() + }; + + describe('getCritter', async () => { + afterEach(() => { + sinon.restore(); + }); + it('should succeed', async () => { + const mockGetCritter = sinon.stub(CritterbaseService.prototype, 'getCritter').resolves(mockCritter); + const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); + mockReq.params = { critterId: 'asdf' }; + const requestHandler = critter.getCritter(); + + await requestHandler(mockReq, mockRes, mockNext); + + expect(mockGetCritter.calledOnce).to.be.true; + expect(mockGetCritter).calledWith('asdf'); + expect(mockRes.statusValue).to.equal(200); + expect(mockRes.json.calledWith(mockCritter)).to.be.true; + }); + it('should fail', async () => { + const mockError = new Error('mock error'); + const mockGetCritter = sinon.stub(CritterbaseService.prototype, 'getCritter').rejects(mockError); + + const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); + const requestHandler = critter.getCritter(); + + try { + await requestHandler(mockReq, mockRes, mockNext); + expect.fail(); + } catch (actualError) { + expect(actualError).to.equal(mockError); + expect(mockGetCritter.calledOnce).to.be.true; + } + }); + }); +}); diff --git a/api/src/paths/critter-data/critters/{critterId}.ts b/api/src/paths/critter-data/critters/{critterId}.ts index ae5f2303cf..d7da8aae37 100644 --- a/api/src/paths/critter-data/critters/{critterId}.ts +++ b/api/src/paths/critter-data/critters/{critterId}.ts @@ -3,8 +3,10 @@ import { Operation } from 'express-openapi'; import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../constants/roles'; import { authorizeRequestHandler } from '../../../request-handlers/security/authorization'; import { CritterbaseService, ICritterbaseUser } from '../../../services/critterbase-service'; +import { getLogger } from '../../../utils/logger'; // TODO: Put this all into an existing endpoint +const defaultLog = getLogger('paths/critter-data/critters'); export const GET: Operation = [ authorizeRequestHandler((req) => { @@ -81,7 +83,12 @@ export function getCritter(): RequestHandler { }; const cb = new CritterbaseService(user); - const result = await cb.getCritter(req.params.critterId); - return res.status(200).json(result); + try { + const result = await cb.getCritter(req.params.critterId); + return res.status(200).json(result); + } catch (error) { + defaultLog.error({ label: 'getCritter', message: 'error', error }); + throw error; + } }; } diff --git a/api/src/paths/critter-data/lookups/{key}.test.ts b/api/src/paths/critter-data/lookups/{key}.test.ts new file mode 100644 index 0000000000..d6764309e2 --- /dev/null +++ b/api/src/paths/critter-data/lookups/{key}.test.ts @@ -0,0 +1,53 @@ +import Ajv from 'ajv'; +import chai, { expect } from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import { CritterbaseService } from '../../../services/critterbase-service'; +import { getRequestHandlerMocks } from '../../../__mocks__/db'; +import * as key from './{key}'; + +chai.use(sinonChai); + +describe('paths/critter-data/lookups/{key}', () => { + const ajv = new Ajv(); + + it('is valid openapi v3 schema', () => { + expect(ajv.validateSchema((key.GET.apiDoc as unknown) as object)).to.be.true; + }); + + const mockSelectOptions = [{ key: 'a', value: 'a', id: 'a' }]; + + describe('getSelectOptions', () => { + afterEach(() => { + sinon.restore(); + }); + it('should succeed', async () => { + const mockGetLookupValues = sinon + .stub(CritterbaseService.prototype, 'getLookupValues') + .resolves(mockSelectOptions); + const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); + const requestHandler = key.getLookupValues(); + + await requestHandler(mockReq, mockRes, mockNext); + + expect(mockGetLookupValues.calledOnce).to.be.true; + expect(mockRes.statusValue).to.equal(200); + expect(mockRes.json.calledWith(mockSelectOptions)).to.be.true; + }); + it('should fail', async () => { + const mockError = new Error('mock error'); + const mockGetLookupValues = sinon.stub(CritterbaseService.prototype, 'getLookupValues').rejects(mockError); + + const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); + const requestHandler = key.getLookupValues(); + + try { + await requestHandler(mockReq, mockRes, mockNext); + expect.fail(); + } catch (actualError) { + expect(actualError).to.equal(mockError); + expect(mockGetLookupValues.calledOnce).to.be.true; + } + }); + }); +}); diff --git a/api/src/paths/critter-data/lookups/{key}.ts b/api/src/paths/critter-data/lookups/{key}.ts index d80c4b77fe..396d2da0aa 100644 --- a/api/src/paths/critter-data/lookups/{key}.ts +++ b/api/src/paths/critter-data/lookups/{key}.ts @@ -3,9 +3,10 @@ import { Operation } from 'express-openapi'; import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../constants/roles'; import { authorizeRequestHandler } from '../../../request-handlers/security/authorization'; import { CritterbaseService, ICbRouteKey, ICritterbaseUser } from '../../../services/critterbase-service'; +import { getLogger } from '../../../utils/logger'; // TODO: Put this all into an existing endpoint - +const defaultLog = getLogger('paths/lookups'); export const GET: Operation = [ authorizeRequestHandler((req) => { return { @@ -118,7 +119,12 @@ export function getLookupValues(): RequestHandler { for (const [a, b] of Object.entries(req.query)) { params.push({ key: String(a), value: String(b) }); } - const result = await cb.getLookupValues(key, params); - return res.status(200).json(result); + try { + const result = await cb.getLookupValues(key, params); + return res.status(200).json(result); + } catch (error) { + defaultLog.error({ label: 'lookups', message: 'error', error }); + throw error; + } }; }