Skip to content

Commit

Permalink
Refactor Gateway list entries mode to support Search by Tag
Browse files Browse the repository at this point in the history
- Add support for list mode entries fetch with Search by _ID tag
- Unit testing
  • Loading branch information
ndegwamartin committed Jul 11, 2023
1 parent b5ddc31 commit bf02fc7
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import lombok.Getter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
Expand All @@ -45,6 +47,7 @@
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.ListResource;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -100,7 +103,8 @@ public RequestMutation getRequestMutation(RequestDetailsReader requestDetailsRea
// This does not bar access to anyone who uses their own sync tags to circumvent
// the filter. The aim of this feature based on scoping was to pre-filter the data for the user
if (isSyncUrl(requestDetailsReader)) {
// This prevents access to a user who has no location/organisation/team assigned to them
// This prevents access to a user who has no location/organisation/team assigned to them by
// assigning a non-existent search tag param and value
if (locationIds.size() == 0 && careTeamIds.size() == 0 && organizationIds.size() == 0) {
locationIds.add(
"CR1bAeGgaYqIpsNkG0iidfE5WVb5BJV1yltmL4YFp3o6mxj3iJPhKh4k9ROhlyZveFC8298lYzft8SIy8yMNLl5GVWQXNRr1sSeBkP2McfFZjbMYyrxlNFOJgqvtccDKKYSwBiLHq2By5tRupHcmpIIghV7Hp39KgF4iBDNqIGMKhgOIieQwt5BRih5FgnwdHrdlK9ix");
Expand Down Expand Up @@ -149,58 +153,104 @@ private List<String> addSyncFilters(
return paramValues;
}

/** NOTE: Always return a null whenever you want to skip post-processing */
@Override
public String postProcess(RequestDetailsReader request, HttpResponse response)
throws IOException {

String resultContent = null;
String listMode = request.getHeader(Constants.FHIR_GATEWAY_MODE);
Bundle resultContentBundle = null;
String gatewayMode = request.getHeader(Constants.FHIR_GATEWAY_MODE);

switch (listMode) {
case Constants.LIST_ENTRIES:
resultContent = postProcessModeListEntries(response);
default:
break;
if (!TextUtils.isBlank(gatewayMode)) {

resultContent = new BasicResponseHandler().handleResponse(response);
IBaseResource responseResource = fhirR4JsonParser.parseResource(resultContent);

switch (gatewayMode) {
case Constants.LIST_ENTRIES:
resultContentBundle = postProcessModeListEntries(responseResource);
break;
default:
break;
}

if (resultContentBundle != null)
resultContent = fhirR4JsonParser.encodeResourceToString(resultContentBundle);
}

return resultContent;
}

@NotNull
private static Bundle processListEntriesGatewayModeByListResource(
ListResource responseListResource) {
Bundle requestBundle = new Bundle();
requestBundle.setType(Bundle.BundleType.BATCH);

for (ListResource.ListEntryComponent listEntryComponent : responseListResource.getEntry()) {
requestBundle.addEntry(
createBundleEntryComponent(
Bundle.HTTPVerb.GET, listEntryComponent.getItem().getReference(), null));
}
return requestBundle;
}

private Bundle processListEntriesGatewayModeByBundle(IBaseResource responseResource) {
Bundle requestBundle = new Bundle();
requestBundle.setType(Bundle.BundleType.BATCH);

List<Bundle.BundleEntryComponent> bundleEntryComponentList =
((Bundle) responseResource)
.getEntry().stream()
.filter(it -> it.getResource() instanceof ListResource)
.flatMap(
bundleEntryComponent ->
((ListResource) bundleEntryComponent.getResource()).getEntry().stream())
.map(
listEntryComponent ->
createBundleEntryComponent(
Bundle.HTTPVerb.GET, listEntryComponent.getItem().getReference(), null))
.collect(Collectors.toList());

return requestBundle.setEntry(bundleEntryComponentList);
}

@NotNull
private static Bundle.BundleEntryComponent createBundleEntryComponent(
Bundle.HTTPVerb method, String requestPath, @Nullable String condition) {

Bundle.BundleEntryComponent bundleEntryComponent = new Bundle.BundleEntryComponent();
bundleEntryComponent.setRequest(
new Bundle.BundleEntryRequestComponent()
.setMethod(method)
.setUrl(requestPath)
.setIfMatch(condition));

return bundleEntryComponent;
}

/**
* Generates a Bundle result from making a batch search request with the contained entries in the
* List as parameters
*
* @param response HTTPResponse
* @param responseResource FHIR Resource result returned byt the HTTPResponse
* @return String content of the result Bundle
*/
private String postProcessModeListEntries(HttpResponse response) throws IOException {
private Bundle postProcessModeListEntries(IBaseResource responseResource) {

String resultContent = new BasicResponseHandler().handleResponse(response);
IBaseResource responseResource = fhirR4JsonParser.parseResource(resultContent);
Bundle requestBundle = null;

if (responseResource instanceof ListResource && ((ListResource) responseResource).hasEntry()) {

Bundle requestBundle = new Bundle();
requestBundle.setType(Bundle.BundleType.BATCH);
Bundle.BundleEntryComponent bundleEntryComponent;

for (ListResource.ListEntryComponent listEntryComponent :
((ListResource) responseResource).getEntry()) {
requestBundle = processListEntriesGatewayModeByListResource((ListResource) responseResource);

bundleEntryComponent = new Bundle.BundleEntryComponent();
bundleEntryComponent.setRequest(
new Bundle.BundleEntryRequestComponent()
.setMethod(Bundle.HTTPVerb.GET)
.setUrl(listEntryComponent.getItem().getReference()));
} else if (responseResource instanceof Bundle) {

requestBundle.addEntry(bundleEntryComponent);
}

Bundle responseBundle =
createFhirClientForR4().transaction().withBundle(requestBundle).execute();

resultContent = fhirR4JsonParser.encodeResourceToString(responseBundle);
requestBundle = processListEntriesGatewayModeByBundle(responseResource);
}
return resultContent;

return createFhirClientForR4().transaction().withBundle(requestBundle).execute();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.ListResource;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand Down Expand Up @@ -417,6 +418,81 @@ public void testPostProcessWithoutListModeHeaderShouldShouldReturnNull() throws
Assert.assertNull(resultContent);
}

@Test
public void testPostProcessWithListModeHeaderSearchByTagShouldFetchListEntriesBundle()
throws IOException {
locationIds.add("Location-1");
testInstance = Mockito.spy(createOpenSRPSyncAccessDecisionTestInstance());

FhirContext fhirR4Context = mock(FhirContext.class);
IGenericClient iGenericClient = mock(IGenericClient.class);
ITransaction iTransaction = mock(ITransaction.class);
ITransactionTyped<Bundle> iClientExecutable = mock(ITransactionTyped.class);

Mockito.when(fhirR4Context.newRestfulGenericClient(System.getenv(PROXY_TO_ENV)))
.thenReturn(iGenericClient);
Mockito.when(iGenericClient.transaction()).thenReturn(iTransaction);
Mockito.when(iTransaction.withBundle(any(Bundle.class))).thenReturn(iClientExecutable);

Bundle resultBundle = new Bundle();
resultBundle.setType(Bundle.BundleType.BATCHRESPONSE);
resultBundle.setId("bundle-result-id");

Mockito.when(iClientExecutable.execute()).thenReturn(resultBundle);

ArgumentCaptor<Bundle> bundleArgumentCaptor = ArgumentCaptor.forClass(Bundle.class);

testInstance.setFhirR4Context(fhirR4Context);

RequestDetailsReader requestDetailsSpy = Mockito.mock(RequestDetailsReader.class);

Mockito.when(requestDetailsSpy.getHeader(OpenSRPSyncAccessDecision.Constants.FHIR_GATEWAY_MODE))
.thenReturn(OpenSRPSyncAccessDecision.Constants.LIST_ENTRIES);

URL listUrl = Resources.getResource("test_list_resource.json");
String testListJson = Resources.toString(listUrl, StandardCharsets.UTF_8);

FhirContext realFhirContext = FhirContext.forR4();
ListResource listResource =
(ListResource) realFhirContext.newJsonParser().parseResource(testListJson);

Bundle bundle = new Bundle();
Bundle.BundleEntryComponent bundleEntryComponent = new Bundle.BundleEntryComponent();
bundleEntryComponent.setResource(listResource);
bundle.setType(Bundle.BundleType.BATCHRESPONSE);
bundle.setEntry(Arrays.asList(bundleEntryComponent));

HttpResponse fhirResponseMock = Mockito.mock(HttpResponse.class, Answers.RETURNS_DEEP_STUBS);

TestUtil.setUpFhirResponseMock(
fhirResponseMock, realFhirContext.newJsonParser().encodeResourceToString(bundle));

String resultContent = testInstance.postProcess(requestDetailsSpy, fhirResponseMock);

Mockito.verify(iTransaction).withBundle(bundleArgumentCaptor.capture());
Bundle requestBundle = bundleArgumentCaptor.getValue();

// Verify modified request to the server
Assert.assertNotNull(requestBundle);
Assert.assertEquals(Bundle.BundleType.BATCH, requestBundle.getType());
List<Bundle.BundleEntryComponent> requestBundleEntries = requestBundle.getEntry();
Assert.assertEquals(2, requestBundleEntries.size());

Assert.assertEquals(Bundle.HTTPVerb.GET, requestBundleEntries.get(0).getRequest().getMethod());
Assert.assertEquals(
"Group/proxy-list-entry-id-1", requestBundleEntries.get(0).getRequest().getUrl());

Assert.assertEquals(Bundle.HTTPVerb.GET, requestBundleEntries.get(1).getRequest().getMethod());
Assert.assertEquals(
"Group/proxy-list-entry-id-2", requestBundleEntries.get(1).getRequest().getUrl());

// Verify returned result content from the server request
Assert.assertNotNull(resultContent);
Assert.assertEquals(
"{\"resourceType\":\"Bundle\",\"id\":\"bundle-result-id\",\"type\":\"batch-response\"}",
resultContent);
}

private OpenSRPSyncAccessDecision createOpenSRPSyncAccessDecisionTestInstance() {
OpenSRPSyncAccessDecision accessDecision =
new OpenSRPSyncAccessDecision(
Expand Down

0 comments on commit bf02fc7

Please sign in to comment.