Skip to content

Commit

Permalink
SIMSBIOHUB-457: Taxonomy Endpoints (#235)
Browse files Browse the repository at this point in the history
Design BioHub taxonomy endpoint + services/repositories.
- Taxon Search endpoint
  - Search by term to SOLR service from frontend
- Taxon ids lookup endpoint
  - Query DB for cached tsn's
  - Search SOLR for tsn's not found in DB, update accordingly
  - Return list of Taxons
  • Loading branch information
KjartanE authored Feb 2, 2024
1 parent 4df6210 commit e761319
Show file tree
Hide file tree
Showing 22 changed files with 1,357 additions and 533 deletions.
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"git.ignoreLimitWarning": true
}
"git.ignoreLimitWarning": true
}
3 changes: 3 additions & 0 deletions api/.pipeline/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ const phases = {
elasticsearchURL: 'http://es01:9200',
elasticsearchEmlIndex: 'eml',
elasticsearchTaxonomyIndex: 'taxonomy_3.0.0',
itisSolrUrl: 'https://services.itis.gov',
s3KeyPrefix: (isStaticDeployment && 'biohub') || `local/${deployChangeId}/biohub`,
tz: config.timezone.api,
sso: config.sso.dev,
Expand Down Expand Up @@ -111,6 +112,7 @@ const phases = {
elasticsearchURL: 'http://es01.a0ec71-dev:9200', // TODO: Update to test instance (es is not yet deployed to test)
elasticsearchEmlIndex: 'eml',
elasticsearchTaxonomyIndex: 'taxonomy_3.0.0',
itisSolrUrl: 'https://services.itis.gov',
s3KeyPrefix: 'biohub',
tz: config.timezone.api,
sso: config.sso.test,
Expand Down Expand Up @@ -139,6 +141,7 @@ const phases = {
elasticsearchURL: 'http://es01:9200',
elasticsearchEmlIndex: 'eml',
elasticsearchTaxonomyIndex: 'taxonomy_3.0.0',
itisSolrUrl: 'https://services.itis.gov',
s3KeyPrefix: 'biohub',
tz: config.timezone.api,
sso: config.sso.prod,
Expand Down
2 changes: 2 additions & 0 deletions api/.pipeline/lib/api.deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ const apiDeploy = async (settings) => {
ELASTICSEARCH_URL: phases[phase].elasticsearchURL,
ELASTICSEARCH_EML_INDEX: phases[phase].elasticsearchEmlIndex,
ELASTICSEARCH_TAXONOMY_INDEX: phases[phase].elasticsearchTaxonomyIndex,
// ITIS SOLR
ITIS_SOLR_URL: phases[phase].itisSolrUrl,
// S3 (Object Store)
S3_KEY_PREFIX: phases[phase].s3KeyPrefix,
OBJECT_STORE_SECRETS: 'biohubbc-object-store',
Expand Down
8 changes: 8 additions & 0 deletions api/.pipeline/templates/api.dc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ parameters:
description: Application timezone
required: false
value: 'America/Vancouver'
# ITIS SOLR
- name: ITIS_SOLR_URL
description: ITIS SOLR API URL
value: 'https://services.itis.gov'
required: true
# Keycloak
- name: KEYCLOAK_HOST
description: Key clock login url
Expand Down Expand Up @@ -263,6 +268,9 @@ objects:
value: ${ELASTICSEARCH_EML_INDEX}
- name: ELASTICSEARCH_TAXONOMY_INDEX
value: ${ELASTICSEARCH_TAXONOMY_INDEX}
# ITIS SOLR
- name: ITIS_SOLR_URL
value: ${ITIS_SOLR_URL}
- name: S3_KEY_PREFIX
value: ${S3_KEY_PREFIX}
- name: TZ
Expand Down
6 changes: 3 additions & 3 deletions api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions api/src/__mocks__/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ export class MockRes {
return this;
});

/**
* The value of the last `.setHeader(<value>)` call.
*
* @memberof MockRes
*/
headerValue: any;
setHeader = sinon.fake((header: any) => {
this.headerValue = header;

return this;
});

/**
* The value of the last `.json(<value>)` call.
*
Expand Down
2 changes: 1 addition & 1 deletion api/src/paths/submission/intake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export function submissionIntake(): RequestHandler {
const searchIndexService = new SearchIndexService(connection);
const regionService = new RegionService(connection);

// validate theubmission
// validate the submission
if (!(await validationService.validateSubmissionFeatures([submissionFeature]))) {
throw new HTTP400('Invalid submission'); // TODO return details on why the submission is invalid
}
Expand Down
87 changes: 0 additions & 87 deletions api/src/paths/taxonomy/species/list.ts

This file was deleted.

88 changes: 0 additions & 88 deletions api/src/paths/taxonomy/species/search.ts

This file was deleted.

97 changes: 97 additions & 0 deletions api/src/paths/taxonomy/taxon/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import Ajv from 'ajv';
import chai, { expect } from 'chai';
import { describe } from 'mocha';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import { findTaxonBySearchTerms, 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';

chai.use(sinonChai);

describe('taxon', () => {
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('findTaxonBySearchTerms', () => {
afterEach(() => {
sinon.restore();
});

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

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

const getSpeciesFromIdsStub = sinon.stub(ItisService.prototype, 'searchItisByTerm').resolves([]);

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
mockReq.query = {
terms: ''
};

const requestHandler = findTaxonBySearchTerms();

await requestHandler(mockReq, mockRes, mockNext);

expect(getSpeciesFromIdsStub).to.have.been.calledWith('');

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

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

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

const mock1 = { id: '1', commonName: 'something', scientificName: 'string' } as unknown as any;
const mock2 = { id: '2', commonName: null, scientificName: 'string' } as unknown as any;

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

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
mockReq.query = {
terms: 't'
};

const requestHandler = findTaxonBySearchTerms();

await requestHandler(mockReq, mockRes, mockNext);

expect(getSpeciesFromIdsStub).to.have.been.calledWith('t');

expect(mockRes.jsonValue).to.eql({ searchResponse: [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, 'searchItisByTerm').rejects(new Error('a test error'));

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
mockReq.query = {
ids: 'a'
};

try {
const requestHandler = findTaxonBySearchTerms();

await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (actualError) {
expect((actualError as HTTPError).message).to.equal('a test error');
}
});
});
});
Loading

0 comments on commit e761319

Please sign in to comment.