Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SIMSBIOHUB-459: Fixed save taxonomy repo method #236

Merged
merged 10 commits into from
Feb 12, 2024
18 changes: 2 additions & 16 deletions api/src/repositories/taxonomy-repository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('TaxonomyRepository', () => {
});

describe('addItisTaxonRecord', () => {
it('should return a new taxon record', async () => {
it('should return undefined on successful insert', async () => {
const mockQueryResponse = {
rowCount: 1,
rows: [
Expand Down Expand Up @@ -75,21 +75,7 @@ describe('TaxonomyRepository', () => {

const response = await taxonomyRepository.addItisTaxonRecord(1, 'string', 'string', {}, 'string');

expect(response).to.be.eql({
taxon_id: 1,
itis_tsn: 1,
bc_taxon_code: 'string',
itis_scientific_name: 'string',
common_name: 'string',
itis_data: {},
record_effective_date: 'string',
record_end_date: 'string',
create_date: 'string',
create_user: 1,
update_date: 'string',
update_user: 1,
revision_count: 1
});
expect(response).to.be.eql(undefined);
});
});

Expand Down
65 changes: 35 additions & 30 deletions api/src/repositories/taxonomy-repository.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import SQL from 'sql-template-strings';
import { z } from 'zod';
import { getKnex } from '../database/db';
import { ApiExecuteSQLError } from '../errors/api-error';
import { getLogger } from '../utils/logger';
import { BaseRepository } from './base-repository';

const defaultLog = getLogger('repositories/taxonomy-repository');

export const TaxonRecord = z.object({
taxon_id: z.number(),
itis_tsn: z.number(),
Expand Down Expand Up @@ -60,40 +62,43 @@ export class TaxonomyRepository extends BaseRepository {
itisTsn: number,
itisScientificName: string,
commonName: string | null,
itisData: Record<any, any>,
itisData: Record<string, unknown>,
itisUpdateDate: string
): Promise<TaxonRecord> {
): Promise<void> {
defaultLog.debug({ label: 'addItisTaxonRecord', itisTsn });

const sqlStatement = SQL`
INSERT INTO
taxon
(
itis_tsn,
itis_scientific_name,
common_name,
itis_data,
itis_update_date
)
VALUES (
${itisTsn},
${itisScientificName},
${commonName},
${itisData},
${itisUpdateDate}
WITH inserted_row AS (
INSERT INTO
taxon
(
itis_tsn,
itis_scientific_name,
common_name,
itis_data,
itis_update_date
)
VALUES (
${itisTsn},
${itisScientificName},
${commonName},
${itisData},
${itisUpdateDate}
)
ON CONFLICT
DO NOTHING
RETURNING *
)
RETURNING
*;
SELECT * FROM inserted_row
UNION
SELECT * FROM taxon
WHERE
taxon.itis_tsn = 93
curtisupshall marked this conversation as resolved.
Show resolved Hide resolved
AND
taxon.record_end_date IS null;
`;

const response = await this.connection.sql(sqlStatement, TaxonRecord);

if (response.rowCount !== 1) {
throw new ApiExecuteSQLError('Failed to insert new taxon record', [
'TaxonomyRepository->addItisTaxonRecord',
'rowCount was null or undefined, expected rowCount = 1'
]);
}

return response.rows[0];
await this.connection.sql(sqlStatement, TaxonRecord);
}

/**
Expand Down
50 changes: 13 additions & 37 deletions api/src/services/taxonomy-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe('TaxonomyService', () => {

const response = await taxonomyService.getTaxonByTsnIds([1]);

expect(repo).to.be.calledOnce;
expect(repo).to.be.calledTwice;
expect(response).to.be.eql([{ tsn: 1, commonName: 'common_name', scientificName: 'itis_scientific_name' }]);
});

Expand Down Expand Up @@ -104,19 +104,24 @@ describe('TaxonomyService', () => {

const taxonomyService = new TaxonomyService(mockDBConnection);

const repo = sinon.stub(TaxonomyRepository.prototype, 'getTaxonByTsnIds').resolves([getTaxonRecord[0]]);
const getTaxonByTsnIdsStub = sinon
.stub(TaxonomyRepository.prototype, 'getTaxonByTsnIds')
.onCall(0)
.resolves([getTaxonRecord[0]])
.onCall(1)
.resolves([...getTaxonRecord]);

const searchItisByTSNStub = sinon
.stub(ItisService.prototype, 'searchItisByTSN')
.resolves(getItisSolrSearchResponse);

const itisService = sinon.stub(TaxonomyService.prototype, 'addItisTaxonRecord').resolves(getTaxonRecord[1]);
const addItisTaxonRecordStub = sinon.stub(TaxonomyService.prototype, 'addItisTaxonRecord').resolves();

const response = await taxonomyService.getTaxonByTsnIds([1, 2]);

expect(repo).to.be.calledOnce;
expect(getTaxonByTsnIdsStub).to.be.calledTwice;
expect(searchItisByTSNStub).to.be.calledOnce;
expect(itisService).to.be.calledOnce;
expect(addItisTaxonRecordStub).to.be.calledOnce;
expect(response).to.be.eql([
{ tsn: 1, commonName: 'common_name', scientificName: 'itis_scientific_name' },
{ tsn: 2, commonName: 'common_name', scientificName: 'itis_scientific_name' }
Expand All @@ -130,40 +135,11 @@ describe('TaxonomyService', () => {

const taxonomyService = new TaxonomyService(mockDBConnection);

const addItisTaxonRecordStub = sinon.stub(TaxonomyRepository.prototype, 'addItisTaxonRecord').resolves({
taxon_id: 1,
itis_tsn: 1,
bc_taxon_code: null,
itis_scientific_name: 'scientificName',
common_name: 'commonName',
itis_data: {},
record_effective_date: 'updateDate',
record_end_date: null,
create_date: 'now',
create_user: 1,
update_date: null,
update_user: null,
revision_count: 1
});

const response = await taxonomyService.addItisTaxonRecord(getItisSolrSearchResponse[0]);
const addItisTaxonRecordStub = sinon.stub(TaxonomyRepository.prototype, 'addItisTaxonRecord').resolves();

await taxonomyService.addItisTaxonRecord(getItisSolrSearchResponse[0]);

expect(addItisTaxonRecordStub).to.be.calledOnce;
expect(response).to.be.eql({
taxon_id: 1,
itis_tsn: 1,
bc_taxon_code: null,
itis_scientific_name: 'scientificName',
common_name: 'commonName',
itis_data: {},
record_effective_date: 'updateDate',
record_end_date: null,
create_date: 'now',
create_user: 1,
update_date: null,
update_user: null,
revision_count: 1
});
});
});

Expand Down
15 changes: 10 additions & 5 deletions api/src/services/taxonomy-service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { IDBConnection } from '../database/db';
import { TaxonomyRepository, TaxonRecord } from '../repositories/taxonomy-repository';
import { getLogger } from '../utils/logger';
import { ItisService, ItisSolrSearchResponse } from './itis-service';

const defaultLog = getLogger('services/taxonomy-service');

export type TaxonSearchResult = {
tsn: number;
commonName: string | null;
Expand Down Expand Up @@ -29,23 +32,25 @@ export class TaxonomyService {
* @memberof TaxonomyService
*/
async getTaxonByTsnIds(tsnIds: number[]): Promise<TaxonSearchResult[]> {
defaultLog.debug({ label: 'getTaxonByTsnIds', tsnIds });

// Search for taxon records in the database
const existingTaxonRecords = await this.taxonRepository.getTaxonByTsnIds(tsnIds);
let patchedTaxonRecords: TaxonRecord[] = [];
const existingTsnIds = existingTaxonRecords.map((record) => record.itis_tsn);

const missingTsnIds = tsnIds.filter((tsnId) => !existingTaxonRecords.find((item) => item.itis_tsn === tsnId));
const missingTsnIds = tsnIds.filter((tsnId) => !existingTsnIds.includes(tsnId));

if (missingTsnIds.length) {
// If the local database does not contain a record for all of the requested ids, search ITIS for the missing
// taxon records, patching the missing records in the local database in the process
const itisService = new ItisService();
const itisResponse = await itisService.searchItisByTSN(missingTsnIds);

patchedTaxonRecords = await Promise.all(itisResponse.map(async (item) => this.addItisTaxonRecord(item)));
await Promise.all(itisResponse.map(this.addItisTaxonRecord));
}

// Missing ids patched, return taxon records for all requested ids
return this._sanitizeTaxonRecordsData(existingTaxonRecords.concat(patchedTaxonRecords));
return this._sanitizeTaxonRecordsData(await this.taxonRepository.getTaxonByTsnIds(tsnIds));
curtisupshall marked this conversation as resolved.
Show resolved Hide resolved
}

_sanitizeTaxonRecordsData(taxonRecords: TaxonRecord[]): TaxonSearchResult[] {
Expand All @@ -65,7 +70,7 @@ export class TaxonomyService {
* @return {*} {Promise<TaxonRecord>}
* @memberof TaxonomyService
*/
async addItisTaxonRecord(itisSolrResponse: ItisSolrSearchResponse): Promise<TaxonRecord> {
async addItisTaxonRecord(itisSolrResponse: ItisSolrSearchResponse): Promise<void> {
let commonName = null;
if (itisSolrResponse.commonNames) {
commonName = itisSolrResponse.commonNames[0].split('$')[1];
Expand Down
Loading