Skip to content

Commit

Permalink
add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mauberti-bc committed Aug 26, 2024
1 parent 9183875 commit be77b0b
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 8 deletions.
103 changes: 103 additions & 0 deletions api/src/paths/taxonomy/taxon/tsn/hierarchy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import Ajv from 'ajv';
import chai, { expect } from 'chai';
import { describe } from 'mocha';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import { GET } from '.';
import * as db from '../../../../database/db';
import { HTTPError } from '../../../../errors/http-error';
import { ItisService } from '../../../../services/itis-service';
import { getMockDBConnection, getRequestHandlerMocks } from '../../../../__mocks__/db';
import { getHierarchyForTSNs } from './hierarchy';

chai.use(sinonChai);

describe('taxonomy/taxon/tsn/hierarchy', () => {
describe('openapi schema', () => {
const ajv = new Ajv();

it('is valid openapi v3 schema', () => {
expect(ajv.validateSchema(GET.apiDoc as unknown as object)).to.be.true;
});
});

describe('getHierarchyForTSNs', () => {
afterEach(() => {
sinon.restore();
});

it('returns an empty array if no species are found', async () => {
const dbConnectionObj = getMockDBConnection();

sinon.stub(db, 'getDBConnection').returns(dbConnectionObj);

const getHierarchyForTSNsStub = sinon.stub(ItisService.prototype, 'getHierarchyForTSNs').resolves([]);

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
mockReq.query = {
tsn: ['1', '2']
};

const requestHandler = getHierarchyForTSNs();

await requestHandler(mockReq, mockRes, mockNext);

expect(getHierarchyForTSNsStub).to.have.been.calledWith([1, 2]);

expect(mockRes.statusValue).to.equal(200);
expect(mockRes.jsonValue).to.eql([]);
});

it('returns an array of species', async () => {
const dbConnectionObj = getMockDBConnection();

sinon.stub(db, 'getDBConnection').returns(dbConnectionObj);

const mock1 = {
tsn: 1,
commonNames: ['something'],
scientificName: 'string',
hierarchy: [3, 2, 1]
} as unknown as any;
const mock2 = { tsn: '2', commonNames: [], scientificName: 'string', hierarchy: [3, 2] } as unknown as any;

const getHierarchyForTSNsStub = sinon.stub(ItisService.prototype, 'getHierarchyForTSNs').resolves([mock1, mock2]);

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
mockReq.query = {
tsn: ['1', '2']
};

const requestHandler = getHierarchyForTSNs();

await requestHandler(mockReq, mockRes, mockNext);

expect(getHierarchyForTSNsStub).to.have.been.calledWith([1, 2]);

expect(mockRes.jsonValue).to.eql([mock1, mock2]);
expect(mockRes.statusValue).to.equal(200);
});

it('catches error, and re-throws error', async () => {
const dbConnectionObj = getMockDBConnection({ rollback: sinon.stub(), release: sinon.stub() });

sinon.stub(db, 'getDBConnection').returns(dbConnectionObj);

sinon.stub(ItisService.prototype, 'getHierarchyForTSNs').rejects(new Error('a test error'));

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
mockReq.query = {
tsn: ['1', '2']
};

try {
const requestHandler = getHierarchyForTSNs();

await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (actualError) {
expect((actualError as HTTPError).message).to.equal('a test error');
}
});
});
});
8 changes: 4 additions & 4 deletions api/src/paths/taxonomy/taxon/tsn/hierarchy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getLogger } from '../../../../utils/logger';

const defaultLog = getLogger('paths/taxonomy/taxon/tsn/hierarchy');

export const GET: Operation = [getTaxonHierarchyByTSN()];
export const GET: Operation = [getHierarchyForTSNs()];

