Cleanup member-match handler (#4431)

Cleanup member-match helper.

Pass RequestDetails down for partitioning context.
This commit is contained in:
michaelabuckley 2023-01-13 15:49:41 -05:00 committed by GitHub
parent 21b1820294
commit 0d79d2026e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 160 additions and 221 deletions

1
.gitignore vendored
View File

@ -168,3 +168,4 @@ Snap.*
/database/ /database/
/.run/

View File

@ -12,7 +12,7 @@ import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4;
import ca.uhn.fhir.jpa.graphql.GraphQLProvider; import ca.uhn.fhir.jpa.graphql.GraphQLProvider;
import ca.uhn.fhir.jpa.graphql.GraphQLProviderWithIntrospection; import ca.uhn.fhir.jpa.graphql.GraphQLProviderWithIntrospection;
import ca.uhn.fhir.jpa.provider.JpaSystemProvider; import ca.uhn.fhir.jpa.provider.JpaSystemProvider;
import ca.uhn.fhir.jpa.provider.r4.IConsentExtensionProvider; import ca.uhn.fhir.jpa.provider.r4.IMemberMatchConsentHook;
import ca.uhn.fhir.jpa.provider.r4.MemberMatchR4ResourceProvider; import ca.uhn.fhir.jpa.provider.r4.MemberMatchR4ResourceProvider;
import ca.uhn.fhir.jpa.provider.r4.MemberMatcherR4Helper; import ca.uhn.fhir.jpa.provider.r4.MemberMatcherR4Helper;
import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl; import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl;
@ -105,7 +105,7 @@ public class JpaR4Config {
@Autowired IFhirResourceDao<Coverage> theCoverageDao, @Autowired IFhirResourceDao<Coverage> theCoverageDao,
@Autowired IFhirResourceDao<Patient> thePatientDao, @Autowired IFhirResourceDao<Patient> thePatientDao,
@Autowired IFhirResourceDao<Consent> theConsentDao, @Autowired IFhirResourceDao<Consent> theConsentDao,
@Autowired(required = false) IConsentExtensionProvider theExtensionProvider @Autowired(required = false) IMemberMatchConsentHook theExtensionProvider
) { ) {
return new MemberMatcherR4Helper( return new MemberMatcherR4Helper(
theContext, theContext,

View File

@ -20,22 +20,34 @@ package ca.uhn.fhir.jpa.provider.r4;
* #L% * #L%
*/ */
import ca.uhn.fhir.util.ExtensionUtil;
import org.hl7.fhir.instance.model.api.IBaseExtension; import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
public interface IConsentExtensionProvider {
/** /**
* Takes a Consent resource and returns a collection of Extensions that will * Hook for Consent pre-save additions.
* be added to the base resource.
* *
* @param theConsentResource - the consent resource * @deprecated - we just use Consumer now
* @return - a collection of resources (or an empty collection if none). * TODO delete this.
*/ */
default Collection<IBaseExtension> getConsentExtension(IBaseResource theConsentResource) { @Deprecated(since = "6.3.6", forRemoval = true)
return Collections.emptyList(); public interface IConsentExtensionProvider extends IMemberMatchConsentHook {
}; Logger ourLog = LoggerFactory.getLogger(IConsentExtensionProvider.class);
Collection<IBaseExtension> getConsentExtension(IBaseResource theConsentResource);
default void accept(IBaseResource theResource) {
Collection<IBaseExtension> extensions = getConsentExtension(theResource);
for (IBaseExtension ext : extensions) {
IBaseExtension<?, ?> e = ExtensionUtil.addExtension(theResource, ext.getUrl());
e.setValue(ext.getValue());
}
ourLog.trace("{} extension(s) added to Consent", extensions.size());
}
} }

View File

@ -0,0 +1,11 @@
package ca.uhn.fhir.jpa.provider.r4;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.function.Consumer;
/**
* Pre-save hook for Consent saved during $member-match.
*/
public interface IMemberMatchConsentHook extends Consumer<IBaseResource> {
}

View File

@ -34,7 +34,7 @@ import org.hl7.fhir.r4.model.Coverage;
import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Patient;
import javax.servlet.http.HttpServletRequest; import javax.annotation.Nullable;
import java.util.Optional; import java.util.Optional;
public class MemberMatchR4ResourceProvider { public class MemberMatchR4ResourceProvider {
@ -76,32 +76,32 @@ public class MemberMatchR4ResourceProvider {
RequestDetails theRequestDetails RequestDetails theRequestDetails
) { ) {
return doMemberMatchOperation(theServletRequest, theMemberPatient, oldCoverage, newCoverage, theConsent, theRequestDetails); return doMemberMatchOperation(theMemberPatient, oldCoverage, newCoverage, theConsent, theRequestDetails);
} }
private Parameters doMemberMatchOperation(HttpServletRequest theServletRequest, Patient theMemberPatient, private Parameters doMemberMatchOperation(Patient theMemberPatient,
Coverage theCoverageToMatch, Coverage theCoverageToLink, Consent theConsent, RequestDetails theRequestDetails) { Coverage theCoverageToMatch, Coverage theCoverageToLink, Consent theConsent, RequestDetails theRequestDetails) {
validateParams(theMemberPatient, theCoverageToMatch, theCoverageToLink, theConsent); validateParams(theMemberPatient, theCoverageToMatch, theCoverageToLink, theConsent);
Optional<Coverage> coverageOpt = myMemberMatcherR4Helper.findMatchingCoverage(theCoverageToMatch); Optional<Coverage> coverageOpt = myMemberMatcherR4Helper.findMatchingCoverage(theCoverageToMatch, theRequestDetails);
if ( ! coverageOpt.isPresent()) { if (coverageOpt.isEmpty()) {
String i18nMessage = myFhirContext.getLocalizer().getMessage( String i18nMessage = myFhirContext.getLocalizer().getMessage(
"operation.member.match.error.coverage.not.found"); "operation.member.match.error.coverage.not.found");
throw new UnprocessableEntityException(Msg.code(1155) + i18nMessage); throw new UnprocessableEntityException(Msg.code(1155) + i18nMessage);
} }
Coverage coverage = coverageOpt.get(); Coverage coverage = coverageOpt.get();
Optional<Patient> patientOpt = myMemberMatcherR4Helper.getBeneficiaryPatient(coverage); Optional<Patient> patientOpt = myMemberMatcherR4Helper.getBeneficiaryPatient(coverage, theRequestDetails);
if (! patientOpt.isPresent()) { if (patientOpt.isEmpty()) {
String i18nMessage = myFhirContext.getLocalizer().getMessage( String i18nMessage = myFhirContext.getLocalizer().getMessage(
"operation.member.match.error.beneficiary.not.found"); "operation.member.match.error.beneficiary.not.found");
throw new UnprocessableEntityException(Msg.code(1156) + i18nMessage); throw new UnprocessableEntityException(Msg.code(1156) + i18nMessage);
} }
Patient patient = patientOpt.get(); Patient patient = patientOpt.get();
if (!myMemberMatcherR4Helper.validPatientMember(patient, theMemberPatient)) { if (!myMemberMatcherR4Helper.validPatientMember(patient, theMemberPatient, theRequestDetails)) {
String i18nMessage = myFhirContext.getLocalizer().getMessage( String i18nMessage = myFhirContext.getLocalizer().getMessage(
"operation.member.match.error.patient.not.found"); "operation.member.match.error.patient.not.found");
throw new UnprocessableEntityException(Msg.code(2146) + i18nMessage); throw new UnprocessableEntityException(Msg.code(2146) + i18nMessage);
@ -120,7 +120,7 @@ public class MemberMatchR4ResourceProvider {
} }
myMemberMatcherR4Helper.addMemberIdentifierToMemberPatient(theMemberPatient, patient.getIdentifierFirstRep()); myMemberMatcherR4Helper.addMemberIdentifierToMemberPatient(theMemberPatient, patient.getIdentifierFirstRep());
myMemberMatcherR4Helper.updateConsentForMemberMatch(theConsent, patient, theMemberPatient); myMemberMatcherR4Helper.updateConsentForMemberMatch(theConsent, patient, theMemberPatient, theRequestDetails);
return myMemberMatcherR4Helper.buildSuccessReturnParameters(theMemberPatient, theCoverageToLink, theConsent); return myMemberMatcherR4Helper.buildSuccessReturnParameters(theMemberPatient, theCoverageToLink, theConsent);
} }
@ -133,7 +133,7 @@ public class MemberMatchR4ResourceProvider {
validateConsentParam(theConsent); validateConsentParam(theConsent);
} }
private void validateParam(Object theParam, String theParamName) { private void validateParam(@Nullable Object theParam, String theParamName) {
if (theParam == null) { if (theParam == null) {
String i18nMessage = myFhirContext.getLocalizer().getMessage( String i18nMessage = myFhirContext.getLocalizer().getMessage(
"operation.member.match.error.missing.parameter", theParamName); "operation.member.match.error.missing.parameter", theParamName);

View File

@ -5,6 +5,7 @@ import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.StringOrListParam; import ca.uhn.fhir.rest.param.StringOrListParam;
import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.StringParam;
@ -13,7 +14,6 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.ParametersUtil; import ca.uhn.fhir.util.ParametersUtil;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
@ -21,7 +21,6 @@ import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Consent; import org.hl7.fhir.r4.model.Consent;
import org.hl7.fhir.r4.model.Coverage; import org.hl7.fhir.r4.model.Coverage;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.HumanName; import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Parameters;
@ -29,9 +28,9 @@ import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Reference;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer;
import static ca.uhn.fhir.rest.api.Constants.PARAM_CONSENT; import static ca.uhn.fhir.rest.api.Constants.PARAM_CONSENT;
import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_IDENTIFIER; import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_IDENTIFIER;
@ -59,7 +58,7 @@ import static ca.uhn.fhir.rest.api.Constants.PARAM_NEW_COVERAGE;
*/ */
public class MemberMatcherR4Helper { public class MemberMatcherR4Helper {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MemberMatcherR4Helper.class); static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MemberMatcherR4Helper.class);
private static final String OUT_COVERAGE_IDENTIFIER_CODE_SYSTEM = "http://terminology.hl7.org/CodeSystem/v2-0203"; private static final String OUT_COVERAGE_IDENTIFIER_CODE_SYSTEM = "http://terminology.hl7.org/CodeSystem/v2-0203";
private static final String OUT_COVERAGE_IDENTIFIER_CODE = "MB"; private static final String OUT_COVERAGE_IDENTIFIER_CODE = "MB";
@ -67,16 +66,14 @@ public class MemberMatcherR4Helper {
private static final String COVERAGE_TYPE = "Coverage"; private static final String COVERAGE_TYPE = "Coverage";
private static final String CONSENT_POLICY_REGULAR_TYPE = "regular"; private static final String CONSENT_POLICY_REGULAR_TYPE = "regular";
private static final String CONSENT_POLICY_SENSITIVE_TYPE = "sensitive"; private static final String CONSENT_POLICY_SENSITIVE_TYPE = "sensitive";
public static final String CONSENT_IDENTIFIER_CODE_SYSTEM = "https://smilecdr.com/fhir/ns/member-match-fixme"; public static final String CONSENT_IDENTIFIER_CODE_SYSTEM = "https://smilecdr.com/fhir/ns/member-match-source-client";
private final FhirContext myFhirContext; private final FhirContext myFhirContext;
private final IFhirResourceDao<Coverage> myCoverageDao; private final IFhirResourceDao<Coverage> myCoverageDao;
private final IFhirResourceDao<Patient> myPatientDao; private final IFhirResourceDao<Patient> myPatientDao;
private final IFhirResourceDao<Consent> myConsentDao; private final IFhirResourceDao<Consent> myConsentDao;
// by default, not provided /** A hook to modify the Consent before save */
// but if it is, extensions can be added to Consent on $member-match private final Consumer<IBaseResource> myConsentModifier;
@Nullable
private final IConsentExtensionProvider myIConsentExtensionProvider;
private boolean myRegularFilterSupported = false; private boolean myRegularFilterSupported = false;
@ -85,27 +82,27 @@ public class MemberMatcherR4Helper {
IFhirResourceDao<Coverage> theCoverageDao, IFhirResourceDao<Coverage> theCoverageDao,
IFhirResourceDao<Patient> thePatientDao, IFhirResourceDao<Patient> thePatientDao,
IFhirResourceDao<Consent> theConsentDao, IFhirResourceDao<Consent> theConsentDao,
@Nullable IConsentExtensionProvider theExtensionProvider @Nullable IMemberMatchConsentHook theConsentModifier
) { ) {
myFhirContext = theContext; myFhirContext = theContext;
myConsentDao = theConsentDao; myConsentDao = theConsentDao;
myPatientDao = thePatientDao; myPatientDao = thePatientDao;
myCoverageDao = theCoverageDao; myCoverageDao = theCoverageDao;
myIConsentExtensionProvider = theExtensionProvider; myConsentModifier = (theConsentModifier != null) ? theConsentModifier : noop -> {};
} }
/** /**
* Find Coverage matching the received member (Patient) by coverage id or by coverage identifier only * Find Coverage matching the received member (Patient) by coverage id or by coverage identifier only
*/ */
public Optional<Coverage> findMatchingCoverage(Coverage theCoverageToMatch) { public Optional<Coverage> findMatchingCoverage(Coverage theCoverageToMatch, RequestDetails theRequestDetails) {
// search by received old coverage id // search by received old coverage id
List<IBaseResource> foundCoverages = findCoverageByCoverageId(theCoverageToMatch); List<IBaseResource> foundCoverages = findCoverageByCoverageId(theCoverageToMatch, theRequestDetails);
if (foundCoverages.size() == 1 && isCoverage(foundCoverages.get(0))) { if (foundCoverages.size() == 1 && isCoverage(foundCoverages.get(0))) {
return Optional.of((Coverage) foundCoverages.get(0)); return Optional.of((Coverage) foundCoverages.get(0));
} }
// search by received old coverage identifier // search by received old coverage identifier
foundCoverages = findCoverageByCoverageIdentifier(theCoverageToMatch); foundCoverages = findCoverageByCoverageIdentifier(theCoverageToMatch, theRequestDetails);
if (foundCoverages.size() == 1 && isCoverage(foundCoverages.get(0))) { if (foundCoverages.size() == 1 && isCoverage(foundCoverages.get(0))) {
return Optional.of((Coverage) foundCoverages.get(0)); return Optional.of((Coverage) foundCoverages.get(0));
} }
@ -114,7 +111,7 @@ public class MemberMatcherR4Helper {
} }
private List<IBaseResource> findCoverageByCoverageIdentifier(Coverage theCoverageToMatch) { private List<IBaseResource> findCoverageByCoverageIdentifier(Coverage theCoverageToMatch, RequestDetails theRequestDetails) {
TokenOrListParam identifierParam = new TokenOrListParam(); TokenOrListParam identifierParam = new TokenOrListParam();
for (Identifier identifier : theCoverageToMatch.getIdentifier()) { for (Identifier identifier : theCoverageToMatch.getIdentifier()) {
identifierParam.add(identifier.getSystem(), identifier.getValue()); identifierParam.add(identifier.getSystem(), identifier.getValue());
@ -122,7 +119,7 @@ public class MemberMatcherR4Helper {
SearchParameterMap paramMap = new SearchParameterMap() SearchParameterMap paramMap = new SearchParameterMap()
.add("identifier", identifierParam); .add("identifier", identifierParam);
ca.uhn.fhir.rest.api.server.IBundleProvider retVal = myCoverageDao.search(paramMap); ca.uhn.fhir.rest.api.server.IBundleProvider retVal = myCoverageDao.search(paramMap, theRequestDetails);
return retVal.getAllResources(); return retVal.getAllResources();
} }
@ -133,21 +130,23 @@ public class MemberMatcherR4Helper {
} }
private List<IBaseResource> findCoverageByCoverageId(Coverage theCoverageToMatch) { private List<IBaseResource> findCoverageByCoverageId(Coverage theCoverageToMatch, RequestDetails theRequestDetails) {
SearchParameterMap paramMap = new SearchParameterMap() SearchParameterMap paramMap = new SearchParameterMap()
.add("_id", new StringParam(theCoverageToMatch.getId())); .add("_id", new StringParam(theCoverageToMatch.getId()));
ca.uhn.fhir.rest.api.server.IBundleProvider retVal = myCoverageDao.search(paramMap); ca.uhn.fhir.rest.api.server.IBundleProvider retVal = myCoverageDao.search(paramMap, theRequestDetails);
return retVal.getAllResources(); return retVal.getAllResources();
} }
public void updateConsentForMemberMatch(Consent theConsent, Patient thePatient, Patient theMemberPatient) { public void updateConsentForMemberMatch(Consent theConsent, Patient thePatient, Patient theMemberPatient, RequestDetails theRequestDetails) {
addClientIdAsExtensionToConsentIfAvailable(theConsent);
addIdentifierToConsent(theConsent, theMemberPatient); addIdentifierToConsent(theConsent, theMemberPatient);
updateConsentPatientAndPerformer(theConsent, thePatient); updateConsentPatientAndPerformer(theConsent, thePatient);
myConsentModifier.accept(theConsent);
// save the resource // WIPMB REVIEW QUESTION Which partition should we target?
myConsentDao.create(theConsent); // Will RequestTenantPartitionInterceptor or PatientIdPartitionInterceptor do the right thing?
// Can we use the userdata field to hint at target partition?
myConsentDao.create(theConsent, theRequestDetails);
} }
public Parameters buildSuccessReturnParameters(Patient theMemberPatient, Coverage theCoverage, Consent theConsent) { public Parameters buildSuccessReturnParameters(Patient theMemberPatient, Coverage theCoverage, Consent theConsent) {
@ -160,24 +159,25 @@ public class MemberMatcherR4Helper {
} }
private Identifier getIdentifier(Patient theMemberPatient) { private Identifier getIdentifier(Patient theMemberPatient) {
List<Identifier> memberIdentifiers = theMemberPatient.getIdentifier(); return theMemberPatient.getIdentifier()
if (memberIdentifiers != null && memberIdentifiers.size() > 0) { .stream()
for (Identifier memberIdentifier : memberIdentifiers) { .filter(this::isTypeMB)
List<Coding> typeCodings = memberIdentifier.getType().getCoding(); .findFirst()
if (memberIdentifier.getType() != null && typeCodings != null && typeCodings.size() > 0) { .orElseThrow(()->{
for (Coding typeCoding : typeCodings) {
if (typeCoding.getCode().equals("MB")) {
return memberIdentifier;
}
}
}
}
}
String i18nMessage = myFhirContext.getLocalizer().getMessage( String i18nMessage = myFhirContext.getLocalizer().getMessage(
"operation.member.match.error.beneficiary.without.identifier"); "operation.member.match.error.beneficiary.without.identifier");
throw new UnprocessableEntityException(Msg.code(2219) + i18nMessage); return new UnprocessableEntityException(Msg.code(2219) + i18nMessage);
});
} }
private boolean isTypeMB(Identifier theMemberIdentifier) {
return theMemberIdentifier.getType() != null &&
theMemberIdentifier.getType().getCoding()
.stream()
.anyMatch(typeCoding->typeCoding.getCode().equals("MB"));
}
public void addMemberIdentifierToMemberPatient(Patient theMemberPatient, Identifier theNewIdentifier) { public void addMemberIdentifierToMemberPatient(Patient theMemberPatient, Identifier theNewIdentifier) {
Coding coding = new Coding() Coding coding = new Coding()
.setSystem(OUT_COVERAGE_IDENTIFIER_CODE_SYSTEM) .setSystem(OUT_COVERAGE_IDENTIFIER_CODE_SYSTEM)
@ -198,31 +198,7 @@ public class MemberMatcherR4Helper {
theMemberPatient.addIdentifier(newIdentifier); theMemberPatient.addIdentifier(newIdentifier);
} }
/** public Optional<Patient> getBeneficiaryPatient(Coverage theCoverage, RequestDetails theRequestDetails) {
* If there is a client id
*
* @param theConsent - the consent to modify
*/
private void addClientIdAsExtensionToConsentIfAvailable(Consent theConsent) {
if (myIConsentExtensionProvider != null) {
Collection<IBaseExtension> extensions = myIConsentExtensionProvider.getConsentExtension(theConsent);
for (IBaseExtension ext : extensions) {
if (ext instanceof Extension) {
theConsent.addExtension((Extension) ext);
} else {
Extension extR4 = new Extension();
extR4.setUrl(ext.getUrl());
extR4.setValue(ext.getValue());
theConsent.addExtension(extR4);
}
}
ourLog.trace("{} extension(s) added to Consent", extensions.size());
}
}
public Optional<Patient> getBeneficiaryPatient(Coverage theCoverage) {
if (theCoverage.getBeneficiaryTarget() == null && theCoverage.getBeneficiary() == null) { if (theCoverage.getBeneficiaryTarget() == null && theCoverage.getBeneficiary() == null) {
return Optional.empty(); return Optional.empty();
} }
@ -245,15 +221,15 @@ public class MemberMatcherR4Helper {
return Optional.empty(); return Optional.empty();
} }
Patient beneficiary = myPatientDao.read(new IdDt(beneficiaryRef.getReference())); Patient beneficiary = myPatientDao.read(new IdDt(beneficiaryRef.getReference()), theRequestDetails);
return Optional.ofNullable(beneficiary); return Optional.ofNullable(beneficiary);
} }
/** /**
* Matching by member patient demographics - family name and birthdate only * Matching by member patient demographics - family name and birthdate only
*/ */
public boolean validPatientMember(Patient thePatientFromContract, Patient thePatientToMatch) { public boolean validPatientMember(Patient thePatientFromContract, Patient thePatientToMatch, RequestDetails theRequestDetails) {
if (thePatientFromContract == null || thePatientFromContract.getIdElement() == null) { if (thePatientFromContract == null || thePatientFromContract.getIdElement() == null || thePatientToMatch == null) {
return false; return false;
} }
StringOrListParam familyName = new StringOrListParam(); StringOrListParam familyName = new StringOrListParam();
@ -263,7 +239,7 @@ public class MemberMatcherR4Helper {
SearchParameterMap map = new SearchParameterMap() SearchParameterMap map = new SearchParameterMap()
.add("family", familyName) .add("family", familyName)
.add("birthdate", new DateParam(thePatientToMatch.getBirthDateElement().getValueAsString())); .add("birthdate", new DateParam(thePatientToMatch.getBirthDateElement().getValueAsString()));
ca.uhn.fhir.rest.api.server.IBundleProvider bundle = myPatientDao.search(map); ca.uhn.fhir.rest.api.server.IBundleProvider bundle = myPatientDao.search(map, theRequestDetails);
for (IBaseResource patientResource : bundle.getAllResources()) { for (IBaseResource patientResource : bundle.getAllResources()) {
IIdType patientId = patientResource.getIdElement().toUnqualifiedVersionless(); IIdType patientId = patientResource.getIdElement().toUnqualifiedVersionless();
if (patientId.getValue().equals(thePatientFromContract.getIdElement().toUnqualifiedVersionless().getValue())) { if (patientId.getValue().equals(thePatientFromContract.getIdElement().toUnqualifiedVersionless().getValue())) {
@ -286,18 +262,16 @@ public class MemberMatcherR4Helper {
} }
/** /**
* This is filtering the Consent policy data. The rule is specified in * The consent policy rules are
* https://build.fhir.org/ig/HL7/davinci-ehrx/StructureDefinition-hrex-consent.html#notes * <a href="https://build.fhir.org/ig/HL7/davinci-ehrx/StructureDefinition-hrex-consent.html#notes">
* described here.</a>
*/ */
private boolean validConsentPolicy(String thePolicyUri) { private boolean validConsentPolicy(String thePolicyUri) {
String policyTypes = StringUtils.substringAfterLast(thePolicyUri, "#"); String policyTypes = StringUtils.substringAfterLast(thePolicyUri, "#");
if (policyTypes.equals(CONSENT_POLICY_SENSITIVE_TYPE)) { if (policyTypes.equals(CONSENT_POLICY_SENSITIVE_TYPE)) {
return true; return true;
} }
if (policyTypes.equals(CONSENT_POLICY_REGULAR_TYPE) && myRegularFilterSupported) { return policyTypes.equals(CONSENT_POLICY_REGULAR_TYPE) && myRegularFilterSupported;
return true;
}
return false;
} }
private void addIdentifierToConsent(Consent theConsent, Patient thePatient) { private void addIdentifierToConsent(Consent theConsent, Patient thePatient) {

View File

@ -1,64 +1,60 @@
package ca.uhn.fhir.jpa.provider; package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.provider.r4.IConsentExtensionProvider;
import ca.uhn.fhir.jpa.provider.r4.MemberMatcherR4Helper;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
import ca.uhn.fhir.rest.server.SimpleBundleProvider; import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import ca.uhn.test.util.LogbackCaptureTestExtension;
import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Consent; import org.hl7.fhir.r4.model.Consent;
import org.hl7.fhir.r4.model.Coverage; import org.hl7.fhir.r4.model.Coverage;
import org.hl7.fhir.r4.model.DateType; import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.HumanName; import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.Answers; import org.mockito.Answers;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Captor; import org.mockito.Captor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Spy; import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.LoggerFactory;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import static ca.uhn.fhir.jpa.provider.r4.MemberMatcherR4Helper.CONSENT_IDENTIFIER_CODE_SYSTEM;
import static ca.uhn.fhir.rest.api.Constants.PARAM_CONSENT; import static ca.uhn.fhir.rest.api.Constants.PARAM_CONSENT;
import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_IDENTIFIER; import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_IDENTIFIER;
import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_PATIENT; import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_PATIENT;
import static ca.uhn.fhir.rest.api.Constants.PARAM_NEW_COVERAGE; import static ca.uhn.fhir.rest.api.Constants.PARAM_NEW_COVERAGE;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isA; import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.never; import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -66,11 +62,8 @@ import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
public class MemberMatcherR4HelperTest { public class MemberMatcherR4HelperTest {
private static final Logger ourLog = (Logger) LoggerFactory.getLogger(MemberMatcherR4Helper.class); @RegisterExtension
LogbackCaptureTestExtension myLogCapture = new LogbackCaptureTestExtension((Logger) MemberMatcherR4Helper.ourLog, Level.TRACE);
@Mock
private ListAppender<ILoggingEvent> myAppender;
@Spy @Spy
private final FhirContext myFhirContext = FhirContext.forR4(); private final FhirContext myFhirContext = FhirContext.forR4();
@Mock @Mock
@ -81,6 +74,7 @@ public class MemberMatcherR4HelperTest {
private IFhirResourceDao<Consent> myConsentDao; private IFhirResourceDao<Consent> myConsentDao;
private MemberMatcherR4Helper myHelper; private MemberMatcherR4Helper myHelper;
RequestDetails myRequestDetails = new SystemRequestDetails();
@BeforeEach @BeforeEach
public void before() { public void before() {
@ -91,13 +85,6 @@ public class MemberMatcherR4HelperTest {
myConsentDao, myConsentDao,
null // extension provider null // extension provider
); );
ourLog.addAppender(myAppender);
}
@AfterEach
public void after() {
ourLog.detachAppender(myAppender);
} }
@Mock private Coverage myCoverageToMatch; @Mock private Coverage myCoverageToMatch;
@ -114,13 +101,13 @@ public class MemberMatcherR4HelperTest {
@Test @Test
void findMatchingCoverageMatchByIdReturnsMatched() { void findMatchingCoverageMatchByIdReturnsMatched() {
when(myCoverageToMatch.getId()).thenReturn("cvg-to-match-id"); when(myCoverageToMatch.getId()).thenReturn("cvg-to-match-id");
when(myCoverageDao.search(isA(SearchParameterMap.class))).thenReturn(myBundleProvider); when(myCoverageDao.search(isA(SearchParameterMap.class), same(myRequestDetails))).thenReturn(myBundleProvider);
when(myBundleProvider.getAllResources()).thenReturn(Collections.singletonList(myMatchedCoverage)); when(myBundleProvider.getAllResources()).thenReturn(Collections.singletonList(myMatchedCoverage));
Optional<Coverage> result = myHelper.findMatchingCoverage(myCoverageToMatch); Optional<Coverage> result = myHelper.findMatchingCoverage(myCoverageToMatch, myRequestDetails);
assertEquals(Optional.of(myMatchedCoverage), result); assertEquals(Optional.of(myMatchedCoverage), result);
verify(myCoverageDao).search(mySearchParameterMapCaptor.capture()); verify(myCoverageDao).search(mySearchParameterMapCaptor.capture(), same(myRequestDetails));
SearchParameterMap spMap = mySearchParameterMapCaptor.getValue(); SearchParameterMap spMap = mySearchParameterMapCaptor.getValue();
assertTrue(spMap.containsKey("_id")); assertTrue(spMap.containsKey("_id"));
List<List<IQueryParameterType>> listListParams = spMap.get("_id"); List<List<IQueryParameterType>> listListParams = spMap.get("_id");
@ -135,14 +122,14 @@ public class MemberMatcherR4HelperTest {
void findMatchingCoverageMatchByIdentifierReturnsMatched() { void findMatchingCoverageMatchByIdentifierReturnsMatched() {
when(myCoverageToMatch.getId()).thenReturn("non-matching-id"); when(myCoverageToMatch.getId()).thenReturn("non-matching-id");
when(myCoverageToMatch.getIdentifier()).thenReturn(Collections.singletonList(myMatchingIdentifier)); when(myCoverageToMatch.getIdentifier()).thenReturn(Collections.singletonList(myMatchingIdentifier));
when(myCoverageDao.search(isA(SearchParameterMap.class))).thenReturn(myBundleProvider); when(myCoverageDao.search(isA(SearchParameterMap.class), same(myRequestDetails))).thenReturn(myBundleProvider);
when(myBundleProvider.getAllResources()).thenReturn( when(myBundleProvider.getAllResources()).thenReturn(
Collections.emptyList(), Collections.singletonList(myMatchedCoverage)); Collections.emptyList(), Collections.singletonList(myMatchedCoverage));
Optional<Coverage> result = myHelper.findMatchingCoverage(myCoverageToMatch); Optional<Coverage> result = myHelper.findMatchingCoverage(myCoverageToMatch, myRequestDetails);
assertEquals(Optional.of(myMatchedCoverage), result); assertEquals(Optional.of(myMatchedCoverage), result);
verify(myCoverageDao, times(2)).search(mySearchParameterMapCaptor.capture()); verify(myCoverageDao, times(2)).search(mySearchParameterMapCaptor.capture(), same(myRequestDetails));
List<SearchParameterMap> spMap = mySearchParameterMapCaptor.getAllValues(); List<SearchParameterMap> spMap = mySearchParameterMapCaptor.getAllValues();
assertTrue(spMap.get(0).containsKey("_id")); assertTrue(spMap.get(0).containsKey("_id"));
assertTrue(spMap.get(1).containsKey("identifier")); assertTrue(spMap.get(1).containsKey("identifier"));
@ -159,10 +146,10 @@ public class MemberMatcherR4HelperTest {
void findMatchingCoverageNoMatchReturnsEmpty() { void findMatchingCoverageNoMatchReturnsEmpty() {
when(myCoverageToMatch.getId()).thenReturn("non-matching-id"); when(myCoverageToMatch.getId()).thenReturn("non-matching-id");
when(myCoverageToMatch.getIdentifier()).thenReturn(Collections.singletonList(myMatchingIdentifier)); when(myCoverageToMatch.getIdentifier()).thenReturn(Collections.singletonList(myMatchingIdentifier));
when(myCoverageDao.search(any(SearchParameterMap.class))).thenReturn(myBundleProvider); when(myCoverageDao.search(any(SearchParameterMap.class), same(myRequestDetails))).thenReturn(myBundleProvider);
when(myBundleProvider.getAllResources()).thenReturn(Collections.emptyList(), Collections.emptyList()); when(myBundleProvider.getAllResources()).thenReturn(Collections.emptyList(), Collections.emptyList());
Optional<Coverage> result = myHelper.findMatchingCoverage(myCoverageToMatch); Optional<Coverage> result = myHelper.findMatchingCoverage(myCoverageToMatch, myRequestDetails);
assertFalse(result.isPresent()); assertFalse(result.isPresent());
} }
@ -245,7 +232,7 @@ public class MemberMatcherR4HelperTest {
when(coverage.getBeneficiaryTarget()).thenReturn(null); when(coverage.getBeneficiaryTarget()).thenReturn(null);
when(coverage.getBeneficiary()).thenReturn(null); when(coverage.getBeneficiary()).thenReturn(null);
Optional<Patient> result = myHelper.getBeneficiaryPatient(coverage); Optional<Patient> result = myHelper.getBeneficiaryPatient(coverage, myRequestDetails);
assertFalse(result.isPresent()); assertFalse(result.isPresent());
} }
@ -256,7 +243,7 @@ public class MemberMatcherR4HelperTest {
when(coverage.getBeneficiary()).thenReturn(null); when(coverage.getBeneficiary()).thenReturn(null);
when(coverage.getBeneficiaryTarget()).thenReturn(new Patient()); when(coverage.getBeneficiaryTarget()).thenReturn(new Patient());
Optional<Patient> result = myHelper.getBeneficiaryPatient(coverage); Optional<Patient> result = myHelper.getBeneficiaryPatient(coverage, myRequestDetails);
assertFalse(result.isPresent()); assertFalse(result.isPresent());
} }
@ -267,7 +254,7 @@ public class MemberMatcherR4HelperTest {
Patient patient = new Patient().setIdentifier(Collections.singletonList(new Identifier())); Patient patient = new Patient().setIdentifier(Collections.singletonList(new Identifier()));
when(coverage.getBeneficiaryTarget()).thenReturn(patient); when(coverage.getBeneficiaryTarget()).thenReturn(patient);
Optional<Patient> result = myHelper.getBeneficiaryPatient(coverage); Optional<Patient> result = myHelper.getBeneficiaryPatient(coverage, myRequestDetails);
assertTrue(result.isPresent()); assertTrue(result.isPresent());
assertEquals(patient, result.get()); assertEquals(patient, result.get());
@ -280,7 +267,7 @@ public class MemberMatcherR4HelperTest {
when(coverage.getBeneficiaryTarget()).thenReturn(null); when(coverage.getBeneficiaryTarget()).thenReturn(null);
when(coverage.getBeneficiary().getResource()).thenReturn(patient); when(coverage.getBeneficiary().getResource()).thenReturn(patient);
Optional<Patient> result = myHelper.getBeneficiaryPatient(coverage); Optional<Patient> result = myHelper.getBeneficiaryPatient(coverage, myRequestDetails);
assertTrue(result.isPresent()); assertTrue(result.isPresent());
assertEquals(patient, result.get()); assertEquals(patient, result.get());
@ -292,7 +279,7 @@ public class MemberMatcherR4HelperTest {
when(coverage.getBeneficiaryTarget()).thenReturn(null); when(coverage.getBeneficiaryTarget()).thenReturn(null);
when(coverage.getBeneficiary()).thenReturn(new Reference()); when(coverage.getBeneficiary()).thenReturn(new Reference());
Optional<Patient> result = myHelper.getBeneficiaryPatient(coverage); Optional<Patient> result = myHelper.getBeneficiaryPatient(coverage, myRequestDetails);
assertFalse(result.isPresent()); assertFalse(result.isPresent());
} }
@ -304,11 +291,10 @@ public class MemberMatcherR4HelperTest {
when(coverage.getBeneficiary().getResource()).thenReturn(null); when(coverage.getBeneficiary().getResource()).thenReturn(null);
when(coverage.getBeneficiary().getReference()).thenReturn("patient-id"); when(coverage.getBeneficiary().getReference()).thenReturn("patient-id");
myHelper.getBeneficiaryPatient(coverage); myHelper.getBeneficiaryPatient(coverage, myRequestDetails);
verify(myPatientDao).read(new IdDt("patient-id")); verify(myPatientDao).read(new IdDt("patient-id"), myRequestDetails);
} }
} }
/** /**
@ -317,19 +303,17 @@ public class MemberMatcherR4HelperTest {
@Nested @Nested
public class TestValidPatientMember { public class TestValidPatientMember {
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private final Patient patient = new Patient();
private Coverage coverage;
private Patient patient;
@Test @Test
void noPatientFoundFromContractReturnsFalse() { void noPatientFoundFromContractReturnsFalse() {
boolean result = myHelper.validPatientMember(null, patient); boolean result = myHelper.validPatientMember(null, patient, myRequestDetails);
assertFalse(result); assertFalse(result);
} }
@Test @Test
void noPatientFoundFromPatientMemberReturnsFalse() { void noPatientFoundFromPatientMemberReturnsFalse() {
boolean result = myHelper.validPatientMember(patient, null); boolean result = myHelper.validPatientMember(patient, null, myRequestDetails);
assertFalse(result); assertFalse(result);
} }
@ -337,11 +321,11 @@ public class MemberMatcherR4HelperTest {
void noMatchingFamilyNameReturnsFalse() { void noMatchingFamilyNameReturnsFalse() {
Patient patientFromMemberMatch = getPatientWithNoIDParm("Person", "2020-01-01"); Patient patientFromMemberMatch = getPatientWithNoIDParm("Person", "2020-01-01");
Patient patientFromContractFound = getPatientWithIDParm("A123", "Smith", "2020-01-01"); Patient patientFromContractFound = getPatientWithIDParm("A123", "Smith", "2020-01-01");
when(myPatientDao.search(any(SearchParameterMap.class))).thenAnswer(t -> { when(myPatientDao.search(any(SearchParameterMap.class), same(myRequestDetails))).thenAnswer(t -> {
IBundleProvider provider = new SimpleBundleProvider(Collections.singletonList(new Patient().setId("B123"))); IBundleProvider provider = new SimpleBundleProvider(Collections.singletonList(new Patient().setId("B123")));
return provider; return provider;
}); });
boolean result = myHelper.validPatientMember(patientFromContractFound, patientFromMemberMatch); boolean result = myHelper.validPatientMember(patientFromContractFound, patientFromMemberMatch, myRequestDetails);
assertFalse(result); assertFalse(result);
} }
@ -350,11 +334,11 @@ public class MemberMatcherR4HelperTest {
void noMatchingBirthdayReturnsFalse() { void noMatchingBirthdayReturnsFalse() {
Patient patientFromMemberMatch = getPatientWithNoIDParm("Person", "1990-01-01"); Patient patientFromMemberMatch = getPatientWithNoIDParm("Person", "1990-01-01");
Patient patientFromContractFound = getPatientWithIDParm("A123", "Person", "2020-01-01"); Patient patientFromContractFound = getPatientWithIDParm("A123", "Person", "2020-01-01");
when(myPatientDao.search(any(SearchParameterMap.class))).thenAnswer(t -> { when(myPatientDao.search(any(SearchParameterMap.class), same(myRequestDetails))).thenAnswer(t -> {
IBundleProvider provider = new SimpleBundleProvider(Collections.singletonList(new Patient().setId("B123"))); IBundleProvider provider = new SimpleBundleProvider(Collections.singletonList(new Patient().setId("B123")));
return provider; return provider;
}); });
boolean result = myHelper.validPatientMember(patientFromContractFound, patientFromMemberMatch); boolean result = myHelper.validPatientMember(patientFromContractFound, patientFromMemberMatch, myRequestDetails);
assertFalse(result); assertFalse(result);
} }
@ -362,11 +346,11 @@ public class MemberMatcherR4HelperTest {
void noMatchingFieldsReturnsFalse() { void noMatchingFieldsReturnsFalse() {
Patient patientFromMemberMatch = getPatientWithNoIDParm("Person", "1990-01-01"); Patient patientFromMemberMatch = getPatientWithNoIDParm("Person", "1990-01-01");
Patient patientFromContractFound = getPatientWithIDParm("A123", "Smith", "2020-01-01"); Patient patientFromContractFound = getPatientWithIDParm("A123", "Smith", "2020-01-01");
when(myPatientDao.search(any(SearchParameterMap.class))).thenAnswer(t -> { when(myPatientDao.search(any(SearchParameterMap.class), same(myRequestDetails))).thenAnswer(t -> {
IBundleProvider provider = new SimpleBundleProvider(Collections.singletonList(new Patient().setId("B123"))); IBundleProvider provider = new SimpleBundleProvider(Collections.singletonList(new Patient().setId("B123")));
return provider; return provider;
}); });
boolean result = myHelper.validPatientMember(patientFromContractFound, patientFromMemberMatch); boolean result = myHelper.validPatientMember(patientFromContractFound, patientFromMemberMatch, myRequestDetails);
assertFalse(result); assertFalse(result);
} }
@ -374,11 +358,11 @@ public class MemberMatcherR4HelperTest {
void patientMatchingReturnTrue() { void patientMatchingReturnTrue() {
Patient patientFromMemberMatch = getPatientWithNoIDParm("Person", "2020-01-01"); Patient patientFromMemberMatch = getPatientWithNoIDParm("Person", "2020-01-01");
Patient patientFromContractFound = getPatientWithIDParm("A123", "Person", "2020-01-01"); Patient patientFromContractFound = getPatientWithIDParm("A123", "Person", "2020-01-01");
when(myPatientDao.search(any(SearchParameterMap.class))).thenAnswer(t -> { when(myPatientDao.search(any(SearchParameterMap.class), same(myRequestDetails))).thenAnswer(t -> {
IBundleProvider provider = new SimpleBundleProvider(Collections.singletonList(patientFromContractFound)); IBundleProvider provider = new SimpleBundleProvider(Collections.singletonList(patientFromContractFound));
return provider; return provider;
}); });
boolean result = myHelper.validPatientMember(patientFromContractFound, patientFromMemberMatch); boolean result = myHelper.validPatientMember(patientFromContractFound, patientFromMemberMatch, myRequestDetails);
assertTrue(result); assertTrue(result);
} }
@ -404,9 +388,6 @@ public class MemberMatcherR4HelperTest {
@Nested @Nested
public class TestValidvalidConsentDataAccess { public class TestValidvalidConsentDataAccess {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Coverage coverage;
private Patient patient;
private Consent consent; private Consent consent;
@Test @Test
@ -521,45 +502,10 @@ public class MemberMatcherR4HelperTest {
return patient; return patient;
} }
private void verifyConsentUpdatedAfterMemberMatch(
Consent theConsent,
Patient thePatient,
Patient theMemberPatient,
List<Extension> theSavedExtensions
) {
// check consent identifier
assertEquals(1, theConsent.getIdentifier().size());
assertEquals(CONSENT_IDENTIFIER_CODE_SYSTEM, theConsent.getIdentifier().get(0).getSystem());
assertNotNull(theConsent.getIdentifier().get(0).getValue());
assertEquals(theConsent.getIdentifier().get(0).getValue(), theMemberPatient.getIdentifier().get(0).getValue());
// check consent patient info
String patientRef = thePatient.getIdElement().toUnqualifiedVersionless().getValue();
assertEquals(patientRef, theConsent.getPatient().getReference());
assertEquals(patientRef, theConsent.getPerformer().get(0).getReference());
if (!theSavedExtensions.isEmpty()) {
// check consent extensions
assertNotNull(theConsent.getExtension());
assertEquals(theSavedExtensions.size(), theConsent.getExtension().size());
for (Extension ext : theSavedExtensions) {
boolean found = false;
for (Extension consentExt : theConsent.getExtension()) {
if (consentExt.getUrl().equals(ext.getUrl())
&& consentExt.getValue().equals(ext.getValue())) {
found = true;
break;
}
}
assertTrue(found,
"Extension " + ext.getUrl() + "|" + ext.getValue().toString() + " not found"
);
}
}
}
@Nested @Nested
public class MemberMatchWithoutConsentProvider { public class MemberMatchWithoutConsentProvider {
@Captor
private ArgumentCaptor<Consent> myDaoCaptor;
@Test @Test
public void updateConsentForMemberMatch_noProvider_addsIdentifierUpdatePatientButNotExtensionAndSaves() { public void updateConsentForMemberMatch_noProvider_addsIdentifierUpdatePatientButNotExtensionAndSaves() {
@ -569,19 +515,26 @@ public class MemberMatcherR4HelperTest {
Patient patient = createPatientForMemberMatchUpdate(false); Patient patient = createPatientForMemberMatchUpdate(false);
Patient memberPatient = createPatientForMemberMatchUpdate(true); Patient memberPatient = createPatientForMemberMatchUpdate(true);
ourLog.setLevel(Level.TRACE);
// test // test
myHelper.updateConsentForMemberMatch(consent, patient, memberPatient); myHelper.updateConsentForMemberMatch(consent, patient, memberPatient, myRequestDetails);
// verify // verify
verify(myAppender, never())
.doAppend(any(ILoggingEvent.class));
ArgumentCaptor<Consent> consentCaptor = ArgumentCaptor.forClass(Consent.class); verify(myConsentDao).create(myDaoCaptor.capture(), same(myRequestDetails));
verify(myConsentDao).create(consentCaptor.capture()); Consent saved = myDaoCaptor.getValue();
Consent saved = consentCaptor.getValue(); // check consent identifier
verifyConsentUpdatedAfterMemberMatch(saved, patient, memberPatient, Collections.emptyList()); assertEquals(1, saved.getIdentifier().size());
assertEquals(MemberMatcherR4Helper.CONSENT_IDENTIFIER_CODE_SYSTEM, saved.getIdentifier().get(0).getSystem());
assertNotNull(saved.getIdentifier().get(0).getValue());
assertEquals(saved.getIdentifier().get(0).getValue(), memberPatient.getIdentifier().get(0).getValue());
// check consent patient info
String patientRef = patient.getIdElement().toUnqualifiedVersionless().getValue();
assertEquals(patientRef, saved.getPatient().getReference());
assertEquals(patientRef, saved.getPerformer().get(0).getReference());
assertThat(myLogCapture.getLogEvents(), empty());
} }
} }
@ -589,6 +542,8 @@ public class MemberMatcherR4HelperTest {
public class MemberMatchWithConsentProvider { public class MemberMatchWithConsentProvider {
@Mock @Mock
private IConsentExtensionProvider myExtensionProvider; private IConsentExtensionProvider myExtensionProvider;
@Captor
private ArgumentCaptor<Consent> myHookCaptor;
@BeforeEach @BeforeEach
public void before() { public void before() {
@ -607,31 +562,17 @@ public class MemberMatcherR4HelperTest {
Consent consent = getConsent(); Consent consent = getConsent();
consent.addPolicy(constructConsentPolicyComponent("#sensitive")); consent.addPolicy(constructConsentPolicyComponent("#sensitive"));
consent.setId("Consent/RED"); consent.setId("Consent/RED");
Extension ext = new Extension();
ext.setUrl("http://example.com");
ext.setValue(new StringType("value"));
Patient patient = createPatientForMemberMatchUpdate(false); Patient patient = createPatientForMemberMatchUpdate(false);
Patient memberPatient = createPatientForMemberMatchUpdate(true); Patient memberPatient = createPatientForMemberMatchUpdate(true);
ourLog.setLevel(Level.TRACE);
// when
when(myExtensionProvider.getConsentExtension(any(IBaseResource.class)))
.thenReturn(Collections.singleton(ext));
// test // test
myHelper.updateConsentForMemberMatch(consent, patient, memberPatient); myHelper.updateConsentForMemberMatch(consent, patient, memberPatient, myRequestDetails);
// verify // verify
ArgumentCaptor<Consent> consentCaptor = ArgumentCaptor.forClass(Consent.class); verify(myExtensionProvider).accept(myHookCaptor.capture());
verify(myConsentDao).create(consentCaptor.capture());
Consent saved = consentCaptor.getValue();
verifyConsentUpdatedAfterMemberMatch(saved, patient, memberPatient, Collections.emptyList());
ArgumentCaptor<ILoggingEvent> eventCaptor = ArgumentCaptor.forClass(ILoggingEvent.class); assertSame(consent, myHookCaptor.getValue());
verify(myAppender).doAppend(eventCaptor.capture()); assertThat(myLogCapture.getLogEvents(), empty());
ILoggingEvent event = eventCaptor.getValue();
assertEquals("1 extension(s) added to Consent", event.getFormattedMessage());
} }
} }
} }

View File

@ -113,13 +113,13 @@ public class LogbackCaptureTestExtension implements BeforeEachCallback, AfterEac
* Guts of beforeEach exposed for manual lifecycle. * Guts of beforeEach exposed for manual lifecycle.
*/ */
public void setUp() { public void setUp() {
myListAppender = new ListAppender<>();
myListAppender.start();
myLogger.addAppender(myListAppender);
if (myLevel != null) { if (myLevel != null) {
mySavedLevel = myLogger.getLevel(); mySavedLevel = myLogger.getLevel();
myLogger.setLevel(myLevel); myLogger.setLevel(myLevel);
} }
myListAppender = new ListAppender<>();
myListAppender.start();
myLogger.addAppender(myListAppender);
} }
@Override @Override