diff --git a/src/client/BaseClient.js b/src/client/BaseClient.js index da848882..feaa6940 100644 --- a/src/client/BaseClient.js +++ b/src/client/BaseClient.js @@ -1,4 +1,4 @@ -const { isValidFHIR } = require('../helpers/fhirUtils'); +const { isValidFHIR, invalidResourcesFromBundle } = require('../helpers/fhirUtils'); const logger = require('../helpers/logger'); class BaseClient { @@ -97,7 +97,7 @@ class BaseClient { }, Promise.resolve(contextBundle)); if (!isValidFHIR(contextBundle)) { - logger.error('Extracted bundle is not valid FHIR.'); + logger.warn(`Extracted bundle is not valid FHIR, the following resources failed validation: ${invalidResourcesFromBundle(contextBundle).join(',')}`); } return { bundle: contextBundle, extractionErrors }; diff --git a/src/helpers/fhirUtils.js b/src/helpers/fhirUtils.js index 81d1b3c8..e98969cc 100644 --- a/src/helpers/fhirUtils.js +++ b/src/helpers/fhirUtils.js @@ -163,8 +163,22 @@ const logOperationOutcomeInfo = (operationOutcome) => { }); }; -const isValidFHIR = (resource) => validator.validate('FHIR', resource); - +function isValidFHIR(resource) { + return validator.validate('FHIR', resource); +} +function invalidResourcesFromBundle(bundle) { + // Bundle is assumed to have all resources in bundle.entry[x].resource + const failingResources = []; + bundle.entry.forEach((e) => { + const { resource } = e; + const { id, resourceType } = resource; + if (!validator.validate('FHIR', resource)) { + const failureId = `${resourceType}-${id}`; + failingResources.push(failureId); + } + }); + return failingResources; +} module.exports = { allResourcesInBundle, @@ -182,4 +196,5 @@ module.exports = { mapFHIRVersions, quantityCodeToUnitLookup, isValidFHIR, + invalidResourcesFromBundle, }; diff --git a/test/client/BaseClient.test.js b/test/client/BaseClient.test.js index 7ca874fb..a77de7f9 100644 --- a/test/client/BaseClient.test.js +++ b/test/client/BaseClient.test.js @@ -76,8 +76,8 @@ describe('BaseClient', () => { }); it('should iterate over all extractors gets, aggregating resultant entries in a bundle', async () => { const extractorClassesWithEntryGets = [ - class Ext1 { get() { return { entry: [{ data: 'here' }] }; }}, - class Ext2 { get() { return { entry: [{ data: 'alsoHere' }] }; }}, + class Ext1 { get() { return { entry: [{ resource: 'here' }] }; }}, + class Ext2 { get() { return { entry: [{ resource: 'alsoHere' }] }; }}, class Ext3 { get() { throw new Error('Error'); }}, ]; engine.registerExtractors(...extractorClassesWithEntryGets); diff --git a/test/helpers/fhirUtils.test.js b/test/helpers/fhirUtils.test.js index da841915..7b8b9425 100644 --- a/test/helpers/fhirUtils.test.js +++ b/test/helpers/fhirUtils.test.js @@ -7,6 +7,8 @@ const { allResourcesInBundle, quantityCodeToUnitLookup, getResourceCountInBundle, + isValidFHIR, + invalidResourcesFromBundle, } = require('../../src/helpers/fhirUtils.js'); const emptyBundle = require('./fixtures/emptyBundle.json'); const bundleWithOneEntry = require('./fixtures/searchsetBundleWithOneEntry.json'); @@ -14,6 +16,8 @@ const bundleWithMultipleEntries = require('./fixtures/searchsetBundleWithMultipl const countBundle5Unique = require('./fixtures/count-bundle-5-unique.json'); const countBundle5Same = require('./fixtures/count-bundle-5-same.json'); const countBundle5Nested = require('./fixtures/count-bundle-5-nested.json'); +const validResource = require('./fixtures/valid-resource'); +const invalidResource = require('./fixtures/invalid-resource'); describe('getQuantityUnit', () => { test('Should return unit text if provided in lookup table', () => { @@ -137,3 +141,25 @@ describe('getResourceCountInBundle', () => { expect(getResourceCountInBundle(countBundle5Nested)).toEqual(counts); }); }); + +describe('isValidFHIR', () => { + test('Should return true when provided valid FHIR resources', () => { + expect(isValidFHIR(validResource)).toEqual(true); + }); + test('Should return false when provided invalid FHIR resources', () => { + expect(isValidFHIR(invalidResource)).toEqual(false); + }); +}); + +describe('invalidResourcesFromBundle', () => { + test('Should return an empty array when all resources are valid', () => { + expect(invalidResourcesFromBundle(emptyBundle)).toEqual([]); + }); + test('Should return an array of all invalid resources when they exist', () => { + const invalidBundle = { ...bundleWithOneEntry }; + invalidBundle.entry[0].resource = invalidResource; + // This is dependent on implementation details intrinsic to invalidResourcesFromBundle + const invalidResourceId = `${invalidResource.resourceType}-${invalidResource.id}`; + expect(invalidResourcesFromBundle(invalidBundle)).toEqual([invalidResourceId]); + }); +}); diff --git a/test/helpers/fixtures/invalid-resource.json b/test/helpers/fixtures/invalid-resource.json new file mode 100644 index 00000000..f4fc75bc --- /dev/null +++ b/test/helpers/fixtures/invalid-resource.json @@ -0,0 +1,14 @@ +{ + "resourceType": "Patient", + "id": "invalid-patient", + "name": [ + { + "family": "Godel", + "given": [ + "Kurt" + ] + } + ], + "gender": "invalid-enum-value", + "birthDate": "1906-04-28" +} diff --git a/test/helpers/fixtures/valid-resource.json b/test/helpers/fixtures/valid-resource.json new file mode 100644 index 00000000..a9050caf --- /dev/null +++ b/test/helpers/fixtures/valid-resource.json @@ -0,0 +1,14 @@ +{ + "resourceType": "Patient", + "id": "valid-patient", + "name": [ + { + "family": "Frege", + "given": [ + "Gottlob" + ] + } + ], + "gender": "male", + "birthDate": "1848-11-08" +}