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

simplify consent discovery #2

Merged
merged 1 commit into from
Nov 30, 2024
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
77 changes: 20 additions & 57 deletions lib/consent-discovery.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const _ = require("lodash");
const superagent = require("superagent");
const logger = require("../lib/logger");
const { maybeAddAuth } = require("../lib/auth");
Expand All @@ -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);
Expand All @@ -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(
Expand All @@ -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
};
75 changes: 8 additions & 67 deletions test/common/setup-mock-consent-servers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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(
Expand All @@ -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,
Expand Down
16 changes: 0 additions & 16 deletions test/controllers/discovery.test.js

This file was deleted.

24 changes: 5 additions & 19 deletions test/controllers/patient-consent-consult.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const Ajv = require("ajv");
const request = require("supertest");
const { app } = require("../../app");
const {
setupMockPatient,
setupMockConsent,
setupMockOrganization,
setupMockAuditEndpoint
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
11 changes: 1 addition & 10 deletions test/controllers/xacml-schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const nock = require("nock");
const request = require("supertest");
const { app } = require("../../app");
const {
setupMockPatient,
setupMockConsent,
setupMockOrganization,
setupMockAuditEndpoint
Expand Down Expand Up @@ -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,
Expand Down
Loading