Skip to content

Commit

Permalink
$package fallback to tx server for ValueSets (#639)
Browse files Browse the repository at this point in the history
* [APHL-1326] - fallback to Terminology Server when packaging VSets

---------

Co-authored-by: taha.attari@smilecdr.com <taha.attari@smilecdr.com>
  • Loading branch information
TahaAttari and taha.attari@smilecdr.com authored Mar 7, 2025
1 parent c82c005 commit 286fd4d
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.opencds.cqf.fhir.utility.dstu3.Parameters.parameters;
import static org.opencds.cqf.fhir.utility.dstu3.Parameters.part;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
Expand All @@ -25,6 +28,7 @@
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
Expand All @@ -44,14 +48,22 @@
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.opencds.cqf.fhir.api.Repository;
import org.opencds.cqf.fhir.cr.visitor.IValueSetExpansionCache;
import org.opencds.cqf.fhir.cr.visitor.PackageVisitor;
import org.opencds.cqf.fhir.utility.Constants;
import org.opencds.cqf.fhir.utility.adapter.IAdapterFactory;
import org.opencds.cqf.fhir.utility.adapter.IEndpointAdapter;
import org.opencds.cqf.fhir.utility.adapter.ILibraryAdapter;
import org.opencds.cqf.fhir.utility.adapter.IParametersAdapter;
import org.opencds.cqf.fhir.utility.adapter.IValueSetAdapter;
import org.opencds.cqf.fhir.utility.adapter.dstu3.AdapterFactory;
import org.opencds.cqf.fhir.utility.adapter.dstu3.LibraryAdapter;
import org.opencds.cqf.fhir.utility.adapter.dstu3.ValueSetAdapter;
import org.opencds.cqf.fhir.utility.client.TerminologyServerClient;
import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository;

class PackageVisitorTests {
Expand Down Expand Up @@ -434,7 +446,7 @@ void packageVisitorShouldUseExpansionCacheIfProvided() {
.copy();
var libraryAdapter = new AdapterFactory().createLibrary(library);
var mockCache = Mockito.mock(IValueSetExpansionCache.class);
var packageVisitor = new PackageVisitor(repo, mockCache);
var packageVisitor = new PackageVisitor(repo, null, mockCache);

var canonical1 = "http://cts.nlm.nih.gov/fhir/ValueSet/123-this-will-be-routine|20210526";
var mockValueSetAdapter1 = Mockito.mock(ValueSetAdapter.class);
Expand All @@ -461,4 +473,54 @@ void packageVisitorShouldUseExpansionCacheIfProvided() {
verify(mockCache, times(2)).addToCache(any(), isNull());
verify(mockCache, times(1)).getExpansionParametersHash(any(LibraryAdapter.class));
}

@Test
void fallback_to_tx_server_if_valueset_missing_locally() {
final var leafOid = "2.16.840.1.113762.1.4.1146.6";
final var authoritativeSource = "http://cts.nlm.nih.gov/fhir/";
var bundle = (Bundle) jsonParser.parseResource(
ReleaseVisitorTests.class.getResourceAsStream("Bundle-ersd-small-active.json"));
Predicate<BundleEntryComponent> leafFinder = e -> e.getResource().getResourceType() == ResourceType.ValueSet
&& ((ValueSet) e.getResource()).getUrl().contains(leafOid);
// remove leaf from bundle
var leafEntry = bundle.getEntry().stream().filter(leafFinder).findFirst();
var missingVset = leafEntry.map(e -> (ValueSet) e.getResource()).get();
bundle.getEntry().remove(leafEntry.get());

repo.transaction(bundle);
var library = repo.read(Library.class, new IdType("Library/SpecificationLibrary"))
.copy();
var endpoint = createEndpoint(authoritativeSource);

var clientMock = mock(TerminologyServerClient.class, new ReturnsDeepStubs());
// expect the Tx Server to provide the missing ValueSet
when(clientMock.getResource(any(IEndpointAdapter.class), any(), any())).thenReturn(Optional.of(missingVset));
doAnswer(new Answer<ValueSet>() {
@Override
public ValueSet answer(InvocationOnMock invocation) throws Throwable {
return new ValueSet(); // Return a new instance of ValueSet
}
})
.when(clientMock)
.expand(any(IValueSetAdapter.class), any(IEndpointAdapter.class), any(IParametersAdapter.class));
var packageVisitor = new PackageVisitor(repo, clientMock);
var libraryAdapter = new AdapterFactory().createLibrary(library);
var params = parameters(part("terminologyEndpoint", (org.hl7.fhir.dstu3.model.Endpoint) endpoint.get()));
// create package
var packagedBundle = (Bundle) libraryAdapter.accept(packageVisitor, params);
var containsVset = packagedBundle.getEntry().stream().anyMatch(leafFinder);
// check for ValueSet
assertTrue(containsVset);
}

private IEndpointAdapter createEndpoint(String authoritativeSource) {
var factory = IAdapterFactory.forFhirVersion(FhirVersionEnum.DSTU3);
var endpoint = factory.createEndpoint(new org.hl7.fhir.dstu3.model.Endpoint());
endpoint.setAddress(authoritativeSource);
endpoint.addExtension(new org.hl7.fhir.dstu3.model.Extension(
Constants.VSAC_USERNAME, new org.hl7.fhir.dstu3.model.StringType("username")));
endpoint.addExtension(new org.hl7.fhir.dstu3.model.Extension(
Constants.APIKEY, new org.hl7.fhir.dstu3.model.StringType("password")));
return endpoint;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.opencds.cqf.fhir.utility.r4.Parameters.parameters;
import static org.opencds.cqf.fhir.utility.r4.Parameters.part;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
Expand All @@ -26,6 +29,7 @@
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Predicate;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.Bundle.BundleType;
Expand All @@ -46,14 +50,22 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.Mockito;
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.opencds.cqf.fhir.api.Repository;
import org.opencds.cqf.fhir.cr.visitor.IValueSetExpansionCache;
import org.opencds.cqf.fhir.cr.visitor.PackageVisitor;
import org.opencds.cqf.fhir.utility.Constants;
import org.opencds.cqf.fhir.utility.adapter.IAdapterFactory;
import org.opencds.cqf.fhir.utility.adapter.IEndpointAdapter;
import org.opencds.cqf.fhir.utility.adapter.ILibraryAdapter;
import org.opencds.cqf.fhir.utility.adapter.IParametersAdapter;
import org.opencds.cqf.fhir.utility.adapter.IValueSetAdapter;
import org.opencds.cqf.fhir.utility.adapter.r4.AdapterFactory;
import org.opencds.cqf.fhir.utility.adapter.r4.LibraryAdapter;
import org.opencds.cqf.fhir.utility.adapter.r4.ValueSetAdapter;
import org.opencds.cqf.fhir.utility.client.TerminologyServerClient;
import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository;

class PackageVisitorTests {
Expand Down Expand Up @@ -393,7 +405,7 @@ void packageVisitorShouldUseExpansionCacheIfProvided() {
.copy();
var libraryAdapter = new AdapterFactory().createLibrary(library);
var mockCache = Mockito.mock(IValueSetExpansionCache.class);
var packageVisitor = new PackageVisitor(repo, mockCache);
var packageVisitor = new PackageVisitor(repo, null, mockCache);

var canonical1 = "http://cts.nlm.nih.gov/fhir/ValueSet/123-this-will-be-routine|20210526";
var mockValueSetAdapter1 = Mockito.mock(ValueSetAdapter.class);
Expand All @@ -420,4 +432,54 @@ void packageVisitorShouldUseExpansionCacheIfProvided() {
verify(mockCache, times(2)).addToCache(any(), isNull());
verify(mockCache, times(1)).getExpansionParametersHash(any(LibraryAdapter.class));
}

@Test
void fallback_to_tx_server_if_valueset_missing_locally() {
final var leafOid = "2.16.840.1.113762.1.4.1146.6";
final var authoritativeSource = "http://cts.nlm.nih.gov/fhir/";
var bundle = (Bundle) jsonParser.parseResource(
ReleaseVisitorTests.class.getResourceAsStream("Bundle-ersd-small-active.json"));
Predicate<BundleEntryComponent> leafFinder = e -> e.getResource().getResourceType() == ResourceType.ValueSet
&& ((ValueSet) e.getResource()).getUrl().contains(leafOid);
// remove leaf from bundle
var leafEntry = bundle.getEntry().stream().filter(leafFinder).findFirst();
var missingVset = leafEntry.map(e -> (ValueSet) e.getResource()).get();
bundle.getEntry().remove(leafEntry.get());

repo.transaction(bundle);
var library = repo.read(Library.class, new IdType("Library/SpecificationLibrary"))
.copy();
var endpoint = createEndpoint(authoritativeSource);

var clientMock = mock(TerminologyServerClient.class, new ReturnsDeepStubs());
// expect the Tx Server to provide the missing ValueSet
when(clientMock.getResource(any(IEndpointAdapter.class), any(), any())).thenReturn(Optional.of(missingVset));
doAnswer(new Answer<ValueSet>() {
@Override
public ValueSet answer(InvocationOnMock invocation) throws Throwable {
return new ValueSet(); // Return a new instance of ValueSet
}
})
.when(clientMock)
.expand(any(IValueSetAdapter.class), any(IEndpointAdapter.class), any(IParametersAdapter.class));
var packageVisitor = new PackageVisitor(repo, clientMock);
var libraryAdapter = new AdapterFactory().createLibrary(library);
var params = parameters(part("terminologyEndpoint", (org.hl7.fhir.r4.model.Endpoint) endpoint.get()));
// create package
var packagedBundle = (Bundle) libraryAdapter.accept(packageVisitor, params);
var containsVset = packagedBundle.getEntry().stream().anyMatch(leafFinder);
// check for ValueSet
assertTrue(containsVset);
}

private IEndpointAdapter createEndpoint(String authoritativeSource) {
var factory = IAdapterFactory.forFhirVersion(FhirVersionEnum.R4);
var endpoint = factory.createEndpoint(new org.hl7.fhir.r4.model.Endpoint());
endpoint.setAddress(authoritativeSource);
endpoint.addExtension(new org.hl7.fhir.r4.model.Extension(
Constants.VSAC_USERNAME, new org.hl7.fhir.r4.model.StringType("username")));
endpoint.addExtension(new org.hl7.fhir.r4.model.Extension(
Constants.APIKEY, new org.hl7.fhir.r4.model.StringType("password")));
return endpoint;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.opencds.cqf.fhir.utility.r5.Parameters.parameters;
import static org.opencds.cqf.fhir.utility.r5.Parameters.part;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
Expand All @@ -25,6 +28,7 @@
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
Expand All @@ -44,14 +48,22 @@
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.opencds.cqf.fhir.api.Repository;
import org.opencds.cqf.fhir.cr.visitor.IValueSetExpansionCache;
import org.opencds.cqf.fhir.cr.visitor.PackageVisitor;
import org.opencds.cqf.fhir.utility.Constants;
import org.opencds.cqf.fhir.utility.adapter.IAdapterFactory;
import org.opencds.cqf.fhir.utility.adapter.IEndpointAdapter;
import org.opencds.cqf.fhir.utility.adapter.ILibraryAdapter;
import org.opencds.cqf.fhir.utility.adapter.IParametersAdapter;
import org.opencds.cqf.fhir.utility.adapter.IValueSetAdapter;
import org.opencds.cqf.fhir.utility.adapter.r5.AdapterFactory;
import org.opencds.cqf.fhir.utility.adapter.r5.LibraryAdapter;
import org.opencds.cqf.fhir.utility.adapter.r5.ValueSetAdapter;
import org.opencds.cqf.fhir.utility.client.TerminologyServerClient;
import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository;

class PackageVisitorTests {
Expand Down Expand Up @@ -435,7 +447,7 @@ void packageVisitorShouldUseExpansionCacheIfProvided() {
.copy();
var libraryAdapter = new AdapterFactory().createLibrary(library);
var mockCache = Mockito.mock(IValueSetExpansionCache.class);
var packageVisitor = new PackageVisitor(repo, mockCache);
var packageVisitor = new PackageVisitor(repo, null, mockCache);

var canonical1 = "http://cts.nlm.nih.gov/fhir/ValueSet/123-this-will-be-routine|20210526";
var mockValueSetAdapter1 = Mockito.mock(ValueSetAdapter.class);
Expand All @@ -462,4 +474,54 @@ void packageVisitorShouldUseExpansionCacheIfProvided() {
verify(mockCache, times(2)).addToCache(any(), isNull());
verify(mockCache, times(1)).getExpansionParametersHash(any(LibraryAdapter.class));
}

@Test
void fallback_to_tx_server_if_valueset_missing_locally() {
final var leafOid = "2.16.840.1.113762.1.4.1146.6";
final var authoritativeSource = "http://cts.nlm.nih.gov/fhir/";
var bundle = (Bundle) jsonParser.parseResource(
ReleaseVisitorTests.class.getResourceAsStream("Bundle-ersd-small-active.json"));
Predicate<BundleEntryComponent> leafFinder = e -> e.getResource().getResourceType() == ResourceType.ValueSet
&& ((ValueSet) e.getResource()).getUrl().contains(leafOid);
// remove leaf from bundle
var leafEntry = bundle.getEntry().stream().filter(leafFinder).findFirst();
var missingVset = leafEntry.map(e -> (ValueSet) e.getResource()).get();
bundle.getEntry().remove(leafEntry.get());

repo.transaction(bundle);
var library = repo.read(Library.class, new IdType("Library/SpecificationLibrary"))
.copy();
var endpoint = createEndpoint(authoritativeSource);

var clientMock = mock(TerminologyServerClient.class, new ReturnsDeepStubs());
// expect the Tx Server to provide the missing ValueSet
when(clientMock.getResource(any(IEndpointAdapter.class), any(), any())).thenReturn(Optional.of(missingVset));
doAnswer(new Answer<ValueSet>() {
@Override
public ValueSet answer(InvocationOnMock invocation) throws Throwable {
return new ValueSet(); // Return a new instance of ValueSet
}
})
.when(clientMock)
.expand(any(IValueSetAdapter.class), any(IEndpointAdapter.class), any(IParametersAdapter.class));
var packageVisitor = new PackageVisitor(repo, clientMock);
var libraryAdapter = new AdapterFactory().createLibrary(library);
var params = parameters(part("terminologyEndpoint", (org.hl7.fhir.r5.model.Endpoint) endpoint.get()));
// create package
var packagedBundle = (Bundle) libraryAdapter.accept(packageVisitor, params);
var containsVset = packagedBundle.getEntry().stream().anyMatch(leafFinder);
// check for ValueSet
assertTrue(containsVset);
}

private IEndpointAdapter createEndpoint(String authoritativeSource) {
var factory = IAdapterFactory.forFhirVersion(FhirVersionEnum.R5);
var endpoint = factory.createEndpoint(new org.hl7.fhir.r5.model.Endpoint());
endpoint.setAddress(authoritativeSource);
endpoint.addExtension(new org.hl7.fhir.r5.model.Extension(
Constants.VSAC_USERNAME, new org.hl7.fhir.r5.model.StringType("username")));
endpoint.addExtension(new org.hl7.fhir.r5.model.Extension(
Constants.APIKEY, new org.hl7.fhir.r5.model.StringType("password")));
return endpoint;
}
}
Loading

0 comments on commit 286fd4d

Please sign in to comment.