Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

test: IGs: Validation during create/update and supportedProfile in CapStatement #237

Merged
merged 7 commits into from
Mar 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 132 additions & 13 deletions integration-tests/implementationGuides.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import { AxiosInstance } from 'axios';
import axios, { AxiosInstance } from 'axios';
import waitForExpect from 'wait-for-expect';
import { Chance } from 'chance';
import { cloneDeep } from 'lodash';
import { expectResourceToBePartOfSearchResults, getFhirClient, randomPatient } from './utils';
import { CapabilityStatement } from './types';

Expand All @@ -15,16 +15,33 @@ describe('Implementation Guides - US Core', () => {
beforeAll(async () => {
client = await getFhirClient();
});
test('capability statement includes search parameters', async () => {
const capabilityStatement: CapabilityStatement = (await client.get('metadata')).data;

const usCorePatientSearchParams = capabilityStatement.rest[0].resource
function getResourcesWithSupportedProfile(capStatement: CapabilityStatement) {
const resourcesWithSupportedProfile: Record<string, string[]> = {};
capStatement.rest[0].resource
.filter(resource => {
return resource.supportedProfile;
})
.forEach(resource => {
if (resource.type) {
resourcesWithSupportedProfile[resource.type] = resource.supportedProfile!.sort();
}
});

return resourcesWithSupportedProfile;
}

test('capability statement includes search parameters and supportedProfile', async () => {
const actualCapabilityStatement: CapabilityStatement = (await client.get('metadata')).data;

const usCorePatientSearchParams = actualCapabilityStatement.rest[0].resource
.filter(resource => resource.type === 'Patient')
.flatMap(resource => resource.searchParam ?? [])
.filter(searchParam =>
searchParam.definition.startsWith('http://hl7.org/fhir/us/core/SearchParameter/us-core'),
);

// Check for expected search params
expect(usCorePatientSearchParams).toEqual(
// There are many more search parameters in US Core but they are all loaded into FWoA in the same way.
// Checking only a few of them is good enough
Expand Down Expand Up @@ -60,13 +77,27 @@ describe('Implementation Guides - US Core', () => {
},
]),
);

const actualResourcesWithSupportedProfile: Record<string, string[]> = getResourcesWithSupportedProfile(
actualCapabilityStatement,
);

const expectedCapStatement: CapabilityStatement = (
await axios.get('https://www.hl7.org/fhir/us/core/CapabilityStatement-us-core-server.json')
).data;

const expectedResourcesWithSupportedProfile: Record<string, string[]> = getResourcesWithSupportedProfile(
expectedCapStatement,
);

// Check for expected supportedProfile
expect(actualResourcesWithSupportedProfile).toEqual(expectedResourcesWithSupportedProfile);
});

test('query using search parameters', async () => {
const chance = new Chance();
const raceCode = chance.word({ length: 15 });
const ethnicityCode = chance.word({ length: 15 });
const usCorePatient = {
const ethnicityCode = '2148-5';
const raceCode = '2106-3';
function getRandomPatientWithEthnicityAndRace() {
const patient = {
...randomPatient(),
...{
extension: [
Expand All @@ -80,6 +111,10 @@ describe('Implementation Guides - US Core', () => {
display: 'White',
},
},
{
url: 'text',
valueString: 'Caucasian',
},
],
url: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-race',
},
Expand All @@ -93,14 +128,98 @@ describe('Implementation Guides - US Core', () => {
display: 'Mexican',
},
},
{
url: 'text',
valueString: 'Hispanic',
},
],
url: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity',
},
],
},
};
return patient;
}

const noTextFieldErrorResponse = {
status: 400,
data: {
resourceType: 'OperationOutcome',
text: {
status: 'generated',
div:
'<div xmlns="http://www.w3.org/1999/xhtml"><h1>Operation Outcome</h1><table border="0"><tr><td style="font-weight: bold;">error</td><td>[]</td><td><pre>Patient.extension[0].extension[1] - The property extension must be an Array, not null (at Patient.extension[0].extension[1])\nPatient.extension[1].extension[1] - The property extension must be an Array, not null (at Patient.extension[1].extension[1])\nPatient.extension[0] - Extension.extension:text: minimum required = 1, but only found 0 (from http://hl7.org/fhir/us/core/StructureDefinition/us-core-race)\nPatient.extension[1] - Extension.extension:text: minimum required = 1, but only found 0 (from http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity)</pre></td></tr></table></div>',
},
issue: [
{
severity: 'error',
code: 'invalid',
diagnostics:
'Patient.extension[0].extension[1] - The property extension must be an Array, not null (at Patient.extension[0].extension[1])\nPatient.extension[1].extension[1] - The property extension must be an Array, not null (at Patient.extension[1].extension[1])\nPatient.extension[0] - Extension.extension:text: minimum required = 1, but only found 0 (from http://hl7.org/fhir/us/core/StructureDefinition/us-core-race)\nPatient.extension[1] - Extension.extension:text: minimum required = 1, but only found 0 (from http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity)',
},
],
},
};

describe('Updating patient', () => {
let patientId = '';
beforeAll(async () => {
const patient = getRandomPatientWithEthnicityAndRace();
const { data } = await client.post('Patient', patient);
patientId = data.id;
});

test('valid US Core patient', async () => {
const patient = getRandomPatientWithEthnicityAndRace();
patient.id = patientId;

await expect(client.put(`Patient/${patientId}`, patient)).resolves.toMatchObject({
status: 200,
data: patient,
});
});

test('invalid US Core patient: no text field', async () => {
const patient = getRandomPatientWithEthnicityAndRace();
patient.id = patientId;

// Remove text field
delete patient.extension[0].extension[1];
delete patient.extension[1].extension[1];

await expect(client.put(`Patient/${patientId}`, patient)).rejects.toMatchObject({
response: noTextFieldErrorResponse,
});
});
});

describe('Creating patient', () => {
test('valid US Core patient', async () => {
const patient = getRandomPatientWithEthnicityAndRace();

const expectedPatient: any = cloneDeep(patient);
delete expectedPatient.id;
await expect(client.post('Patient', patient)).resolves.toMatchObject({
status: 201,
data: expectedPatient,
});
});

test('invalid US Core patient: no text field', async () => {
const patient = getRandomPatientWithEthnicityAndRace();
// Remove text field
delete patient.extension[0].extension[1];
delete patient.extension[1].extension[1];
await expect(client.post('Patient', patient)).rejects.toMatchObject({
response: noTextFieldErrorResponse,
});
});
});

test('query using search parameters', async () => {
const patient = getRandomPatientWithEthnicityAndRace();

const testPatient: ReturnType<typeof randomPatient> = (await client.post('Patient', usCorePatient)).data;
const testPatient: ReturnType<typeof randomPatient> = (await client.post('Patient', patient)).data;
carvantes marked this conversation as resolved.
Show resolved Hide resolved

// wait for the patient to be asynchronously written to ES
await waitForExpect(
Expand All @@ -121,8 +240,8 @@ describe('Implementation Guides - US Core', () => {

const p = (params: any) => ({ url: 'Patient', params });
const testsParams = [
p({ race: raceCode }),
p({ ethnicity: ethnicityCode }),
p({ race: raceCode, name: testPatient.name[0].family }),
p({ ethnicity: ethnicityCode, name: testPatient.name[0].family }),
p({ given: testPatient.name[0].given[0] }), // US Core "given" is functionally the same as the base FHIR "given"
];

Expand Down
1 change: 1 addition & 0 deletions integration-tests/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export interface Resource {
searchParam?: SearchParam[];
searchInclude?: string[];
searchRevInclude?: string[];
supportedProfile?: string[];
}

export interface REST {
Expand Down