Skip to content

Commit

Permalink
chore(#142): adding tests to increase coverage (#143)
Browse files Browse the repository at this point in the history
  • Loading branch information
witash authored Oct 23, 2024
1 parent 59f607b commit 7d279c1
Show file tree
Hide file tree
Showing 18 changed files with 980 additions and 332 deletions.
6 changes: 3 additions & 3 deletions mediator/src/controllers/cht.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export async function createPatient(chtPatientDoc: any) {

export async function updatePatientIds(chtFormDoc: any) {
// first, get the existing patient from fhir server
const response = await getFHIRPatientResource(chtFormDoc.external_id);
const response = await getFHIRPatientResource(chtFormDoc.doc.external_id);

if (response.status != 200) {
return { status: 500, data: { message: `FHIR responded with ${response.status}`} };
Expand All @@ -37,10 +37,10 @@ export async function updatePatientIds(chtFormDoc: any) {
}

const fhirPatient = response.data.entry[0].resource;
addId(fhirPatient, chtPatientIdentifierType, chtFormDoc.patient_id);
addId(fhirPatient, chtPatientIdentifierType, chtFormDoc.doc.patient_id);

// now, we need to get the actual patient doc from cht...
const patient_uuid = await getPatientUUIDFromSourceId(chtFormDoc._id);
const patient_uuid = await getPatientUUIDFromSourceId(chtFormDoc.doc._id);
if (patient_uuid){
addId(fhirPatient, chtDocumentIdentifierType, patient_uuid);
return updateFhirResource({ ...fhirPatient, resourceType: 'Patient' });
Expand Down
178 changes: 178 additions & 0 deletions mediator/src/controllers/tests/cht.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import {
createPatient,
updatePatientIds,
createEncounter
} from '../cht'
import {
ChtPatientFactory,
ChtSMSPatientFactory,
ChtPatientIdsFactory,
ChtPregnancyForm
} from '../../middlewares/schemas/tests/cht-request-factories';
import {
PatientFactory,
EncounterFactory,
ObservationFactory
} from '../../middlewares/schemas/tests/fhir-resource-factories';
import {
chtDocumentIdentifierType,
chtPatientIdentifierType
} from '../../mappers/cht';

import * as fhir from '../../utils/fhir';
import * as cht from '../../utils/cht';

import axios from 'axios';
import { randomUUID } from 'crypto';

jest.mock('axios');

describe('CHT outgoing document controllers', () => {
describe('createPatient', () => {
it('creates a FHIR Patient from CHT patient doc', async () => {
jest.spyOn(fhir, 'updateFhirResource').mockResolvedValueOnce({
data: {},
status: 200,
});

const data = ChtPatientFactory.build();

const res = await createPatient(data);

expect(res.status).toBe(200);

// assert that the create resource has the right identifier and type
expect(fhir.updateFhirResource).toHaveBeenCalledWith(
expect.objectContaining({
resourceType: 'Patient',
identifier: expect.arrayContaining([
expect.objectContaining({
type: chtDocumentIdentifierType,
value: data.doc._id
})
]),
})
);
});

it('creates a FHIR Patient from an SMS form using source id', async () => {
jest.spyOn(fhir, 'updateFhirResource').mockResolvedValueOnce({
data: {},
status: 200,
});

let sourceId = randomUUID();
jest.spyOn(cht, 'getPatientUUIDFromSourceId').mockResolvedValueOnce(sourceId);

const data = ChtSMSPatientFactory.build();

const res = await createPatient(data);

expect(res.status).toBe(200);

// assert that the createid resource has the right identifier and type
expect(fhir.updateFhirResource).toHaveBeenCalledWith(
expect.objectContaining({
resourceType: 'Patient',
identifier: expect.arrayContaining([
expect.objectContaining({
type: chtDocumentIdentifierType,
value: sourceId
})
]),
})
);
});
});

describe('updatePatientIds', () => {
it('updates patient ids', async () => {
const existingPatient = PatientFactory.build();
jest.spyOn(fhir, 'getFHIRPatientResource').mockResolvedValue({
data: { total: 1, entry: [ { resource: existingPatient } ] },
status: 200,
});
jest.spyOn(fhir, 'updateFhirResource').mockResolvedValueOnce({
data: {},
status: 200,
});

let sourceId = randomUUID();
jest.spyOn(cht, 'getPatientUUIDFromSourceId').mockResolvedValueOnce(sourceId);

const data = ChtPatientIdsFactory.build();

const res = await updatePatientIds(data);

expect(res.status).toBe(200);

// assert that the created resource has the right identifier and type
expect(fhir.updateFhirResource).toHaveBeenCalledWith(
expect.objectContaining({
id: existingPatient.id,
identifier: expect.arrayContaining([
expect.objectContaining({
type: chtDocumentIdentifierType,
value: sourceId
})
]),
})
);
});
});

describe('createEncounter', () => {
it('creates FHIR Encounter from CHT form', async () => {
jest.spyOn(fhir, 'getFHIRPatientResource').mockResolvedValueOnce({
data: { total: 1, entry: [ { resource: PatientFactory.build() } ] },
status: 200,
});
// observations use createFhirResource
jest.spyOn(fhir, 'createFhirResource').mockResolvedValueOnce({
data: {},
status: 200,
});
// encounter uses updatedFhirResource
jest.spyOn(fhir, 'updateFhirResource').mockResolvedValueOnce({
data: {},
status: 200,
});

const data = ChtPregnancyForm.build();

const res = await createEncounter(data);

expect(res.status).toBe(200);

// assert that the encounter was created
expect(fhir.updateFhirResource).toHaveBeenCalledWith(
expect.objectContaining({
resourceType: 'Encounter',
identifier: expect.arrayContaining([
expect.objectContaining({
type: chtDocumentIdentifierType,
value: data.id
})
]),
})
);

// assert that at least one observation was created with the right codes
expect(fhir.createFhirResource).toHaveBeenCalledWith(
expect.objectContaining({
resourceType: 'Observation',
code: {
coding: expect.arrayContaining([{
code: data.observations[0].code
}])
},
valueCodeableConcept: {
coding: expect.arrayContaining([{
code: data.observations[0].valueCode
}])
}
})
);
});
});
});
4 changes: 4 additions & 0 deletions mediator/src/controllers/tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
deleteFhirSubscription,
createFHIRSubscriptionResource,
} from '../../utils/fhir';
import { queryCht } from '../../utils/cht';

jest.mock('../../utils/fhir');
jest.mock('../../utils/cht');
Expand All @@ -24,3 +25,6 @@ export const mockCreateFHIRSubscriptionResource =
export const mockCreateChtRecord = createChtFollowUpRecord as jest.MockedFn<
typeof createChtFollowUpRecord
>;
export const mockQueryCht = queryCht as jest.MockedFn<
typeof queryCht
>;
4 changes: 2 additions & 2 deletions mediator/src/mappers/openmrs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const openMRSIdentifierType: fhir4.CodeableConcept = {

export const openMRSSource = 'openmrs';

const visitNoteType: fhir4.CodeableConcept = {
export const visitNoteType: fhir4.CodeableConcept = {
text: "Visit Note",
coding: [{
system: "http://fhir.openmrs.org/code-system/encounter-type",
Expand All @@ -23,7 +23,7 @@ const visitNoteType: fhir4.CodeableConcept = {
}]
}

const visitType: fhir4.CodeableConcept = {
export const visitType: fhir4.CodeableConcept = {
text: "Home Visit",
coding: [{
system: "http://fhir.openmrs.org/code-system/visit-type",
Expand Down
4 changes: 4 additions & 0 deletions mediator/src/middlewares/schemas/encounter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ export const EncounterSchema = joi.object({
type: joi.array().length(1).required(),
subject: joi.required(),
participant: joi.array().length(1).required(),
period: joi.object({
start: joi.string(),
end: joi.string()
})
});
18 changes: 18 additions & 0 deletions mediator/src/middlewares/schemas/tests/cht-request-factories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ export const ChtPatientDoc = Factory.define('chtPatientDoc')
.attr('sex', 'female')
.attr('patient_id', randomUUID());

export const ChtSMSPatientFactory = Factory.define('chtPatient')
.attr('doc', () => ChtSMSPatientDoc.build())

export const ChtSMSPatientDoc = Factory.define('chtPatientDoc')
.attr('_id', randomUUID())
.attr('name', 'John Doe')
.attr('phone', '+9770000000')
.attr('date_of_birth', '2000-01-01')
.attr('sex', 'female')
.attr('source_id', randomUUID());

export const ChtPatientIdsFactory = Factory.define('chtPatientIds')
.attr('doc', () => ChtPatientIdsDoc.build())

export const ChtPatientIdsDoc = Factory.define('chtPatientIds')
.attr('external_id', randomUUID())
.attr('patient_uuid', randomUUID());

export const ChtPregnancyForm = Factory.define('chtPregnancyDoc')
.attr('patient_uuid', randomUUID())
.attr('reported_date', Date.now())
Expand Down
24 changes: 19 additions & 5 deletions mediator/src/middlewares/schemas/tests/fhir-resource-factories.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { randomUUID } from 'crypto';
import { Factory } from 'rosie';
import { VALID_CODE, VALID_SYSTEM } from '../endpoint';
import { chtDocumentIdentifierType } from '../../../mappers/cht';

const identifier = [
{
type: {
text: 'CHT Document Identifier'
},
type: chtDocumentIdentifierType,
system: 'cht',
value: randomUUID(),
},
Expand All @@ -28,11 +27,15 @@ export const EncounterFactory = Factory.define('encounter')
.attr('resourceType', 'Encounter')
.attr('id', randomUUID())
.attr('identifier', identifier)
.attr('status', 'planned')
.attr('status', 'finished')
.attr('class', 'outpatient')
.attr('type', [{ text: 'Community health worker visit' }])
.attr('subject', { reference: 'Patient/3' })
.attr('participant', [{ type: [{ text: 'Community health worker' }] }]);
.attr('participant', [{ type: [{ text: 'Community health worker' }] }])
.attr('period', {
start: new Date(new Date().getTime() - 60 * 60 * 1000).toISOString(),
end: new Date(new Date().getTime() - 50 * 60 * 1000).toISOString()
})

export const EndpointFactory = Factory.define('endpoint')
.attr('connectionType', { system: VALID_SYSTEM, code: VALID_CODE })
Expand All @@ -56,3 +59,14 @@ export const ServiceRequestFactory = Factory.define('serviceRequest')
.attr('intent', 'order')
.attr('subject', SubjectFactory.build())
.attr('requester', RequesterFactory.build());

export const ObservationFactory = Factory.define('Observation')
.attr('resourceType', 'Observation')
.attr('id', () => randomUUID())
.attr('encounter', () => { reference: 'Encounter/' + randomUUID() })
.attr('code', {
coding: [{ code: 'DANGER_SIGNS' }],
})
.attr('valueCodeableConcept', {
coding: [{ code: 'HIGH_BLOOD_PRESSURE' }]
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { randomUUID } from 'crypto';
import { Factory } from 'rosie';
import { visitNoteType, visitType } from '../../../mappers/openmrs';

// creates an openmrs patient with the special address extension
export const OpenMRSPatientFactory = Factory.define('openMRSFhirPatient')
.attr('resourceType', 'Patient')
.attr('id', () => randomUUID()) // Assign a random UUID for the patient
.attr('address', ['addressKey', 'addressValue'], (addressKey, addressValue) => [
{
extension: [{
extension: [
{
url: `http://fhir.openmrs.org/ext/address#${addressKey}`,
valueString: addressValue
}
]
}]
}
]);

// creates an openmrs encounter with visit type
export const OpenMRSVisitFactory = Factory.define('openMRSVisit')
.attr('resourceType', 'Encounter')
.attr('id', () => randomUUID()) // Assign a random UUID for the patient
.attr('type', visitType);

// creates an openmrs encounter with visit note type
export const OpenMRSVisitNoteFactory = Factory.define('openMRSVisit')
.attr('resourceType', 'Encounter')
.attr('id', () => randomUUID()) // Assign a random UUID for the patient
.attr('type', visitNoteType);
2 changes: 1 addition & 1 deletion mediator/src/routes/cht.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ router.post(

router.post(
'/patient_ids',
requestHandler((req) => updatePatientIds(req.body.doc))
requestHandler((req) => updatePatientIds(req.body))
);

router.post(
Expand Down
Loading

0 comments on commit 7d279c1

Please sign in to comment.