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

Test coverage #1265

Merged
merged 10 commits into from
Apr 2, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class ObservationSubCountMeasurementRepository extends BaseRepository {
return response.rows;
}

async deleteObservationMeasurements(surveyObservationId: number[], surveyId: number) {
async deleteObservationMeasurements(surveyId: number, surveyObservationId: number[]) {
await this.deleteObservationQualitativeMeasurementRecordsForSurveyObservationIds(surveyObservationId, surveyId);
await this.deleteObservationQuantitativeMeasurementRecordsForSurveyObservationIds(surveyObservationId, surveyId);
}
Expand Down
169 changes: 169 additions & 0 deletions api/src/repositories/subcount-repository.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import chai, { expect } from 'chai';
import { describe } from 'mocha';
import { QueryResult } from 'pg';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import { ApiExecuteSQLError } from '../errors/api-error';
import { getMockDBConnection } from '../__mocks__/db';
import {
InsertObservationSubCount,
InsertSubCountEvent,
ObservationSubCountRecord,
SubCountCritterRecord,
SubCountEventRecord,
SubCountRepository
} from './subcount-repository';

chai.use(sinonChai);

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

describe('insertObservationSubCount', () => {
it('should successfully insert observation subcount', async () => {
const mockSubcount: ObservationSubCountRecord = {
observation_subcount_id: 1,
survey_observation_id: 1,
subcount: 5,
create_date: '1970-01-01',
create_user: 1,
update_date: null,
update_user: null,
revision_count: 1
};

const mockResponse = ({
rows: [mockSubcount],
rowCount: 1
} as any) as Promise<QueryResult<any>>;

const dbConnection = getMockDBConnection({
knex: () => mockResponse
});

const repo = new SubCountRepository(dbConnection);
const response = await repo.insertObservationSubCount(mockSubcount);

expect(response).to.eql(mockSubcount);
});

it('should catch query errors and throw an ApiExecuteSQLError', async () => {
const mockResponse = ({
rows: [],
rowCount: 0
} as any) as Promise<QueryResult<any>>;
const dbConnection = getMockDBConnection({
knex: () => mockResponse
});

const repo = new SubCountRepository(dbConnection);
try {
await repo.insertObservationSubCount((null as unknown) as InsertObservationSubCount);
expect.fail();
} catch (error) {
expect(((error as any) as ApiExecuteSQLError).message).to.be.eq('Failed to insert observation subcount');
}
});
});

describe('insertSubCountEvent', () => {
it('should successfully insert subcount_event record', async () => {
const mockInsertSubcountEvent: InsertSubCountEvent = {
observation_subcount_id: 1,
critterbase_event_id: 'aaaa'
};

const mockSubcountEvent: SubCountEventRecord = {
observation_subcount_id: 1,
create_date: '1970-01-01',
create_user: 1,
update_date: null,
update_user: null,
revision_count: 1,
subcount_event_id: 1,
critterbase_event_id: 'aaaa'
};

const mockResponse = ({
rows: [mockSubcountEvent],
rowCount: 1
} as any) as Promise<QueryResult<any>>;

const dbConnection = getMockDBConnection({
knex: () => mockResponse
});

const repo = new SubCountRepository(dbConnection);
const response = await repo.insertSubCountEvent(mockInsertSubcountEvent);

expect(response).to.eql(mockSubcountEvent);
});

it('should catch query errors and throw an ApiExecuteSQLError', async () => {
const mockResponse = ({
rows: [],
rowCount: 0
} as any) as Promise<QueryResult<any>>;
const dbConnection = getMockDBConnection({
knex: () => mockResponse
});

const repo = new SubCountRepository(dbConnection);
try {
await repo.insertSubCountEvent((null as unknown) as InsertSubCountEvent);
expect.fail();
} catch (error) {
expect(((error as any) as ApiExecuteSQLError).message).to.be.eq('Failed to insert subcount event');
}
});
});

describe('insertSubCountCritter', () => {
it('should successfully insert a subcount_critter record', async () => {
const mockSubcountCritterRecord: SubCountCritterRecord = {
subcount_critter_id: 1,
observation_subcount_id: 1,
critter_id: 1,
create_date: '1970-01-01',
create_user: 1,
update_date: null,
update_user: null,
revision_count: 1
};

const mockResponse = ({
rows: [mockSubcountCritterRecord],
rowCount: 1
} as any) as Promise<QueryResult<any>>;

const dbConnection = getMockDBConnection({
knex: () => mockResponse
});

const repo = new SubCountRepository(dbConnection);
const response = await repo.insertSubCountCritter(mockSubcountCritterRecord);

expect(response).to.eql(mockSubcountCritterRecord);
});

it('should catch query errors and throw an ApiExecuteSQLError', async () => {
const mockResponse = ({
rows: [],
rowCount: 0
} as any) as Promise<QueryResult<any>>;
const dbConnection = getMockDBConnection({
knex: () => mockResponse
});

const repo = new SubCountRepository(dbConnection);
try {
await repo.insertSubCountCritter((null as unknown) as SubCountCritterRecord);
expect.fail();
} catch (error) {
expect(((error as any) as ApiExecuteSQLError).message).to.be.eq('Failed to insert subcount critter');
}
});
});
});
25 changes: 18 additions & 7 deletions api/src/repositories/subcount-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ export const SubCountEventRecord = z.object({
export type SubCountEventRecord = z.infer<typeof SubCountEventRecord>;
export type InsertSubCountEvent = Pick<SubCountEventRecord, 'observation_subcount_id' | 'critterbase_event_id'>;

export const SubCountCritterRecord = z.object({
subcount_critter_id: z.number(),
observation_subcount_id: z.number(),
critter_id: z.number(),
create_date: z.string(),
create_user: z.number(),
update_date: z.string().nullable(),
update_user: z.number().nullable(),
revision_count: z.number()
});

export type SubCountCritterRecord = z.infer<typeof SubCountCritterRecord>;

export class SubCountRepository extends BaseRepository {
/**
* Inserts a new observation_subcount record
Expand Down Expand Up @@ -77,16 +90,14 @@ export class SubCountRepository extends BaseRepository {
/**
* Inserts a new subcount_critter record.
*
* TODO: Implement this function fully. The incoming `record` parameter and the return value are of type `unknown`.
*
* @param {unknown} record
* @return {*} {Promise<unknown>}
* @param {SubCountCritterRecord} subcountCritter
* @return {*} {Promise<SubCountCritterRecord>}
* @memberof SubCountRepository
*/
async insertSubCountCritter(record: unknown): Promise<unknown> {
const queryBuilder = getKnex().insert(record).into('subcount_critter').returning('*');
async insertSubCountCritter(subcountCritter: SubCountCritterRecord): Promise<SubCountCritterRecord> {
const queryBuilder = getKnex().insert(subcountCritter).into('subcount_critter').returning('*');

const response = await this.connection.knex(queryBuilder);
const response = await this.connection.knex(queryBuilder, SubCountCritterRecord);

if (response.rowCount !== 1) {
throw new ApiExecuteSQLError('Failed to insert subcount critter', [
Expand Down
87 changes: 87 additions & 0 deletions api/src/services/subcount-service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import chai, { expect } from 'chai';
import { describe } from 'mocha';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import { ObservationSubCountMeasurementRepository } from '../repositories/observation-subcount-measurement-repository';
import {
InsertObservationSubCount,
InsertSubCountEvent,
ObservationSubCountRecord,
SubCountEventRecord,
SubCountRepository
} from '../repositories/subcount-repository';
import { getMockDBConnection } from '../__mocks__/db';
import { SubCountService } from './subcount-service';

chai.use(sinonChai);

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

describe('insertObservationSubCount', () => {
it('should insert observation subcount', async () => {
const mockDbConnection = getMockDBConnection();
const subCountService = new SubCountService(mockDbConnection);

const insertObservationSubCountStub = sinon
.stub(SubCountRepository.prototype, 'insertObservationSubCount')
.resolves({ observation_subcount_id: 1 } as ObservationSubCountRecord);

const response = await subCountService.insertObservationSubCount({
survey_observation_id: 1
} as InsertObservationSubCount);

expect(insertObservationSubCountStub).to.be.calledOnceWith({ survey_observation_id: 1 });
expect(response).to.eql({ observation_subcount_id: 1 });
});
});

describe('insertSubCountEvent', () => {
it('should insert subcount event', async () => {
const mockDbConnection = getMockDBConnection();
const subCountService = new SubCountService(mockDbConnection);

const insertSubCountEventStub = sinon
.stub(SubCountRepository.prototype, 'insertSubCountEvent')
.resolves({ observation_subcount_id: 1 } as SubCountEventRecord);

const response = await subCountService.insertSubCountEvent({ observation_subcount_id: 1 } as InsertSubCountEvent);

expect(insertSubCountEventStub).to.be.calledOnceWith({ observation_subcount_id: 1 });
expect(response).to.eql({ observation_subcount_id: 1 });
});
});

describe('deleteObservationSubCountRecords', () => {
it('should delete observation_subcount records and related child records', async () => {
const mockDbConnection = getMockDBConnection();
const subCountService = new SubCountService(mockDbConnection);

const mockSurveyId = 1;
const mockSurveyObservationIds = [1, 2, 3, 4];

const deleteSubCountCritterRecordsForObservationIdStub = sinon
.stub(SubCountRepository.prototype, 'deleteSubCountCritterRecordsForObservationId')
.resolves();

const deleteObservationMeasurementsStub = sinon
.stub(ObservationSubCountMeasurementRepository.prototype, 'deleteObservationMeasurements')
.resolves();

const deleteObservationSubCountRecordsStub = sinon
.stub(SubCountRepository.prototype, 'deleteObservationSubCountRecords')
.resolves();

await subCountService.deleteObservationSubCountRecords(mockSurveyId, mockSurveyObservationIds);

expect(deleteSubCountCritterRecordsForObservationIdStub).to.be.calledOnceWith(
mockSurveyId,
mockSurveyObservationIds
);
expect(deleteObservationMeasurementsStub).to.be.calledOnceWith(mockSurveyId, mockSurveyObservationIds);
expect(deleteObservationSubCountRecordsStub).to.be.calledOnceWith(mockSurveyId, mockSurveyObservationIds);
});
});
});
5 changes: 3 additions & 2 deletions api/src/services/subcount-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,13 @@ export class SubCountService extends DBService {
* @memberof SubCountService
*/
async deleteObservationSubCountRecords(surveyId: number, surveyObservationIds: number[]): Promise<void> {
const repo = new ObservationSubCountMeasurementRepository(this.connection);

// Delete child subcount_critter records, if any
await this.subCountRepository.deleteSubCountCritterRecordsForObservationId(surveyId, surveyObservationIds);

// Delete child observation measurements, if any
const repo = new ObservationSubCountMeasurementRepository(this.connection);
await repo.deleteObservationMeasurements(surveyObservationIds, surveyId);
await repo.deleteObservationMeasurements(surveyId, surveyObservationIds);

// Delete observation_subcount records, if any
return this.subCountRepository.deleteObservationSubCountRecords(surveyId, surveyObservationIds);
Expand Down
Loading
Loading