GET.apiDoc = {
description: 'Get taxon hierarchy information by TSN ids.',
Expand Down Expand Up @@ -79,9 +79,9 @@ GET.apiDoc = {
*
* @returns {RequestHandler}
*/
export function getTaxonHierarchyByTSN(): RequestHandler {
export function getHierarchyForTSNs(): RequestHandler {
return async (req, res) => {
defaultLog.debug({ label: 'getTaxonHierarchyByTSN', message: 'query params', query: req.query });
defaultLog.debug({ label: 'getHierarchyForTSNs', message: 'query params', query: req.query });

const connection = getAPIUserDBConnection();

Expand All @@ -98,7 +98,7 @@ export function getTaxonHierarchyByTSN(): RequestHandler {

res.status(200).json(response);
} catch (error) {
defaultLog.error({ label: 'getTaxonHierarchyByTSN', message: 'error', error });
defaultLog.error({ label: 'getHierarchyForTSNs', message: 'error', error });
connection.rollback();
throw error;
} finally {
Expand Down
131 changes: 130 additions & 1 deletion api/src/services/itis-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import chai, { expect } from 'chai';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import { ApiGeneralError } from '../errors/api-error';
import { ItisService } from './itis-service';
import { ItisService, ItisSolrSearchResponseHierarchy } from './itis-service';

chai.use(sinonChai);

Expand Down Expand Up @@ -182,6 +182,65 @@ describe('ItisService', () => {
});
});

describe('getHierarchyForTSNs', async () => {
it('returns array of hierarchy objects for tsns', async () => {
const mockTsns = [1, 2];
const mockTsnHierarchies = [
{ tsn: mockTsns[0], hierarchyTSN: ['$3$2$1$'] },
{ tsn: mockTsns[1], hierarchyTSN: ['$3$2$'] }
];
const mockAxiosResponse = {
data: {
response: {
docs: mockTsnHierarchies
}
}
};

const getItisSolrTsnHierarchyUrlStub = sinon
.stub(ItisService.prototype, 'getItisSolrTsnHierarchyUrl')
.returns('url');

const axiosStub = sinon.stub(axios, 'get').resolves(mockAxiosResponse);

const itisService = new ItisService();

const response = await itisService.getHierarchyForTSNs(mockTsns);

expect(getItisSolrTsnHierarchyUrlStub).to.have.been.calledWith(mockTsns);
expect(axiosStub).to.have.been.calledWith('url');
expect(response).to.eql([
{
tsn: mockTsns[0],
hierarchy: [3, 2, 1]
},
{
tsn: mockTsns[1],
hierarchy: [3, 2]
}
]);
});

it('catches and re-throws an error', async () => {
const mockTsns = [1, 2];
sinon.stub(axios, 'get').rejects(new Error('a test error'));

const itisService = new ItisService();
const getItisSolrTsnHierarchyUrlStub = sinon
.stub(ItisService.prototype, 'getItisSolrTsnHierarchyUrl')
.resolves('url');

try {
await itisService.getHierarchyForTSNs(mockTsns);

expect.fail();
} catch (error) {
expect((error as ApiGeneralError).message).to.equal('a test error');
expect(getItisSolrTsnHierarchyUrlStub).to.have.been.calledWith(mockTsns);
}
});
});

describe('getItisSolrTermSearchUrl', () => {
it('throws an error when itis solr url is not set', async () => {
process.env.ITIS_SOLR_URL = '';
Expand Down Expand Up @@ -210,6 +269,35 @@ describe('ItisService', () => {
});
});

describe('getItisSolrTsnHierarchyUrl', () => {
const mockTsns = [1];
it('throws an error when itis solr url is not set', async () => {
process.env.ITIS_SOLR_URL = '';

const itisService = new ItisService();

try {
await itisService.getItisSolrTsnHierarchyUrl(mockTsns);

expect.fail();
} catch (error) {
expect((error as ApiGeneralError).message).to.equal('Failed to build ITIS query.');
}
});

it('returns a valid url', async () => {
process.env.ITIS_SOLR_URL = 'https://services.itis.gov/';

const itisService = new ItisService();

const response = await itisService.getItisSolrTsnHierarchyUrl(mockTsns);

expect(response).to.equal(
'https://services.itis.gov/??wt=json&sort=kingdom+asc&rows=150&omitHeader=true&fl=tsn+hierarchyTSN&&q=tsn:1'
);
});
});

describe('getItisSolrTsnSearchUrl', () => {
it('throws an error when itis solr url is not set', async () => {
process.env.ITIS_SOLR_URL = '';
Expand Down Expand Up @@ -237,4 +325,45 @@ describe('ItisService', () => {
);
});
});

describe('getItisSolrTsnSearchUrl', () => {
it('throws an error when itis solr url is not set', async () => {
process.env.ITIS_SOLR_URL = '';

const itisService = new ItisService();

try {
await itisService.getItisSolrTsnSearchUrl([123]);

expect.fail();
} catch (error) {
expect((error as ApiGeneralError).message).to.equal('Failed to build ITIS query.');
}
});

it('returns a valid url', async () => {
process.env.ITIS_SOLR_URL = 'https://services.itis.gov/';

const itisService = new ItisService();

const response = await itisService.getItisSolrTsnSearchUrl([123]);

expect(response).to.equal(
'https://services.itis.gov/??wt=json&sort=kingdom+asc&rows=150&omitHeader=true&fl=tsn+scientificName:nameWOInd+kingdom+parentTSN+commonNames:vernacular+updateDate+usage+rank&&q=tsn:123'
);
});
});

describe('_sanitizeHierarchyData', () => {
it('turns an ITIS hierarchy string into an array'),
() => {
const mockData: ItisSolrSearchResponseHierarchy[] = [{ tsn: '1', hierarchyTSN: ['$3$2$1$'] }];

const itisService = new ItisService();

const result = itisService._sanitizeHierarchyData(mockData);

expect(result).to.eql([3, 2, 1]);
};
});
});
7 changes: 4 additions & 3 deletions api/src/services/itis-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export class ItisService {
* @memberof ItisService
*/
async getHierarchyForTSNs(tsnIds: number[]): Promise<TSNWithHierarchy[]> {
const url = await this.getItisSolrTsnHierarchyUrl(tsnIds);
const url = this.getItisSolrTsnHierarchyUrl(tsnIds);

defaultLog.debug({ label: 'getHierarchyForTSNs', message: 'url', url });

Expand Down Expand Up @@ -140,6 +140,7 @@ export class ItisService {
* Cleans up the ITIS hierarchy response data
*
* @param {ItisSolrSearchResponse[]} data
* @return {TSNWithHierarchy[]}
* @memberof ItisService
*/
_sanitizeHierarchyData = (data: ItisSolrSearchResponseHierarchy[]): TSNWithHierarchy[] => {
Expand Down Expand Up @@ -200,10 +201,10 @@ export class ItisService {
* Get the ITIS SOLR search-by-tsn URL for hierarchy information
*
* @param {number[]} tsnIds
* @return {*} {Promise<string>}
* @return {*} {string}
* @memberof ItisService
*/
async getItisSolrTsnHierarchyUrl(tsnIds: number[]): Promise<string> {
getItisSolrTsnHierarchyUrl(tsnIds: number[]): string {
const itisUrl = this._getItisSolrUrl();

if (!itisUrl) {
Expand Down

0 comments on commit be77b0b

Please sign in to comment.