From 223f673bc6d54240d7ebab3d673b661376f45063 Mon Sep 17 00:00:00 2001 From: Tim Coffman <234244+timcoffman@users.noreply.github.com> Date: Fri, 19 Apr 2024 16:02:26 -0500 Subject: [PATCH] support authorized patient ids that include base url --- ...ntalDataStoreAuthorizationInterceptor.java | 25 ++- src/main/resources/application.yaml | 2 +- .../ohsu/cmp/ecp/sds/LocalPartitionTest.java | 18 ++ .../PatientAppClientForeignPartitionTest.java | 162 ++++++++++++++++++ .../cmp/ecp/sds/PatientAppClientTest.java | 99 ++++++++--- .../cmp/ecp/sds/PatientPartitionsTest.java | 4 +- .../sds/SupplementalDataStoreMatchers.java | 65 +++++++ src/test/resources/application.yaml | 2 +- 8 files changed, 344 insertions(+), 33 deletions(-) create mode 100644 src/test/java/edu/ohsu/cmp/ecp/sds/PatientAppClientForeignPartitionTest.java create mode 100644 src/test/java/edu/ohsu/cmp/ecp/sds/SupplementalDataStoreMatchers.java diff --git a/src/main/java/edu/ohsu/cmp/ecp/sds/SupplementalDataStoreAuthorizationInterceptor.java b/src/main/java/edu/ohsu/cmp/ecp/sds/SupplementalDataStoreAuthorizationInterceptor.java index c152be9..2e33726 100644 --- a/src/main/java/edu/ohsu/cmp/ecp/sds/SupplementalDataStoreAuthorizationInterceptor.java +++ b/src/main/java/edu/ohsu/cmp/ecp/sds/SupplementalDataStoreAuthorizationInterceptor.java @@ -60,7 +60,7 @@ public List buildRuleList(RequestDetails theRequestDetails) { if ( permissions.readAndWriteSpecificPatient().isPresent() ) ruleBuilder = buildRuleListForPermissions( ruleBuilder, permissions.readAndWriteSpecificPatient().get() ) ; - return ruleBuilder.denyAll("everything else").build(); + return ruleBuilder.denyAll("no access rules grant permission").build(); } private IAuthRuleBuilder buildRuleListForPermissions( IAuthRuleBuilder ruleBuilder, Permissions.ReadAllPatients readAllPatients ) { @@ -106,7 +106,12 @@ private IAuthRuleBuilder buildRuleListForPermissions( IAuthRuleBuilder ruleBuild .allResources() .inCompartment("Patient", localPatientId) .andThen() - .allow("write local patient " + localPatientId) + .deny( "write local patient " + localPatientId ) + .write() + .resourcesOfType( "Patient" ) + .inCompartment("Patient", localPatientId) + .andThen() + .allow("write local patient " + localPatientId + " related resources") .write() .allResources() .inCompartment("Patient", localPatientId) @@ -137,6 +142,22 @@ private IAuthRuleBuilder buildRuleListForPermissions( IAuthRuleBuilder ruleBuild .andThen() ; + if ( nonLocalPatientId.hasBaseUrl() ) { + IIdType nonLocalPatientIdWithoutBaseUrl = nonLocalPatientId.toUnqualified(); + ruleBuilder = ruleBuilder + .allow("read non-local patient " + nonLocalPatientIdWithoutBaseUrl ) + .read() + .allResources() + .inCompartment("Patient", nonLocalPatientIdWithoutBaseUrl) + .andThen() + .allow("write non-local patient " + nonLocalPatientIdWithoutBaseUrl) + .write() + .allResources() + .inCompartment("Patient", nonLocalPatientIdWithoutBaseUrl) + .andThen() + ; + } + /* permit access to all sds-local linkages that link to specific patient */ ruleBuilder = ruleBuilder .allow("read linkages for non-local patient " + nonLocalPatientId) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 8e3db0e..ffa0852 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -102,7 +102,7 @@ hapi: # allowed_bundle_types: COLLECTION,DOCUMENT,MESSAGE,TRANSACTION,TRANSACTIONRESPONSE,BATCH,BATCHRESPONSE,HISTORY,SEARCHSET # allow_cascading_deletes: true # allow_contains_searches: true - # allow_external_references: true + allow_external_references: true # allow_multiple_delete: true # allow_override_default_search_params: true # auto_create_placeholder_reference_targets: false diff --git a/src/test/java/edu/ohsu/cmp/ecp/sds/LocalPartitionTest.java b/src/test/java/edu/ohsu/cmp/ecp/sds/LocalPartitionTest.java index 10c50fc..edda344 100644 --- a/src/test/java/edu/ohsu/cmp/ecp/sds/LocalPartitionTest.java +++ b/src/test/java/edu/ohsu/cmp/ecp/sds/LocalPartitionTest.java @@ -1,6 +1,7 @@ package edu.ohsu.cmp.ecp.sds; import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.Condition; import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Questionnaire; import org.hl7.fhir.r4.model.QuestionnaireResponse; @@ -32,4 +33,21 @@ void canStoreAndRetrieveResourceInLocalPartition() { Assertions.assertNotNull( readQuestResp ); } + + @Test + void canStoreAndRetrieveConditionResourceInLocalPartition() { + IGenericClient client = client() ; + + Patient pat = new Patient(); + IIdType patId = client.create().resource(pat).execute().getId(); + + Condition condition = new Condition() ; + condition.setSubject( new Reference(patId) ); + IIdType conditionId = client.create().resource(condition).execute().getId(); + + Condition readCondition = client.read().resource(Condition.class).withId(conditionId).execute(); + + Assertions.assertNotNull( readCondition ); + + } } diff --git a/src/test/java/edu/ohsu/cmp/ecp/sds/PatientAppClientForeignPartitionTest.java b/src/test/java/edu/ohsu/cmp/ecp/sds/PatientAppClientForeignPartitionTest.java new file mode 100644 index 0000000..26b8e64 --- /dev/null +++ b/src/test/java/edu/ohsu/cmp/ecp/sds/PatientAppClientForeignPartitionTest.java @@ -0,0 +1,162 @@ +package edu.ohsu.cmp.ecp.sds; + +import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static edu.ohsu.cmp.ecp.sds.SupplementalDataStoreMatchers.identifiesResource; +import static edu.ohsu.cmp.ecp.sds.SupplementalDataStoreMatchers.identifiesSameResourceAs; + +import java.util.List; + +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Condition; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Linkage; +import org.hl7.fhir.r4.model.QuestionnaireResponse; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.Type; +import org.hl7.fhir.r4.model.UriType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ActiveProfiles; + +import ca.uhn.fhir.jpa.starter.AppTestMockPermissionRegistry; +import ca.uhn.fhir.jpa.starter.AppTestMockPrincipalRegistry; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException; +import junit.framework.AssertionFailedError; + +@ActiveProfiles( "auth-aware-test") +public class PatientAppClientForeignPartitionTest extends BaseSuppplementalDataStoreTest { + + /* + * Use Case: store NON-LOCAL Patient and other resources in the patient compartment + */ + + @Autowired + AppTestMockPrincipalRegistry mockPrincipalRegistry ; + + @Autowired + AppTestMockPermissionRegistry mockPermissionRegistry ; + + private static final String FOREIGN_PARTITION_NAME = "http://my.ehr.org/fhir/R4/" ; + + private IIdType authorizedPatientId; + private IGenericClient patientAppClient ; + + @BeforeEach + public void setupAuthorizedPatient() { + authorizedPatientId = new IdType( FOREIGN_PARTITION_NAME, "Patient", createTestSpecificId(), null ); + String token = mockPrincipalRegistry.register().principal( "MyPatient", authorizedPatientId.toString() ).token() ; + + patientAppClient = authenticatingClientTargetingPartition( token, FOREIGN_PARTITION_NAME ) ; + } + + @Test + void canStoreAndReadPatientThatIsAuthorizedPatient() { + Patient patient = new Patient() ; + patient.setId( new IdType( "Patient", authorizedPatientId.getIdPart() ) ) ; + IIdType patientId = patientAppClient.update().resource(patient).execute().getId(); + + assertThat( patientId, identifiesResource( patient ) ); + + Patient readPatient = patientAppClient.read().resource(Patient.class).withId( patientId ).execute(); + + assertThat( readPatient, notNullValue() ); + assertThat( readPatient.getIdElement(), identifiesSameResourceAs( patient.getIdElement() ) ); + } + + @Test + void canStorePatientWithoutAdditionalSetup() { + String subjectPatientId = createTestSpecificId() ; + Patient subjectPatient = new Patient() ; + subjectPatient.setId( new IdType( "Patient", subjectPatientId ) ) ; + + IIdType claimedPatientId = patientAppClient.update().resource(subjectPatient).execute().getId(); + + Patient readPatientResp = patientAppClient.read().resource(Patient.class).withId(claimedPatientId).execute(); + + Assertions.assertNotNull( readPatientResp ); + } + + private Condition createHealthConcern( Reference subjectRef, String codeAsPlainText ) { + Condition condition = new Condition() ; + condition.setSubject( subjectRef ) ; + + CodeableConcept healthConcernCategory = new CodeableConcept(); + healthConcernCategory.addCoding( new Coding( "http://hl7.org/fhir/ValueSet/condition-category", "health-concern", "health concern" ) ) ; + condition.setCategory( asList( healthConcernCategory ) ) ; + + condition.setCode( healthConcernCategory.setText(codeAsPlainText) ) ; + + return condition ; + } + + @Test + void canStoreConditionWhereSubjectIsClaimedPatientWithoutAdditionalSetup() { + String subjectPatientId = createTestSpecificId() ; + Patient subjectPatient = new Patient() ; + subjectPatient.setId( new IdType( "Patient", subjectPatientId ) ) ; + + IIdType claimedPatientId = patientAppClient.update().resource(subjectPatient).execute().getId(); + + Condition condition = createHealthConcern( new Reference(claimedPatientId), "my health concern" ) ; + + IIdType conditionId = patientAppClient.create().resource(condition).execute().getId(); + + Condition readQuestResp = patientAppClient.read().resource(Condition.class).withId(conditionId).execute(); + + Assertions.assertNotNull( readQuestResp ); + } + + @Test + void canExpunge() { + String authorizedPatientId = createTestSpecificId() ; + String token = mockPrincipalRegistry.register().principal( "MyPatient", "Patient/" + authorizedPatientId ).token() ; + + IGenericClient patientAppClient = authenticatingClient( token ) ; + + Reference authorizedPatient = new Reference( new IdType( "Patient", authorizedPatientId ) ); + + List linkages = new TestClientSearch(patientAppClient).searchLinkagesWhereItemRefersTo( authorizedPatient.getReferenceElement() ) ; + assertThat( linkages, hasSize(1) ) ; + Linkage linkage = linkages.get(0) ; + Reference localPatientRef = + linkage.getItem().stream() + .filter( i -> i.getType() == Linkage.LinkageType.SOURCE ) + .map( Linkage.LinkageItemComponent::getResource ) + .findFirst() + .orElseThrow( AssertionFailedError::new ) + ; + + String questId = createTestSpecificId(); + + QuestionnaireResponse questionnaireResponse = new QuestionnaireResponse() ; + questionnaireResponse.setSubject( authorizedPatient ) ; + questionnaireResponse.setQuestionnaire( questId ) ; + IIdType questRespId = patientAppClient.create().resource(questionnaireResponse).execute().getId(); + + QuestionnaireResponse readQuestResp = patientAppClient.read().resource(QuestionnaireResponse.class).withId(questRespId).execute(); + Assertions.assertNotNull( readQuestResp ); + +// patientAppClient +// .operation() +// .onInstance( localPatientRef.getReferenceElement() ) +// .named( "$expunge" ) +// .withParameter( BooleanType.class, "expungeEverything", new BooleanType(true) ) +// ; + } + +} diff --git a/src/test/java/edu/ohsu/cmp/ecp/sds/PatientAppClientTest.java b/src/test/java/edu/ohsu/cmp/ecp/sds/PatientAppClientTest.java index bd6f9c9..86d6387 100644 --- a/src/test/java/edu/ohsu/cmp/ecp/sds/PatientAppClientTest.java +++ b/src/test/java/edu/ohsu/cmp/ecp/sds/PatientAppClientTest.java @@ -5,8 +5,13 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static edu.ohsu.cmp.ecp.sds.SupplementalDataStoreMatchers.identifiesResource; +import static edu.ohsu.cmp.ecp.sds.SupplementalDataStoreMatchers.identifiesSameResourceAs; + +import java.util.List; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.CapabilityStatement; @@ -17,11 +22,15 @@ import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.Condition; import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Linkage; import org.hl7.fhir.r4.model.QuestionnaireResponse; +import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Type; import org.hl7.fhir.r4.model.UriType; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ActiveProfiles; @@ -35,6 +44,12 @@ @ActiveProfiles( "auth-aware-test") public class PatientAppClientTest extends BaseSuppplementalDataStoreTest { + /* + * Use Case: retrieve metadata + * + * Use Case: store new LOCAL Condition/Goal/Observation resources and retrieve them + */ + @Autowired AppTestMockPrincipalRegistry mockPrincipalRegistry ; @@ -43,8 +58,20 @@ public class PatientAppClientTest extends BaseSuppplementalDataStoreTest { private static final String FOREIGN_PARTITION_NAME = "http://my.ehr.org/fhir/R4/" ; + private IIdType authorizedPatientId; + private IGenericClient patientAppClient ; + + @BeforeEach + public void setupAuthorizedPatient() { + authorizedPatientId = new IdType( FOREIGN_PARTITION_NAME, "Patient", createTestSpecificId(), null ); + String token = mockPrincipalRegistry.register().principal( "MyPatient", authorizedPatientId.toString() ).token() ; + + patientAppClient = authenticatingClient( token ) ; + } + @Test void canFetchOAuth2Metadata() { + // no authorization attached IGenericClient patientAppClient = client() ; CapabilityStatement cap = patientAppClient.capabilities().ofType( CapabilityStatement.class ).execute() ; @@ -86,16 +113,10 @@ void canFetchOAuth2Metadata() { @Test void canStoreQuestionnaireWhereSubjectIsAuthorizedPatientWithoutAdditionalSetup() { - String authorizedPatientId = createTestSpecificId() ; - String token = mockPrincipalRegistry.register().principal( "MyPatient", "Patient/" + authorizedPatientId ).token() ; - - IGenericClient patientAppClient = authenticatingClient( token ) ; - - Reference authorizedPatient = new Reference( new IdType( "Patient", authorizedPatientId ) ); String questId = createTestSpecificId(); QuestionnaireResponse questionnaireResponse = new QuestionnaireResponse() ; - questionnaireResponse.setSubject( authorizedPatient ) ; + questionnaireResponse.setSubject( new Reference( authorizedPatientId ) ) ; questionnaireResponse.setQuestionnaire( questId ) ; IIdType questRespId = patientAppClient.create().resource(questionnaireResponse).execute().getId(); @@ -106,11 +127,6 @@ void canStoreQuestionnaireWhereSubjectIsAuthorizedPatientWithoutAdditionalSetup( @Test void cannotStoreQuestionnaireWhereSubjectIsNotAuthorizedPatient() { - String authorizedPatientId = createTestSpecificId() ; - String token = mockPrincipalRegistry.register().principal( "MyPatient", "Patient/" + authorizedPatientId ).token() ; - - IGenericClient patientAppClient = authenticatingClient( token ) ; - Reference nonAuthorizedPatient = new Reference( new IdType( "Patient", createTestSpecificId() ) ); String questId = createTestSpecificId(); @@ -123,7 +139,7 @@ void cannotStoreQuestionnaireWhereSubjectIsNotAuthorizedPatient() { patientAppClient.create().resource(questionnaireResponse).execute().getId(); } ); - assertThat( exception.getMessage(), containsString( "Access denied by rule: everything else" ) ) ; + assertThat( exception.getMessage(), containsString( "Access denied by rule: no access rules grant permission" ) ) ; } private Condition createHealthConcern( Reference subjectRef, String codeAsPlainText ) { @@ -141,14 +157,7 @@ private Condition createHealthConcern( Reference subjectRef, String codeAsPlainT @Test void canStoreConditionWhereSubjectIsAuthorizedPatientWithoutAdditionalSetup() { - String authorizedPatientId = createTestSpecificId() ; - String token = mockPrincipalRegistry.register().principal( "MyPatient", "Patient/" + authorizedPatientId ).token() ; - - IGenericClient patientAppClient = authenticatingClient( token ) ; - - Reference authorizedPatient = new Reference( new IdType( "Patient", authorizedPatientId ) ); - - Condition condition = createHealthConcern( authorizedPatient, "my health concern" ) ; + Condition condition = createHealthConcern( new Reference( authorizedPatientId ), "my health concern" ) ; IIdType conditionId = patientAppClient.create().resource(condition).execute().getId(); @@ -157,6 +166,7 @@ void canStoreConditionWhereSubjectIsAuthorizedPatientWithoutAdditionalSetup() { Assertions.assertNotNull( readQuestResp ); } + /* * Scenario: "log in as Nancy Smart (related to Timmy) and then access records for Barbara" * c.f. authorize_response_relatedperson.json @@ -166,7 +176,6 @@ void canStoreConditionWhereSubjectIsAuthorizedPatientWithoutAdditionalSetup() { @Test void cannotStoreConditionWhereSubjectIsUnrelatedToAuthorizedUser() { String authorizedRelatedPersonId = createTestSpecificId() ; - String authorizedPatientId = createTestSpecificId() ; String token = mockPrincipalRegistry.register().principal( "MyRelatedPerson", "RelatedPerson/" + authorizedRelatedPersonId ).token() ; mockPermissionRegistry .person( "Patient/other-patient" ) @@ -176,7 +185,7 @@ void cannotStoreConditionWhereSubjectIsUnrelatedToAuthorizedUser() { IGenericClient patientAppClient = authenticatingClient( token ) ; - Reference authorizedPatient = new Reference( new IdType( "Patient", authorizedPatientId ) ); + Reference authorizedPatient = new Reference( authorizedPatientId ); Condition condition = createHealthConcern( authorizedPatient, "my health concern" ) ; @@ -195,7 +204,6 @@ void cannotStoreConditionWhereSubjectIsUnrelatedToAuthorizedUser() { @Test void canStoreConditionWhereSubjectIsRelatedToAuthorizedUser() { String authorizedRelatedPersonId = createTestSpecificId() ; - String authorizedPatientId = createTestSpecificId() ; String token = mockPrincipalRegistry.register().principal( "MyRelatedPerson", "RelatedPerson/" + authorizedRelatedPersonId ).token() ; mockPermissionRegistry .person( "Patient/" + authorizedPatientId ) @@ -205,9 +213,7 @@ void canStoreConditionWhereSubjectIsRelatedToAuthorizedUser() { IGenericClient patientAppClient = authenticatingClient( token ) ; - Reference authorizedPatient = new Reference( new IdType( "Patient", authorizedPatientId ) ); - - Condition condition = createHealthConcern( authorizedPatient, "my health concern" ) ; + Condition condition = createHealthConcern( new Reference( authorizedPatientId ), "my health concern" ) ; IIdType conditionId = patientAppClient.create().resource(condition).execute().getId(); @@ -215,5 +221,44 @@ void canStoreConditionWhereSubjectIsRelatedToAuthorizedUser() { Assertions.assertNotNull( readQuestResp ); } + + + @Test + void canExpunge() { + String authorizedPatientId = createTestSpecificId() ; + String token = mockPrincipalRegistry.register().principal( "MyPatient", "Patient/" + authorizedPatientId ).token() ; + + IGenericClient patientAppClient = authenticatingClient( token ) ; + + Reference authorizedPatient = new Reference( new IdType( "Patient", authorizedPatientId ) ); + + List linkages = new TestClientSearch(patientAppClient).searchLinkagesWhereItemRefersTo( authorizedPatient.getReferenceElement() ) ; + assertThat( linkages, hasSize(1) ) ; + Linkage linkage = linkages.get(0) ; + Reference localPatientRef = + linkage.getItem().stream() + .filter( i -> i.getType() == Linkage.LinkageType.SOURCE ) + .map( Linkage.LinkageItemComponent::getResource ) + .findFirst() + .orElseThrow( AssertionFailedError::new ) + ; + + String questId = createTestSpecificId(); + + QuestionnaireResponse questionnaireResponse = new QuestionnaireResponse() ; + questionnaireResponse.setSubject( authorizedPatient ) ; + questionnaireResponse.setQuestionnaire( questId ) ; + IIdType questRespId = patientAppClient.create().resource(questionnaireResponse).execute().getId(); + + QuestionnaireResponse readQuestResp = patientAppClient.read().resource(QuestionnaireResponse.class).withId(questRespId).execute(); + Assertions.assertNotNull( readQuestResp ); + +// patientAppClient +// .operation() +// .onInstance( localPatientRef.getReferenceElement() ) +// .named( "$expunge" ) +// .withParameter( BooleanType.class, "expungeEverything", new BooleanType(true) ) +// ; + } } diff --git a/src/test/java/edu/ohsu/cmp/ecp/sds/PatientPartitionsTest.java b/src/test/java/edu/ohsu/cmp/ecp/sds/PatientPartitionsTest.java index 6281ac5..0e9f176 100644 --- a/src/test/java/edu/ohsu/cmp/ecp/sds/PatientPartitionsTest.java +++ b/src/test/java/edu/ohsu/cmp/ecp/sds/PatientPartitionsTest.java @@ -53,7 +53,7 @@ void cannotStoreOtherResourceInForeignPartitionBeforePatient() { } ); - assertThat( ex.getMessage(), containsString("Access denied by rule: everything else") ) ; + assertThat( ex.getMessage(), containsString("Access denied by rule: no access rules grant permission") ) ; } @Test @@ -92,7 +92,7 @@ void cannotReadOtherPatientResourceInForeignPartition() { } ); - assertThat( ex.getMessage(), containsString("Access denied by rule: everything else") ) ; + assertThat( ex.getMessage(), containsString("Access denied by rule: no access rules grant permission") ) ; } @Test diff --git a/src/test/java/edu/ohsu/cmp/ecp/sds/SupplementalDataStoreMatchers.java b/src/test/java/edu/ohsu/cmp/ecp/sds/SupplementalDataStoreMatchers.java new file mode 100644 index 0000000..50525cf --- /dev/null +++ b/src/test/java/edu/ohsu/cmp/ecp/sds/SupplementalDataStoreMatchers.java @@ -0,0 +1,65 @@ +package edu.ohsu.cmp.ecp.sds; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IIdType; + +public class SupplementalDataStoreMatchers { + + private SupplementalDataStoreMatchers() {} + + public static BaseMatcher identifiesResource( IAnyResource resource ) { + return identifiesSameResourceAs( null == resource ? null : resource.getIdElement() ) ; + } + + public static BaseMatcher identifiesSameResourceAs( IIdType idType ) { + return new IdMatcher( idType ) ; + } + + public static class IdMatcher extends BaseMatcher { + + private final IIdType expectedId ; + + public IdMatcher(IIdType idType) { + this.expectedId = idType ; + } + + @Override + public boolean matches(Object item) { + if ( null == expectedId ) + return null == item ; + if ( !(item instanceof IIdType) ) + return false ; + IIdType id = (IIdType)item ; + + if ( id.isEmpty() ) + return expectedId.isEmpty() ; + + if ( id.hasBaseUrl() && expectedId.hasBaseUrl() ) + if ( !id.getBaseUrl().equals( expectedId.getBaseUrl() )) + return false ; + + if ( id.hasResourceType() && expectedId.hasResourceType() ) + if ( !id.getResourceType().equals( expectedId.getResourceType() )) + return false ; + + if ( id.hasIdPart() && expectedId.hasIdPart() ) + if ( !id.getIdPart().equals( expectedId.getIdPart() )) + return false ; + + if ( id.hasVersionIdPart() && expectedId.hasVersionIdPart() ) + if ( !id.getVersionIdPart().equals( expectedId.getVersionIdPart() )) + return false ; + + return true ; + } + + @Override + public void describeTo(Description description) { + description.appendText( "FHIR id identifies same resource as ") ; + description.appendValue( expectedId ) ; + } + + } +} diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml index 739379e..663da96 100644 --- a/src/test/resources/application.yaml +++ b/src/test/resources/application.yaml @@ -83,7 +83,7 @@ hapi: # - Observation # allow_cascading_deletes: true # allow_contains_searches: true - # allow_external_references: true + allow_external_references: true # allow_multiple_delete: true # allow_override_default_search_params: true # auto_create_placeholder_reference_targets: false