From 9a1acfc65fa5256456f79592475c8138fa852682 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Thu, 21 Nov 2024 20:29:35 +0300 Subject: [PATCH] =?UTF-8?q?REL=20pagination=20unit=20tests=20=E2=9C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gateway/plugins/SyncAccessDecision.java | 21 +-- .../fhir/gateway/plugins/Utils.java | 1 + .../plugins/PermissionAccessCheckerTest.java | 12 ++ .../plugins/SyncAccessDecisionTest.java | 147 ++++++++++++++++++ 4 files changed, 172 insertions(+), 9 deletions(-) diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java index 1839452c..0f152ed6 100755 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecision.java @@ -186,7 +186,7 @@ private List getRelChunkStrategyIds( int endIndex = Math.min( - startIndex + SyncAccessDecisionConstants.REL_LOCATION_CHUNK_SIZE, + startIndex + getRelLocationChunkSize(), syncStrategyIdsMap .get(Constants.SyncStrategy.RELATED_ENTITY_LOCATION) .size()); @@ -283,18 +283,21 @@ public String postProcess(RequestDetailsReader request, HttpResponse response) return resultContent; } - // int count = getQueryParamValue("_count", request); - private String getQueryParamValue( String key, RequestDetailsReader requestDetailsReader, String defaultValue) { - String[] countRaw = requestDetailsReader.getParameters().get(key); + String[] countRaw = requestDetailsReader.getParameters().getOrDefault(key, new String[] {}); return countRaw.length > 0 ? countRaw[0] : defaultValue; } private int getQueryParamIntValue( String key, RequestDetailsReader requestDetailsReader, int defaultValue) { - String[] countRaw = requestDetailsReader.getParameters().getOrDefault(key, new String[] {}); - return countRaw.length > 0 ? Integer.parseInt(countRaw[0]) : defaultValue; + return Integer.parseInt( + getQueryParamValue(key, requestDetailsReader, String.valueOf(defaultValue))); + } + + @VisibleForTesting + protected int getRelLocationChunkSize() { + return SyncAccessDecisionConstants.REL_LOCATION_CHUNK_SIZE; } private IBaseResource processRelatedEntityLocationSyncStrategy( @@ -313,7 +316,7 @@ private IBaseResource processRelatedEntityLocationSyncStrategy( if (syncStrategyIdsMap .getOrDefault(Constants.SyncStrategy.RELATED_ENTITY_LOCATION, List.of()) .size() - <= SyncAccessDecisionConstants.REL_LOCATION_CHUNK_SIZE) { + <= getRelLocationChunkSize()) { return responseResource; } @@ -333,12 +336,12 @@ private IBaseResource processRelatedEntityLocationSyncStrategy( + "?" + getRequestParametersString(request.getParameters()); - for (int startIndex = SyncAccessDecisionConstants.REL_LOCATION_CHUNK_SIZE; + for (int startIndex = getRelLocationChunkSize(); startIndex < syncStrategyIdsMap .get(Constants.SyncStrategy.RELATED_ENTITY_LOCATION) .size(); - startIndex += SyncAccessDecisionConstants.REL_LOCATION_CHUNK_SIZE) { + startIndex += getRelLocationChunkSize()) { List syncLocationIds = getRelChunkStrategyIds(startIndex, syncStrategyIdsMap); diff --git a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java index f3cd7d2f..915935e5 100644 --- a/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java +++ b/plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java @@ -292,6 +292,7 @@ public static String replaceAddQueryParamValue(String url, String paramName, Str Map queryMap = new HashMap<>(); for (String param : queryParams) { + if (param.isEmpty()) continue; String[] pair = param.split("="); queryMap.put(pair[0], pair.length > 1 ? pair[1] : ""); } diff --git a/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/PermissionAccessCheckerTest.java b/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/PermissionAccessCheckerTest.java index 284c2df3..2c688e27 100755 --- a/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/PermissionAccessCheckerTest.java +++ b/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/PermissionAccessCheckerTest.java @@ -451,4 +451,16 @@ public void testAccessDeniedWhenSingleRoleMissingForTypeBundleResources() throws assertThat(canAccess, equalTo(false)); } + + @Test + public void testGenerateSyncStrategyIdsCacheKey() { + String testUserId = "my-test-user-id"; + Map strategyIdMap = + Map.of(Constants.SyncStrategy.CARE_TEAM, new String[] {"id-1, id-2,id-3"}); + String cacheKey = + PermissionAccessChecker.generateSyncStrategyIdsCacheKey( + testUserId, Constants.SyncStrategy.CARE_TEAM, strategyIdMap); + + Assert.assertEquals(testUserId, cacheKey); + } } diff --git a/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecisionTest.java b/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecisionTest.java index c3c7ef60..dd322f89 100755 --- a/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecisionTest.java +++ b/plugins/src/test/java/org/smartregister/fhir/gateway/plugins/SyncAccessDecisionTest.java @@ -14,12 +14,15 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Date; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpResponse; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Encounter; import org.hl7.fhir.r4.model.ListResource; +import org.hl7.fhir.r4.model.Meta; import org.jetbrains.annotations.NotNull; import org.junit.After; import org.junit.Assert; @@ -39,10 +42,13 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.api.SearchStyleEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.gclient.IQuery; import ca.uhn.fhir.rest.gclient.ITransaction; import ca.uhn.fhir.rest.gclient.ITransactionTyped; +import ca.uhn.fhir.rest.gclient.IUntypedQuery; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; @RunWith(MockitoJUnitRunner.class) @@ -1071,4 +1077,145 @@ private SyncAccessDecision createSyncAccessDecisionTestInstance(String syncStrat accessDecision.setSkippedResourcesConfig(skippedDataFilterConfig); return accessDecision; } + + @Test + public void testPostProcessWithRELSyncStrategyWorksCorrectly() throws IOException { + + relatedEntityLocationIds.add("relocationid1"); + relatedEntityLocationIds.add("relocationid2"); + relatedEntityLocationIds.add("relocationid3"); + + testInstance = + Mockito.spy( + createSyncAccessDecisionTestInstance( + Constants.SyncStrategy.RELATED_ENTITY_LOCATION)); + + FhirContext fhirR4Context = mock(FhirContext.class); + IGenericClient iGenericClient = mock(IGenericClient.class); + ITransaction iTransaction = mock(ITransaction.class); + ITransactionTyped iClientExecutable = mock(ITransactionTyped.class); + + Mockito.when(iGenericClient.transaction()).thenReturn(iTransaction); + Mockito.when(iTransaction.withBundle(any(Bundle.class))).thenReturn(iClientExecutable); + Mockito.when(testInstance.getRelLocationChunkSize()).thenReturn(2); + + Bundle resultBundle = new Bundle(); + resultBundle.setType(Bundle.BundleType.BATCHRESPONSE); + resultBundle.setId("bundle-result-id"); + + Mockito.when(iClientExecutable.execute()).thenReturn(resultBundle); + + testInstance.setFhirR4Context(fhirR4Context); + + RequestDetailsReader requestDetailsSpy = Mockito.mock(RequestDetailsReader.class); + + Map params = new HashMap<>(); + params.put(Constants.PAGINATION_PAGE_SIZE, new String[] {"2"}); + params.put( + Constants.SYNC_LOCATIONS_SEARCH_PARAM, new String[] {"location1d", "location2d"}); + String fhirServerBase = "http://test:8080/fhir"; + + Mockito.when(requestDetailsSpy.getParameters()).thenReturn(params); + Mockito.when(requestDetailsSpy.getFhirServerBase()).thenReturn(fhirServerBase); + Mockito.when(requestDetailsSpy.getRequestPath()).thenReturn("Encounter"); + + StringBuilder queryParamStringBuilder = new StringBuilder(); + params.forEach( + (key, value) -> + queryParamStringBuilder + .append("&") + .append(key) + .append("=") + .append(String.join(",", value))); + + Mockito.when(requestDetailsSpy.getCompleteUrl()) + .thenReturn(fhirServerBase + "/Encounter?" + queryParamStringBuilder.toString()); + + Encounter encounter = new Encounter(); + encounter.setId("encounter-1"); + + Encounter encounter2 = new Encounter(); + encounter2.setId("encounter-2"); + + Bundle bundle = new Bundle(); + bundle.setTotal(3); + bundle.setType(Bundle.BundleType.SEARCHSET); + bundle.setMeta(new Meta().setLastUpdated(new Date())); + + Bundle.BundleEntryComponent bundleEntryComponent = new Bundle.BundleEntryComponent(); + bundleEntryComponent.setResource(encounter); + + Bundle.BundleEntryComponent bundleEntryComponent2 = new Bundle.BundleEntryComponent(); + bundleEntryComponent2.setResource(encounter); + + bundle.setEntry(List.of(bundleEntryComponent, bundleEntryComponent2)); + + HttpResponse fhirResponseMock = + Mockito.mock(HttpResponse.class, Answers.RETURNS_DEEP_STUBS); + + TestUtil.setUpFhirResponseMock( + fhirResponseMock, + FhirContext.forR4().newJsonParser().encodeResourceToString(bundle)); + + IUntypedQuery searchClient = Mockito.mock(IUntypedQuery.class); + IQuery searchClientIQuery = Mockito.mock(IQuery.class); + Mockito.when(iGenericClient.search()).thenReturn(searchClient); + + // First batch + Mockito.when(searchClient.byUrl("Encounter?&_count=2")).thenReturn(searchClientIQuery); + Mockito.when(searchClientIQuery.usingStyle(SearchStyleEnum.POST)) + .thenReturn(searchClientIQuery); + Mockito.when(searchClientIQuery.execute()).thenReturn(bundle); + + // Second batch + Bundle bundle2 = new Bundle(); + bundle2.setTotal(3); + bundle2.setType(Bundle.BundleType.SEARCHSET); + + Encounter encounter3 = new Encounter(); + encounter3.setId("encounter-3"); + + Bundle.BundleEntryComponent bundleEntryComponent3 = new Bundle.BundleEntryComponent(); + bundleEntryComponent3.setResource(encounter3); + + bundle2.setEntry(List.of(bundleEntryComponent3)); + + IQuery searchClientIQuery2 = Mockito.mock(IQuery.class); + Mockito.when( + searchClient.byUrl( + "Encounter?&_count=2&_tag=https://smartregister.org/related-entity-location-tag-id%7Crelocationid3,")) + .thenReturn(searchClientIQuery2); + Mockito.when(searchClientIQuery2.usingStyle(SearchStyleEnum.POST)) + .thenReturn(searchClientIQuery2); + Mockito.when(searchClientIQuery2.execute()).thenReturn(bundle2); + + testInstance.setFhirR4Client(iGenericClient); + testInstance.setFhirR4Context(FhirContext.forR4()); + String resultContent = testInstance.postProcess(requestDetailsSpy, fhirResponseMock); + + // Verify correct request to HAPI FHIR server + + ArgumentCaptor searchUrlCaptor = ArgumentCaptor.forClass(String.class); + Mockito.verify(searchClient).byUrl(searchUrlCaptor.capture()); + + String capturedSearchUrl = searchUrlCaptor.getValue(); + Assert.assertNotNull(capturedSearchUrl); + Assert.assertEquals( + "Encounter?&_count=2&_tag=https://smartregister.org/related-entity-location-tag-id%7Crelocationid3,", + capturedSearchUrl); + + // Verify returned result content from the server request, has pagination links + Assert.assertNotNull(resultContent); + Bundle resultContentBundle = + (Bundle) FhirContext.forR4Cached().newJsonParser().parseResource(resultContent); + Assert.assertNotNull(resultContentBundle.getMeta().getLastUpdated()); + Assert.assertEquals(2, resultContentBundle.getLink().size()); + Assert.assertEquals(2, resultContentBundle.getEntry().size()); + Assert.assertEquals( + "http://test:8080/fhir/Encounter?&_count=2&_syncLocations=location1d,location2d", + resultContentBundle.getLink(Bundle.LINK_SELF).getUrl()); + Assert.assertEquals( + "http://test:8080/fhir/Encounter?_count=2&_sLPage=2&_syncLocations=location1d,location2d", + resultContentBundle.getLink(Bundle.LINK_NEXT).getUrl()); + } }