diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/domain/ChainedSearchParam.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/domain/ChainedSearchParam.java index eff7c939a7d..292b9d98901 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/domain/ChainedSearchParam.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/domain/ChainedSearchParam.java @@ -6,6 +6,11 @@ package com.ibm.fhir.persistence.jdbc.domain; +import static com.ibm.fhir.search.SearchConstants.PROFILE; +import static com.ibm.fhir.search.SearchConstants.SECURITY; +import static com.ibm.fhir.search.SearchConstants.TAG; +import static com.ibm.fhir.search.SearchConstants.URL; + import java.util.logging.Logger; import com.ibm.fhir.persistence.exception.FHIRPersistenceException; @@ -85,8 +90,13 @@ private void addFinalFilter(T currentSubQuery, SearchQueryVisitor visitor msp.visit(currentSubQuery, visitor); } else if (currentParm.getType() == Type.COMPOSITE) { visitor.addCompositeParam(currentSubQuery, currentParm); - } else if (currentParm.isCanonical()) { + } else if (currentParm.isCanonical() || PROFILE.equals(currentParm.getCode()) || + (currentParm.getType() == Type.URI && URL.equals(currentParm.getCode()))) { visitor.addCanonicalParam(currentSubQuery, ((QueryData)currentSubQuery).getResourceType(), currentParm); + } else if (TAG.equals(currentParm.getCode())) { + visitor.addTagParam(currentSubQuery, ((QueryData)currentSubQuery).getResourceType(), currentParm); + } else if (SECURITY.equals(currentParm.getCode())) { + visitor.addSecurityParam(currentSubQuery, ((QueryData)currentSubQuery).getResourceType(), currentParm); } else { visitor.addFilter(currentSubQuery, getRootResourceType(), currentParm); } diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/domain/SearchQueryRenderer.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/domain/SearchQueryRenderer.java index 6da5e7dcc44..866153b78f9 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/domain/SearchQueryRenderer.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/domain/SearchQueryRenderer.java @@ -1757,6 +1757,14 @@ private QueryData addGlobalTokenParam(QueryData queryData, QueryParameter queryP // need to use paramAlias as the common token values alias and generate a new alias for the parameter table. tokenValuesAlias = getParamAlias(getNextAliasIndex()); tokenValueSearch = true; + } else { + ColumnExpNodeVisitor visitor = new ColumnExpNodeVisitor(); // gathers all columns used in the filter expression + Set columns = filter.visit(visitor); + if (columns.contains(DataDefinitionUtil.getQualifiedName(paramAlias, CODE_SYSTEM_ID)) || + columns.contains(DataDefinitionUtil.getQualifiedName(paramAlias, TOKEN_VALUE))) { + tokenValuesAlias = getParamAlias(getNextAliasIndex()); + tokenValueSearch = true; + } } if (Modifier.NOT.equals(queryParm.getModifier()) || Modifier.NOT_IN.equals(queryParm.getModifier())) { @@ -1765,10 +1773,6 @@ private QueryData addGlobalTokenParam(QueryData queryData, QueryParameter queryP exists.from(parameterTable, alias(tokenValuesAlias)) .where(tokenValuesAlias, "LOGICAL_RESOURCE_ID").eq(lrAlias, "LOGICAL_RESOURCE_ID"); // correlate with the main query if (tokenValueSearch) { - if (Modifier.TEXT.equals(queryParm.getModifier())) { - exists.from().where().and(tokenValuesAlias, "PARAMETER_NAME_ID") - .eq(getParameterNameId(queryParm.getCode() + SearchConstants.TEXT_MODIFIER_SUFFIX)); - } // Join to common token values table and add filter predicate to the common token values join on clause exists.from().innerJoin("COMMON_TOKEN_VALUES", alias(paramAlias), on(paramAlias, "COMMON_TOKEN_VALUE_ID") .eq(tokenValuesAlias, "COMMON_TOKEN_VALUE_ID") @@ -2083,10 +2087,16 @@ public void addFilter(QueryData queryData, String resourceType, QueryParameter c // AND P3.PARAMETER_NAME_ID = 123 -- 'name parameter' // AND P3.STR_VALUE = 'Jones') -- 'name filter' final int aliasIndex = getNextAliasIndex(); - final String paramTable = paramValuesTableName(queryData.getResourceType(), currentParm); final String paramAlias = getParamAlias(aliasIndex); - WhereFragment pf = paramFilter(currentParm, paramAlias); + final String paramTable; + if (Type.TOKEN.equals(currentParm.getType()) && + !(TAG.equals(currentParm.getCode()) || SECURITY.equals(currentParm.getCode()))) { + paramTable = getTokenParamTable(pf.getExpression(), queryData.getResourceType(), paramAlias); + } else { + paramTable = paramValuesTableName(queryData.getResourceType(), currentParm); + } + if (currentParm.getModifier() == Modifier.NOT) { // Needs to be handled as a NOT EXISTS correlated subquery SelectAdapter exists = Select.select("1"); diff --git a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractSearchTokenTest.java b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractSearchTokenTest.java index bad25a1ca3b..5d0f5ab608b 100644 --- a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractSearchTokenTest.java +++ b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractSearchTokenTest.java @@ -248,7 +248,7 @@ public void testSearchToken_CodeableConcept() throws Exception { assertSearchReturnsSavedResource("CodeableConcept-validCodeAndSystem", "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation|"); // This shouldn't return any results because the CodeableConcept has a system -// assertSearchDoesntReturnSavedResource("CodeableConcept", "|code"); + assertSearchDoesntReturnSavedResource("CodeableConcept", "|code"); } @Test @@ -268,9 +268,7 @@ public void testSearchToken_CodeableConcept_escaped() throws Exception { public void testSearchToken_CodeableConcept_chained() throws Exception { assertSearchReturnsComposition("subject:Basic.CodeableConcept", "code"); assertSearchReturnsComposition("subject:Basic.CodeableConcept", "http://example.org/codesystem|code"); - - // system-only token search is not working for chained searches yet (https://github.com/IBM/FHIR/issues/2553) -// assertSearchReturnsComposition("subject:Basic.CodeableConcept", "http://example.org/codesystem|"); + assertSearchReturnsComposition("subject:Basic.CodeableConcept", "http://example.org/codesystem|"); // This shouldn't return any results because the CodeableConcept has a system assertSearchDoesntReturnComposition("subject:Basic.CodeableConcept", "|code"); @@ -400,9 +398,7 @@ public void testSearchToken_Coding_escaped() throws Exception { public void testSearchToken_Coding_chained() throws Exception { assertSearchReturnsComposition("subject:Basic.Coding", "code"); assertSearchReturnsComposition("subject:Basic.Coding", "http://example.org/codesystem|code"); - - // system-only token search is not working for chained searches yet (https://github.com/IBM/FHIR/issues/2553) -// assertSearchReturnsComposition("subject:Basic.Coding", "http://example.org/codesystem|"); + assertSearchReturnsComposition("subject:Basic.Coding", "http://example.org/codesystem|"); // This shouldn't return any results because the Coding has a system assertSearchDoesntReturnComposition("subject:Basic.Coding", "|code"); @@ -523,16 +519,14 @@ public void testSearchToken_Identifier() throws Exception { assertSearchReturnsSavedResource("Identifier-validValueAndSystem", "http://hl7.org/fhir/identifier-use|"); // This shouldn't return any results because the Identifier has a system -// assertSearchDoesntReturnSavedResource("Identifier", "|code"); + assertSearchDoesntReturnSavedResource("Identifier", "|code"); } @Test public void testSearchToken_Identifier_chained() throws Exception { assertSearchReturnsComposition("subject:Basic.Identifier", "code"); assertSearchReturnsComposition("subject:Basic.Identifier", "http://example.org/identifiersystem|code"); - - // system-only token search is not working for chained searches yet (https://github.com/IBM/FHIR/issues/2553) -// assertSearchReturnsComposition("subject:Basic.Identifier", "http://example.org/identifiersystem|"); + assertSearchReturnsComposition("subject:Basic.Identifier", "http://example.org/identifiersystem|"); // This shouldn't return any results because the Identifier has a system assertSearchDoesntReturnComposition("subject:Basic.Identifier", "|code"); diff --git a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractWholeSystemSearchTest.java b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractWholeSystemSearchTest.java index 5a757dade39..1bfbaf0984c 100644 --- a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractWholeSystemSearchTest.java +++ b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractWholeSystemSearchTest.java @@ -226,6 +226,14 @@ public void testSearchAllUsingSecurity() throws Exception { assertTrue(isResourceInResponse(savedResource, resources), "Expected resource not found in the response"); } + @Test + public void testSearchAllUsingSecuritySystemOnly() throws Exception { + List resources = runQueryTest(Resource.class, "_security", SECURITY_SYSTEM + "|"); + assertNotNull(resources); + assertEquals(resources.size(), 1, "Number of resources returned"); + assertTrue(isResourceInResponse(savedResource, resources), "Expected resource not found in the response"); + } + @Test public void testSearchAllUsingSource() throws Exception { List resources = runQueryTest(Resource.class, "_source", SOURCE); @@ -282,6 +290,14 @@ public void testSearchAllUsingTagModifierBelow() throws Exception { assertTrue(isResourceInResponse(savedResource, resources), "Expected resource not found in the response"); } + @Test + public void testSearchAllUsingTagSystemOnly() throws Exception { + List resources = runQueryTest(Resource.class, "_tag", TAG_SYSTEM2 + "|"); + assertNotNull(resources); + assertEquals(resources.size(), 1, "Number of resources returned"); + assertTrue(isResourceInResponse(savedResource, resources), "Expected resource not found in the response"); + } + @Test public void testSearchAllUsingProfile() throws Exception { List resources = runQueryTest(Resource.class, "_profile", PROFILE); diff --git a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractReverseChainTest.java b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractReverseChainTest.java index cad033ca5e8..be15e31eae0 100644 --- a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractReverseChainTest.java +++ b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractReverseChainTest.java @@ -6,7 +6,9 @@ package com.ibm.fhir.persistence.test.common; +import static com.ibm.fhir.model.type.Code.code; import static com.ibm.fhir.model.type.String.string; +import static com.ibm.fhir.model.type.Uri.uri; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertNotNull; import static org.testng.AssertJUnit.assertTrue; @@ -27,16 +29,20 @@ import com.ibm.fhir.model.config.FHIRModelConfig; import com.ibm.fhir.model.resource.Device; import com.ibm.fhir.model.resource.Encounter; +import com.ibm.fhir.model.resource.Library; import com.ibm.fhir.model.resource.Observation; import com.ibm.fhir.model.resource.Organization; import com.ibm.fhir.model.resource.Patient; import com.ibm.fhir.model.resource.Resource; import com.ibm.fhir.model.test.TestUtil; +import com.ibm.fhir.model.type.Canonical; import com.ibm.fhir.model.type.Code; import com.ibm.fhir.model.type.CodeableConcept; import com.ibm.fhir.model.type.Coding; import com.ibm.fhir.model.type.HumanName; +import com.ibm.fhir.model.type.Meta; import com.ibm.fhir.model.type.Reference; +import com.ibm.fhir.model.type.Uri; import com.ibm.fhir.model.type.code.ObservationStatus; /** @@ -60,6 +66,7 @@ public abstract class AbstractReverseChainTest extends AbstractPersistenceTest { private static Organization savedOrg1; private static Organization savedOrg2; private static Organization savedOrg3; + private static Library savedLibrary1; private static boolean checkReferenceTypes = true; private static Instant now = Instant.now().truncatedTo(ChronoUnit.MILLIS); @@ -75,6 +82,9 @@ public void createResources() throws Exception { Observation observation = TestUtil.getMinimalResource(Observation.class); Patient patient = TestUtil.getMinimalResource(Patient.class); Device device = TestUtil.getMinimalResource(Device.class); + Library library = TestUtil.getMinimalResource(Library.class); + Coding uniqueTag = Coding.builder().system(uri("http://ibm.com/fhir/tag")).code(code(now.toString())).build(); + Coding uniqueSecurity = Coding.builder().system(uri("http://ibm.com/fhir/security")).code(code(now.toString())).build(); // Organizations that will be referenced by a Patient savedOrg1 = org.toBuilder().active(com.ibm.fhir.model.type.Boolean.of(true)).build(); @@ -92,7 +102,13 @@ public void createResources() throws Exception { savedObservation1 = persistence.create(getDefaultPersistenceContext(), observation).getResource(); // a Patient that will be referenced by Observations and references an Organization - savedPatient1 = patient.toBuilder().managingOrganization(reference("Organization/" + savedOrg2.getId())).build(); + savedPatient1 = patient.toBuilder() + .meta(Meta.builder() + .tag(uniqueTag) + .security(uniqueSecurity) + .profile(Canonical.of("http://ibm.com/fhir/profile/" + now.toString())).build()) + .managingOrganization(reference("Organization/" + savedOrg2.getId())) + .build(); savedPatient1 = persistence.create(getDefaultPersistenceContext(), savedPatient1).getResource(); // an Observation with a reference to a Patient and a logical ID-only reference to another observation @@ -128,10 +144,15 @@ public void createResources() throws Exception { savedPatient3 = patient.toBuilder().managingOrganization(reference("Organization/" + savedOrg1.getId())).build(); savedPatient3 = persistence.create(getDefaultPersistenceContext(), savedPatient3).getResource(); - // an Observation with a reference to a Patient + // a Library that is referenced by an Observation + savedLibrary1 = library.toBuilder().url(Uri.of("http://ibm.com/fhir/Library/abc")).version(string("1.0")).build(); + savedLibrary1 = persistence.create(getDefaultPersistenceContext(), savedLibrary1).getResource(); + + // an Observation with a reference to a Patient and a Library savedObservation5 = observation.toBuilder() .subject(reference("Patient/" + savedPatient3.getId())) .status(ObservationStatus.FINAL) + .focus(reference("Library/" + savedLibrary1.getId())) .build(); savedObservation5 = persistence.create(getDefaultPersistenceContext(), savedObservation5).getResource(); @@ -165,7 +186,8 @@ public void createResources() throws Exception { public void deleteResources() throws Exception { Resource[] resources = {savedPatient1, savedPatient2, savedPatient3, savedPatient4, savedObservation1, savedObservation2, savedObservation3, savedObservation4, savedObservation5, - savedObservation6, savedEncounter1, savedDevice1, savedDevice2, savedOrg1, savedOrg2, savedOrg3}; + savedObservation6, savedEncounter1, savedDevice1, savedDevice2, savedOrg1, savedOrg2, savedOrg3, + savedLibrary1}; if (persistence.isDeleteSupported()) { if (persistence.isTransactional()) { @@ -220,6 +242,54 @@ public void testReverseChainSingleResult() throws Exception { assertEquals(savedPatient3.getId(), resources.get(0).getId()); } + /** + * This test queries for Organizations which are referenced by Patients with a specified profile. + * One patient is found containing the profile, thus one Organization is returned. + * @throws Exception + */ + @Test + public void testReverseChainWithProfile() throws Exception { + Map> queryParms = new HashMap>(); + queryParms.put("_has:Patient:organization:_profile", Collections.singletonList("http://ibm.com/fhir/profile/" + now.toString())); + List resources = runQueryTest(Organization.class, queryParms); + assertNotNull(resources); + assertEquals(1, resources.size()); + assertEquals("Organization", resources.get(0).getClass().getSimpleName()); + assertEquals(savedOrg2.getId(), resources.get(0).getId()); + } + + /** + * This test queries for Organizations which are referenced by Patients with a specified tag. + * One patient is found containing the tag, thus one Organization is returned. + * @throws Exception + */ + @Test + public void testReverseChainWithTag() throws Exception { + Map> queryParms = new HashMap>(); + queryParms.put("_has:Patient:organization:_tag", Collections.singletonList("http://ibm.com/fhir/tag|" + now.toString())); + List resources = runQueryTest(Organization.class, queryParms); + assertNotNull(resources); + assertEquals(1, resources.size()); + assertEquals("Organization", resources.get(0).getClass().getSimpleName()); + assertEquals(savedOrg2.getId(), resources.get(0).getId()); + } + + /** + * This test queries for Organizations which are referenced by Patients with a specified security. + * One patient is found containing the security, thus one Organization is returned. + * @throws Exception + */ + @Test + public void testReverseChainWithSecurity() throws Exception { + Map> queryParms = new HashMap>(); + queryParms.put("_has:Patient:organization:_security", Collections.singletonList("http://ibm.com/fhir/security|" + now.toString())); + List resources = runQueryTest(Organization.class, queryParms); + assertNotNull(resources); + assertEquals(1, resources.size()); + assertEquals("Organization", resources.get(0).getClass().getSimpleName()); + assertEquals(savedOrg2.getId(), resources.get(0).getId()); + } + /** * This test queries for Patients which are referenced by Observations with a specified encounter. * Two observations are found containing the encounter reference, thus two Patients are returned. @@ -545,6 +615,82 @@ public void testChainMultipleResults() throws Exception { assertTrue(resourceIds.contains(savedObservation3.getId())); } + /** + * This test queries for Observations which reference Patients with a specified profile. + * One patient is found containing the profile, thus two Observations are returned. + * @throws Exception + */ + @Test + public void testChainWithProfile() throws Exception { + Map> queryParms = new HashMap>(); + queryParms.put("patient:Patient._profile", Collections.singletonList("http://ibm.com/fhir/profile/" + now.toString())); + List resources = runQueryTest(Observation.class, queryParms); + assertNotNull(resources); + assertEquals(2, resources.size()); + List resourceIds = new ArrayList<>(); + for (Resource resource : resources) { + resourceIds.add(resource.getId()); + } + assertTrue(resourceIds.contains(savedObservation2.getId())); + assertTrue(resourceIds.contains(savedObservation3.getId())); + } + + /** + * This test queries for Observations which reference Patients with a specified tag. + * One patient is found containing the tag, thus two Observations are returned. + * @throws Exception + */ + @Test + public void testChainWithTag() throws Exception { + Map> queryParms = new HashMap>(); + queryParms.put("patient:Patient._tag", Collections.singletonList("http://ibm.com/fhir/tag|" + now.toString())); + List resources = runQueryTest(Observation.class, queryParms); + assertNotNull(resources); + assertEquals(2, resources.size()); + List resourceIds = new ArrayList<>(); + for (Resource resource : resources) { + resourceIds.add(resource.getId()); + } + assertTrue(resourceIds.contains(savedObservation2.getId())); + assertTrue(resourceIds.contains(savedObservation3.getId())); + } + + /** + * This test queries for Observations which reference Patients with a specified security. + * One patient is found containing the security, thus two Observations are returned. + * @throws Exception + */ + @Test + public void testChainWithSecurity() throws Exception { + Map> queryParms = new HashMap>(); + queryParms.put("patient:Patient._security", Collections.singletonList("http://ibm.com/fhir/security|" + now.toString())); + List resources = runQueryTest(Observation.class, queryParms); + assertNotNull(resources); + assertEquals(2, resources.size()); + List resourceIds = new ArrayList<>(); + for (Resource resource : resources) { + resourceIds.add(resource.getId()); + } + assertTrue(resourceIds.contains(savedObservation2.getId())); + assertTrue(resourceIds.contains(savedObservation3.getId())); + } + + /** + * This test queries for Observations which reference Libraries with a specified url. + * One library is found containing the url, thus one Observation is returned. + * @throws Exception + */ + @Test + public void testChainWithUrl() throws Exception { + Map> queryParms = new HashMap>(); + queryParms.put("focus:Library.url", Collections.singletonList("http://ibm.com/fhir/Library/abc|1.0")); + List resources = runQueryTest(Observation.class, queryParms); + assertNotNull(resources); + assertEquals(1, resources.size()); + assertEquals("Observation", resources.get(0).getClass().getSimpleName()); + assertEquals(savedObservation5.getId(), resources.get(0).getId()); + } + /** * This test queries for Observations which reference Patients which in turn * reference Organizations with a specific name. diff --git a/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchAllTest.java b/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchAllTest.java index 44485c932f7..367b9682713 100644 --- a/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchAllTest.java +++ b/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchAllTest.java @@ -227,6 +227,20 @@ public void testSearchAllUsingTag() throws Exception { assertTrue(bundle.getEntry().size() >= 1); } + @Test(groups = { "server-search-all" }, dependsOnMethods = { "testCreatePatient" }) + public void testSearchAllUsingTagSystemOnly() throws Exception { + FHIRParameters parameters = new FHIRParameters(); + parameters.searchParam("_tag", "http://ibm.com/fhir/tag|"); + FHIRResponse response = client.searchAll(parameters, false, headerTenant, headerDataStore); + assertResponse(response.getResponse(), Response.Status.OK.getStatusCode()); + Bundle bundle = response.getResource(Bundle.class); + + assertNotNull(bundle); + printOutResource(DEBUG_SEARCH, bundle); + + assertTrue(bundle.getEntry().size() >= 1); + } + @Test(groups = { "server-search-all" }, dependsOnMethods = { "testCreatePatient" }) public void testSearchAllUsingSecurity() throws Exception { // @@ -246,6 +260,20 @@ public void testSearchAllUsingSecurity() throws Exception { assertTrue(bundle.getEntry().size() >= 1); } + @Test(groups = { "server-search-all" }, dependsOnMethods = { "testCreatePatient" }) + public void testSearchAllUsingSecuritySystemOnly() throws Exception { + FHIRParameters parameters = new FHIRParameters(); + parameters.searchParam("_security", "http://ibm.com/fhir/security|"); + FHIRResponse response = client.searchAll(parameters, false, headerTenant, headerDataStore); + assertResponse(response.getResponse(), Response.Status.OK.getStatusCode()); + Bundle bundle = response.getResource(Bundle.class); + + assertNotNull(bundle); + printOutResource(DEBUG_SEARCH, bundle); + + assertTrue(bundle.getEntry().size() >= 1); + } + @Test(groups = { "server-search-all" }, dependsOnMethods = { "testCreatePatient" }) public void testSearchAllUsingSource() throws Exception { FHIRParameters parameters = new FHIRParameters(); diff --git a/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchChainTest.java b/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchChainTest.java index c6f36a994eb..48b11995948 100644 --- a/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchChainTest.java +++ b/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchChainTest.java @@ -6,11 +6,14 @@ package com.ibm.fhir.server.test; +import static com.ibm.fhir.model.type.Code.code; +import static com.ibm.fhir.model.type.Uri.uri; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import java.time.Instant; +import java.util.UUID; import javax.ws.rs.client.Entity; import javax.ws.rs.client.WebTarget; @@ -31,6 +34,8 @@ import com.ibm.fhir.model.resource.Procedure; import com.ibm.fhir.model.test.TestUtil; import com.ibm.fhir.model.type.Canonical; +import com.ibm.fhir.model.type.Coding; +import com.ibm.fhir.model.type.Meta; import com.ibm.fhir.model.type.Reference; import com.ibm.fhir.model.type.Uri; import com.ibm.fhir.model.type.code.AdministrativeGender; @@ -48,14 +53,24 @@ public class SearchChainTest extends FHIRServerTestBase { private String measureReportId; private String measureId; private String libraryId; + private String strUniqueTag = UUID.randomUUID().toString(); @Test(groups = { "server-search-chain" }) public void testCreatePatient() throws Exception { WebTarget target = getWebTarget(); + Coding security = Coding.builder().system(uri("http://ibm.com/fhir/security/" + strUniqueTag)).code(code(strUniqueTag)).build(); + Coding tag = Coding.builder().system(uri("http://ibm.com/fhir/tag/" + strUniqueTag)).code(code(strUniqueTag)).build(); + // Build a new Patient and then call the 'create' API. Patient patient = TestUtil.readLocalResource("Patient_JohnDoe.json"); - patient = patient.toBuilder().gender(AdministrativeGender.MALE).build(); + patient = patient.toBuilder() + .meta(Meta.builder() + .security(security) + .tag(tag) + .profile(Canonical.of("http://ibm.com/fhir/profile/Profile/" + strUniqueTag)) + .build()) + .gender(AdministrativeGender.MALE).build(); Entity entity = Entity.entity(patient, FHIRMediaType.APPLICATION_FHIR_JSON); @@ -284,6 +299,76 @@ public void testSearchPatientWithIdThatDoesntExist() { assertTrue(bundle.getEntry().size() == 0); } + @Test(groups = { "server-search-chain" }, dependsOnMethods = {"testCreatePatient" , "testCreateProcedure"}) + public void testSearchPatientWithProfile() { + WebTarget target = getWebTarget(); + Response response = + target.path("Procedure").queryParam("subject:Patient._profile", "http://ibm.com/fhir/profile/Profile/" + strUniqueTag) + .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .get(); + assertResponse(response, Response.Status.OK.getStatusCode()); + Bundle bundle = response.readEntity(Bundle.class); + + assertNotNull(bundle); + assertTrue(bundle.getEntry().size() >= 1); + } + + @Test(groups = { "server-search-chain" }, dependsOnMethods = {"testCreatePatient" , "testCreateProcedure"}) + public void testSearchPatientWithTag() { + WebTarget target = getWebTarget(); + Response response = + target.path("Procedure").queryParam("subject:Patient._tag", "http://ibm.com/fhir/tag/" + strUniqueTag + "|" + strUniqueTag) + .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .get(); + assertResponse(response, Response.Status.OK.getStatusCode()); + Bundle bundle = response.readEntity(Bundle.class); + + assertNotNull(bundle); + assertTrue(bundle.getEntry().size() >= 1); + } + + @Test(groups = { "server-search-chain" }, dependsOnMethods = {"testCreatePatient" , "testCreateProcedure"}) + public void testSearchPatientWithTagSystemOnly() { + WebTarget target = getWebTarget(); + Response response = + target.path("Procedure").queryParam("subject:Patient._tag", "http://ibm.com/fhir/tag/" + strUniqueTag + "|") + .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .get(); + assertResponse(response, Response.Status.OK.getStatusCode()); + Bundle bundle = response.readEntity(Bundle.class); + + assertNotNull(bundle); + assertTrue(bundle.getEntry().size() >= 1); + } + + @Test(groups = { "server-search-chain" }, dependsOnMethods = {"testCreatePatient" , "testCreateProcedure"}) + public void testSearchPatientWithSecurity() { + WebTarget target = getWebTarget(); + Response response = + target.path("Procedure").queryParam("subject:Patient._security", "http://ibm.com/fhir/security/" + strUniqueTag + "|" + strUniqueTag) + .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .get(); + assertResponse(response, Response.Status.OK.getStatusCode()); + Bundle bundle = response.readEntity(Bundle.class); + + assertNotNull(bundle); + assertTrue(bundle.getEntry().size() >= 1); + } + + @Test(groups = { "server-search-chain" }, dependsOnMethods = {"testCreatePatient" , "testCreateProcedure"}) + public void testSearchPatientWithSecuritySystemOnly() { + WebTarget target = getWebTarget(); + Response response = + target.path("Procedure").queryParam("subject:Patient._security", "http://ibm.com/fhir/security/" + strUniqueTag + "|") + .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .get(); + assertResponse(response, Response.Status.OK.getStatusCode()); + Bundle bundle = response.readEntity(Bundle.class); + + assertNotNull(bundle); + assertTrue(bundle.getEntry().size() >= 1); + } + @Test(groups = { "server-search-chain" }, dependsOnMethods = {"testCreatePatient" , "testCreateEncounter"}) public void testVersionedReferenceSearchPatientWithId() { WebTarget target = getWebTarget(); diff --git a/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchReverseChainTest.java b/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchReverseChainTest.java index b964186fa03..24cbcf90d46 100644 --- a/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchReverseChainTest.java +++ b/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchReverseChainTest.java @@ -180,7 +180,13 @@ public void testCreatePatient1() throws Exception { .given(of("1" + tag)) .build()) .meta(Meta.builder() + .profile(Canonical.of("http://ibm.com/fhir/Profile/" + tag)) .tag(Coding.builder() + .system(Uri.of("http://ibm.com/fhir/tag/" + tag)) + .code(Code.of(tag)) + .build()) + .security(Coding.builder() + .system(Uri.of("http://ibm.com/fhir/security/" + tag)) .code(Code.of(tag)) .build()) .build()) @@ -1358,4 +1364,84 @@ public void testSearchCanonicalMultipleReverseChainSingleResult() { assertEquals(bundle.getEntry().get(0).getResource().getId(), libraryId); } + @Test(groups = { "server-search-reverse-chain" }, dependsOnMethods = {"testCreatePatient1"}) + public void testSearchSingleReverseChainWithTagParm() { + WebTarget target = getWebTarget(); + Response response = + target.path("Organization") + .queryParam("_has:Patient:organization:_tag", "http://ibm.com/fhir/tag/" + tag + "|" + tag) + .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .get(); + assertResponse(response, Response.Status.OK.getStatusCode()); + Bundle bundle = response.readEntity(Bundle.class); + + assertNotNull(bundle); + assertEquals(bundle.getEntry().size(), 1); + assertEquals(bundle.getEntry().get(0).getResource().getId(), organization1Id); + } + + @Test(groups = { "server-search-reverse-chain" }, dependsOnMethods = {"testCreatePatient1"}) + public void testSearchSingleReverseChainWithTagParmSystemOnly() { + WebTarget target = getWebTarget(); + Response response = + target.path("Organization") + .queryParam("_has:Patient:organization:_tag", "http://ibm.com/fhir/tag/" + tag + "|") + .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .get(); + assertResponse(response, Response.Status.OK.getStatusCode()); + Bundle bundle = response.readEntity(Bundle.class); + + assertNotNull(bundle); + assertEquals(bundle.getEntry().size(), 1); + assertEquals(bundle.getEntry().get(0).getResource().getId(), organization1Id); + } + + @Test(groups = { "server-search-reverse-chain" }, dependsOnMethods = {"testCreatePatient1"}) + public void testSearchSingleReverseChainWithSecurityParm() { + WebTarget target = getWebTarget(); + Response response = + target.path("Organization") + .queryParam("_has:Patient:organization:_security", "http://ibm.com/fhir/security/" + tag + "|" + tag) + .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .get(); + assertResponse(response, Response.Status.OK.getStatusCode()); + Bundle bundle = response.readEntity(Bundle.class); + + assertNotNull(bundle); + assertEquals(bundle.getEntry().size(), 1); + assertEquals(bundle.getEntry().get(0).getResource().getId(), organization1Id); + } + + @Test(groups = { "server-search-reverse-chain" }, dependsOnMethods = {"testCreatePatient1"}) + public void testSearchSingleReverseChainWithSecurityParmSystemOnly() { + WebTarget target = getWebTarget(); + Response response = + target.path("Organization") + .queryParam("_has:Patient:organization:_security", "http://ibm.com/fhir/security/" + tag + "|") + .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .get(); + assertResponse(response, Response.Status.OK.getStatusCode()); + Bundle bundle = response.readEntity(Bundle.class); + + assertNotNull(bundle); + assertEquals(bundle.getEntry().size(), 1); + assertEquals(bundle.getEntry().get(0).getResource().getId(), organization1Id); + } + + @Test(groups = { "server-search-reverse-chain" }, dependsOnMethods = {"testCreatePatient1"}) + public void testSearchSingleReverseChainWithProfileParm() { + WebTarget target = getWebTarget(); + Response response = + target.path("Organization") + .queryParam("_has:Patient:organization:_profile", "http://ibm.com/fhir/Profile/" + tag) + .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .get(); + assertResponse(response, Response.Status.OK.getStatusCode()); + Bundle bundle = response.readEntity(Bundle.class); + + assertNotNull(bundle); + assertEquals(bundle.getEntry().size(), 1); + assertEquals(bundle.getEntry().get(0).getResource().getId(), organization1Id); + } + } \ No newline at end of file