diff --git a/lib/consent-discovery.js b/lib/consent-discovery.js index 4430836..8213723 100644 --- a/lib/consent-discovery.js +++ b/lib/consent-discovery.js @@ -1,4 +1,3 @@ -const _ = require("lodash"); const superagent = require("superagent"); const logger = require("../lib/logger"); const { maybeAddAuth } = require("../lib/auth"); @@ -13,26 +12,33 @@ const addFhirBaseToEntry = (entry, url) => ({ }); const addFhirBaseToEntries = (entries, url) => - entries.map((entry) => addFhirBaseToEntry(entry, url)); + entries ? entries.map((entry) => addFhirBaseToEntry(entry, url)) : []; -async function fetchConsents(patientIdentifiers, category) { +async function fetchConsents(patientIdentifiers) { try { - const patientFhirIds = await patientIds(patientIdentifiers); + const consentSearchQueries = CONSENT_FHIR_SERVERS.map((fhirBase) => + patientIdentifiers.map((patientIdentifier) => + resolveConsents(fhirBase, patientIdentifier) + ) + ).flat(); - const consentSearchQueries = patientFhirIds.map(({ fhirBase, patientId }) => - resolveConsents(fhirBase, patientId, category) - ); + const fhirBasePointers = CONSENT_FHIR_SERVERS.map((fhirBase) => + patientIdentifiers.map(() => fhirBase) + ).flat(); const consentSearchResults = await Promise.all(consentSearchQueries); const resolvedConsents = consentSearchResults - .filter(({ body }) => body?.entry?.length) .map(({ body }, index) => ({ ...body, - entry: addFhirBaseToEntries(body.entry, patientFhirIds[index].fhirBase) + entry: addFhirBaseToEntries( + body.entry, + fhirBasePointers[index] + ) })) + .filter((body) => body?.entry?.length) .map((bundle) => bundle.entry) .flat(); - + return resolvedConsents; } catch (e) { console.log(e); @@ -45,11 +51,12 @@ async function fetchConsents(patientIdentifiers, category) { } } -function resolveConsents(fhirBase, patientId, category) { +function resolveConsents(fhirBase, patientIdentifier) { const patientParam = { - patient: `Patient/${patientId}` + "patient.identifier": patientIdentifier.system + ? `${patientIdentifier.system}|${patientIdentifier.value}` + : `|${patientIdentifier.value}` }; - //todo: incorporate categoty into the search parameters const params = { ...patientParam }; return maybeAddAuth( @@ -60,50 +67,6 @@ function resolveConsents(fhirBase, patientId, category) { ); } -async function patientIds(patientIdentifiers) { - const patientIdQueries = CONSENT_FHIR_SERVERS.map((fhirBase) => - resolvePatientOnServer(fhirBase, patientIdentifiers) - ); - - const patientIds = await Promise.all(patientIdQueries); - - return _.zipWith(CONSENT_FHIR_SERVERS, patientIds, (fhirBase, patientId) => ({ - fhirBase, - patientId - })).filter(({ patientId }) => patientId); -} - -async function resolvePatientOnServer(fhirBase, patientIdentifiers) { - const patientSearchQueries = patientIdentifiers.map((patientIdentifier) => - resolvePatientOnServerByIdentifer(fhirBase, patientIdentifier) - ); - const patientIdSearchResults = await Promise.all(patientSearchQueries); - - const patientIds = patientIdSearchResults - .map((patientIdResult) => firstPatientId(patientIdResult)) - .filter((patientId) => patientId); - - return patientIds?.[0]; -} - -function resolvePatientOnServerByIdentifer(fhirBase, patientIdentifier) { - const query = { - identifier: patientIdentifier.system - ? `${patientIdentifier.system}|${patientIdentifier.value}` - : `|${patientIdentifier.value}` - }; - return maybeAddAuth( - superagent - .get(`${fhirBase}/Patient`) - .query(query) - // .query({ _summary: "true" }) not implemented by some servers. - .set({ Accept: "application/json, application/fhir+json" }) - ); -} - -const firstPatientId = (patientSearchResult) => - patientSearchResult.body?.entry?.[0]?.resource?.id; - module.exports = { fetchConsents }; diff --git a/test/common/setup-mock-consent-servers.js b/test/common/setup-mock-consent-servers.js index a31ab5b..7b52c55 100644 --- a/test/common/setup-mock-consent-servers.js +++ b/test/common/setup-mock-consent-servers.js @@ -8,38 +8,12 @@ const CONSENT_FHIR_SERVERS = (process.env.CONSENT_FHIR_SERVERS || "") const EMPTY_BUNDLE = require("../fixtures/empty-bundle.json"); const PATIENT = require("../fixtures/patients/patient-boris.json"); -const PATIENT_RESULTS_BUNDLE = _.set( - _.set(_.clone(EMPTY_BUNDLE), "entry[0].resource", PATIENT), - "total", - 1 -); - const MOCK_FHIR_SERVERS = CONSENT_FHIR_SERVERS.map((fhirBase) => nock(fhirBase) .defaultReplyHeaders({ "Content-Type": "application/json; charset=utf-8" }) .replyContentLength() ); -function setupMockPatient(patientId, index) { - const fhirServerIndex = index || 0; - const system = patientId.system; - const value = patientId.value; - - MOCK_FHIR_SERVERS[fhirServerIndex] - .get("/Patient") - .query({ identifier: `${system}|${value}` }) - .reply(200, PATIENT_RESULTS_BUNDLE); - - for (var i = 0; i < MOCK_FHIR_SERVERS.length; i++) { - if (i == index) continue; - - MOCK_FHIR_SERVERS[i] - .get("/Patient") - .query({ identifier: `${system}|${value}` }) - .reply(200, EMPTY_BUNDLE); - } -} - function setupMockOrganization(url, organizationResource, howManyRequests) { const numberOfTimes = howManyRequests || 1; MOCK_FHIR_SERVERS[0] @@ -61,42 +35,12 @@ function setupMockPractitioner(url, practitionerResource, howManyRequests) { .reply(200, practitionerResource); } -function setupMockConsent(category, consent, index, patientId) { - const fhirServerIndex = index || 0; - const fhirPatientId = patientId || "Patient/52"; - - const CONSENT_RESULTS_BUNDLE = consent - ? _.set( - _.set( - _.set( - _.clone(EMPTY_BUNDLE), - "entry[0].resource", - _.set(consent, "id", "1") - ), - "entry[0].fullUrl", - `${CONSENT_FHIR_SERVERS[0]}/Consent/1` - ), - "total", - 1 - ) - : EMPTY_BUNDLE; - - MOCK_FHIR_SERVERS[fhirServerIndex] - .get(`/Consent?patient=${fhirPatientId}`) - .reply(200, CONSENT_RESULTS_BUNDLE); - - for (var i = 0; i < MOCK_FHIR_SERVERS.length; i++) { - if (i == index) continue; - - MOCK_FHIR_SERVERS[i] - .get(`/Consent?patient=${fhirPatientId}`) - .reply(200, EMPTY_BUNDLE); - } -} - -function setupMockConsentNoCategory(consent, index, patientId) { +function setupMockConsent(consent, index, patientIdentifier) { const fhirServerIndex = index || 0; - const fhirPatientId = patientId || "Patient/52"; + const { system, value } = patientIdentifier || { + system: "http://hl7.org/fhir/sid/us-medicare", + value: "0000-000-0000" + }; const CONSENT_RESULTS_BUNDLE = consent ? _.set( @@ -115,22 +59,19 @@ function setupMockConsentNoCategory(consent, index, patientId) { : EMPTY_BUNDLE; MOCK_FHIR_SERVERS[fhirServerIndex] - .get(`/Consent?patient=${fhirPatientId}`) + .get(`/Consent?patient.identifier=${system}|${value}`) .reply(200, CONSENT_RESULTS_BUNDLE); for (var i = 0; i < MOCK_FHIR_SERVERS.length; i++) { - if (i == index) continue; - + if (i == fhirServerIndex) continue; MOCK_FHIR_SERVERS[i] - .get(`/Consent?patient=${fhirPatientId}`) + .get(`/Consent?patient.identifier=${system}|${value}`) .reply(200, EMPTY_BUNDLE); } } module.exports = { - setupMockPatient, setupMockConsent, - setupMockConsentNoCategory, setupMockOrganization, setupMockPractitioner, setupMockAuditEndpoint, diff --git a/test/controllers/discovery.test.js b/test/controllers/discovery.test.js deleted file mode 100644 index 2e48492..0000000 --- a/test/controllers/discovery.test.js +++ /dev/null @@ -1,16 +0,0 @@ -const request = require("supertest"); -const { app } = require("../../app"); - -it("should respond to ping", async () => { - expect.assertions(2); - - const res = await request(app).get("/cds-services"); - expect(res.status).toEqual(200); - expect(res.body).toEqual( - expect.objectContaining({ - services: expect.arrayContaining([ - expect.objectContaining({ id: "patient-consent-consult" }) - ]) - }) - ); -}); diff --git a/test/controllers/patient-consent-consult.test.js b/test/controllers/patient-consent-consult.test.js index 57dac42..ebffd88 100644 --- a/test/controllers/patient-consent-consult.test.js +++ b/test/controllers/patient-consent-consult.test.js @@ -6,7 +6,6 @@ const Ajv = require("ajv"); const request = require("supertest"); const { app } = require("../../app"); const { - setupMockPatient, setupMockConsent, setupMockOrganization, setupMockAuditEndpoint @@ -121,19 +120,13 @@ it("should return 400 on bad content", async () => { const REQUEST = require("../fixtures/request-samples/patient-consent-consult-hook-request.json"); -const MOCK_PATIENT_ID = { - system: "http://hl7.org/fhir/sid/us-medicare", - value: "0000-000-0000" -}; - const ORGANIZATION = require("../fixtures/organizations/org-good-health.json"); it("should return 200 and an array including a consent permit card with an OPTIN consent", async () => { expect.assertions(2); setupMockAuditEndpoint(); - setupMockPatient(MOCK_PATIENT_ID); - setupMockConsent("patient-privacy", CONSENT_OPTIN); + setupMockConsent(CONSENT_OPTIN); setupMockOrganization( `/${_.get( CONSENT_OPTIN, @@ -165,8 +158,7 @@ it("should return 200 and an array including a consent deny card with an OPTIN c expect.assertions(2); setupMockAuditEndpoint(); - setupMockPatient(MOCK_PATIENT_ID); - setupMockConsent("patient-privacy", CONSENT_OPTIN); + setupMockConsent(CONSENT_OPTIN); setupMockOrganization( `/${_.get( CONSENT_OPTIN, @@ -204,8 +196,7 @@ it("should return 200 and an array including a consent deny card with an OPTOUT expect.assertions(2); setupMockAuditEndpoint(); - setupMockPatient(MOCK_PATIENT_ID); - setupMockConsent("patient-privacy", CONSENT_OPTOUT); + setupMockConsent(CONSENT_OPTOUT); setupMockOrganization( `/${_.get( CONSENT_OPTOUT, @@ -240,11 +231,7 @@ it("should return 200 and an array including a consent permit card with obligati const ACTIVE_PRIVACY_CONSENT_WITH_SEC_LABEL_PROVISION = require("../fixtures/consents/r4/consent-boris-deny-restricted-label.json"); - setupMockPatient(MOCK_PATIENT_ID); - setupMockConsent( - "patient-privacy", - ACTIVE_PRIVACY_CONSENT_WITH_SEC_LABEL_PROVISION - ); + setupMockConsent(ACTIVE_PRIVACY_CONSENT_WITH_SEC_LABEL_PROVISION); setupMockOrganization( `/${_.get( ACTIVE_PRIVACY_CONSENT_WITH_SEC_LABEL_PROVISION, @@ -315,8 +302,7 @@ it("should return 200 and an array including a consent permit card with obligati it("should return 200 and an array including a NO_CONSENT card when no consent exists", async () => { expect.assertions(2); - setupMockPatient(MOCK_PATIENT_ID); - setupMockConsent("patient-privacy", null); + setupMockConsent(null); const res = await request(app) .post(HOOK_ENDPOINT) diff --git a/test/controllers/xacml-schema.test.js b/test/controllers/xacml-schema.test.js index dbbd709..d96c75d 100644 --- a/test/controllers/xacml-schema.test.js +++ b/test/controllers/xacml-schema.test.js @@ -4,7 +4,6 @@ const nock = require("nock"); const request = require("supertest"); const { app } = require("../../app"); const { - setupMockPatient, setupMockConsent, setupMockOrganization, setupMockAuditEndpoint @@ -237,21 +236,13 @@ it("should return 400 on missing required attribtues", async () => { it("should return a response compliant with the response schema", async () => { const REQUEST = require("../fixtures/request-samples/xacml-request.json"); - const MOCK_PATIENT_ID = { - system: "http://hl7.org/fhir/sid/us-medicare", - value: "0000-000-0000" - }; const ORGANIZATION = require("../fixtures/organizations/org-good-health.json"); const ACTIVE_PRIVACY_CONSENT_WITH_SEC_LABEL_PROVISION = require("../fixtures/consents/r4/consent-boris-deny-restricted-label.json"); expect.assertions(1); setupMockAuditEndpoint(); - setupMockPatient(MOCK_PATIENT_ID); - setupMockConsent( - "patient-privacy", - ACTIVE_PRIVACY_CONSENT_WITH_SEC_LABEL_PROVISION - ); + setupMockConsent(ACTIVE_PRIVACY_CONSENT_WITH_SEC_LABEL_PROVISION); setupMockOrganization( `/${_.get( ACTIVE_PRIVACY_CONSENT_WITH_SEC_LABEL_PROVISION, diff --git a/test/controllers/xacml.test.js b/test/controllers/xacml.test.js index c5f9ff6..52bcb63 100644 --- a/test/controllers/xacml.test.js +++ b/test/controllers/xacml.test.js @@ -4,7 +4,6 @@ const nock = require("nock"); const request = require("supertest"); const { app } = require("../../app"); const { - setupMockPatient, setupMockConsent, setupMockOrganization, setupMockAuditEndpoint @@ -21,19 +20,13 @@ afterEach(() => { const REQUEST = require("../fixtures/request-samples/xacml-request.json"); -const MOCK_PATIENT_ID = { - system: "http://hl7.org/fhir/sid/us-medicare", - value: "0000-000-0000" -}; - const ORGANIZATION = require("../fixtures/organizations/org-good-health.json"); it("should return 200 and a permit card with an OPTIN consent", async () => { expect.assertions(2); setupMockAuditEndpoint(); - setupMockPatient(MOCK_PATIENT_ID); - setupMockConsent("patient-privacy", CONSENT_OPTIN); + setupMockConsent(CONSENT_OPTIN); setupMockOrganization( `/${_.get( CONSENT_OPTIN, @@ -58,8 +51,7 @@ it("should return 200 and a deny response with an OPTIN consent and provision wi expect.assertions(2); setupMockAuditEndpoint(); - setupMockPatient(MOCK_PATIENT_ID); - setupMockConsent("patient-privacy", CONSENT_OPTIN); + setupMockConsent(CONSENT_OPTIN); setupMockOrganization( `/${_.get( CONSENT_OPTIN, @@ -89,8 +81,7 @@ it("should return 200 and a deny response with an OPTIN consent and provision wi it("should return 200 and a deny response with an OPTOUT consent", async () => { expect.assertions(2); setupMockAuditEndpoint(); - setupMockPatient(MOCK_PATIENT_ID); - setupMockConsent("patient-privacy", CONSENT_OPTOUT); + setupMockConsent(CONSENT_OPTOUT); setupMockOrganization( `/${_.get( CONSENT_OPTOUT, @@ -117,11 +108,7 @@ it("should return 200 and a consent permit response with obligations when a cons const ACTIVE_PRIVACY_CONSENT_WITH_SEC_LABEL_PROVISION = require("../fixtures/consents/r4/consent-boris-deny-restricted-label.json"); setupMockAuditEndpoint(); - setupMockPatient(MOCK_PATIENT_ID); - setupMockConsent( - "patient-privacy", - ACTIVE_PRIVACY_CONSENT_WITH_SEC_LABEL_PROVISION - ); + setupMockConsent(ACTIVE_PRIVACY_CONSENT_WITH_SEC_LABEL_PROVISION); setupMockOrganization( `/${_.get( ACTIVE_PRIVACY_CONSENT_WITH_SEC_LABEL_PROVISION, @@ -174,8 +161,7 @@ it("should return 200 and a consent permit response with obligations when a cons it("should return 200 and an array including a NO_CONSENT card when no consent exists", async () => { expect.assertions(2); - setupMockPatient(MOCK_PATIENT_ID); - setupMockConsent("patient-privacy", null); + setupMockConsent(null); const res = await request(app) .post(ENDPOINT) diff --git a/test/lib/consent-discovery.test.js b/test/lib/consent-discovery.test.js index 035eefe..8fe7f73 100644 --- a/test/lib/consent-discovery.test.js +++ b/test/lib/consent-discovery.test.js @@ -4,9 +4,7 @@ const { fetchConsents } = require("../../lib/consent-discovery"); const CONSENT = require("../fixtures/consents/r4/consent-boris-optin.json"); const { - setupMockPatient, setupMockConsent, - setupMockConsentNoCategory, MOCK_FHIR_SERVERS } = require("../common/setup-mock-consent-servers"); @@ -18,50 +16,23 @@ it("make sure there is at least one FHIR Consent Server", async () => { expect(MOCK_FHIR_SERVERS.length).toBeGreaterThan(0); }); -it("should return an array of consents from all servers with consent category", async () => { +it("should return an array of consents from all servers", async () => { expect.assertions(1); - setupMockPatient({ system: "ssn", value: "111111111" }); - setupMockConsent("patient-privacy", CONSENT); + setupMockConsent(CONSENT); - const consents = await fetchConsents( - [{ system: "ssn", value: "111111111" }], - "patient-privacy" - ); - expect(consents).toHaveLength(1); -}); - -it("should return an array of consents from all servers without consent category", async () => { - expect.assertions(1); - - setupMockPatient({ system: "ssn", value: "111111111" }); - setupMockConsentNoCategory(CONSENT); - - const consents = await fetchConsents([{ system: "ssn", value: "111111111" }]); - expect(consents).toHaveLength(1); -}); - -it("should return an array of consents from all servers based on multiple patient identifiers", async () => { - expect.assertions(1); - - setupMockPatient({ system: "ssn", value: "111111111" }, 0); - setupMockPatient({ system: "other-system", value: "22222222" }, 1); - setupMockConsent("patient-privacy", CONSENT, 0); - - const consents = await fetchConsents( - [ - { system: "other-system", value: "22222222" }, - { system: "ssn", value: "111111111" } - ], - "patient-privacy" - ); + const consents = await fetchConsents([ + { system: "http://hl7.org/fhir/sid/us-medicare", value: "0000-000-0000" } + ]); expect(consents).toHaveLength(1); }); it("should throw an exception if consent servers don't respond.", async () => { expect.assertions(1); try { - await fetchConsents([{ system: "ssn", value: "111111111" }]); + await fetchConsents([ + { system: "http://hl7.org/fhir/sid/us-medicare", value: "0000-000-0000" } + ]); } catch (e) { expect(e).toMatchObject({ error: "service_unavailable"