Mb share jpa date tests (#3154)

* publish test-jar from hapi-fhir-storage

* Extract BaseDateSearchDaoTests up to hapi-fhir-storage.

Share a date search tests between JPA and Mongo.

* Use ITestDataBuilder for setup

* Move Mongo date cases into common set

* cleanup

* More cleanup

* naming
This commit is contained in:
michaelabuckley 2021-11-10 16:07:36 -05:00 committed by GitHub
parent 25c025cdee
commit 7d7865ab17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 242 additions and 145 deletions

View File

@ -156,6 +156,14 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-storage</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>

View File

@ -46,7 +46,6 @@ public class ExtendedLuceneIndexExtractor {
@NotNull
public ExtendedLuceneIndexData extract(ResourceIndexedSearchParams theNewParams) {
// wip mb this is testable now.
ExtendedLuceneIndexData retVal = new ExtendedLuceneIndexData(myContext);
theNewParams.myStringParams.forEach(nextParam ->
@ -59,7 +58,7 @@ public class ExtendedLuceneIndexExtractor {
// awkwardly, links are shared between different search params if they use the same path,
// so we re-build the linkage.
// WIP MB is this the right design? Or should we follow JPA and share these?
// WIPMB is this the right design? Or should we follow JPA and share these?
Map<String, List<String>> linkPathToParamName = new HashMap<>();
for (String nextParamName : theNewParams.getPopulatedResourceLinkParameters()) {
RuntimeSearchParam sp = myParams.get(nextParamName);

View File

@ -1,106 +0,0 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.conformance.DateSearchTestCase;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.util.FhirTerser;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Observation;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.support.TransactionCallback;
import java.util.List;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Run the tests defined in {@link DateSearchTestCase} in a DAO test as a @Nested suite.
*/
public abstract class BaseDAODateSearchTest {
private static final Logger ourLog = LoggerFactory.getLogger(BaseDAODateSearchTest.class);
/** Id of test Observation */
IIdType myObservationId;
/**
* Test for our date search operators.
* <p>
* Be careful - date searching is defined by set relations over intervals, not a simple number comparison.
* See http://hl7.org/fhir/search.html#prefix for details.
* <p>
*
* @param theResourceDate the date to use as Observation effective date
* @param theQuery the query parameter value including prefix (e.g. eq2020-01-01)
* @param theExpectedMatch true if tdheQuery should match theResourceDate.
*/
@ParameterizedTest
// use @CsvSource to debug individual cases.
//@CsvSource("2019-12-31T08:00:00,eq2020,false,inline,1")
@MethodSource("dateSearchCases")
public void testDateSearchMatching(String theResourceDate, String theQuery, Boolean theExpectedMatch, String theFileName, int theLineNumber) {
if (isShouldSkip(theResourceDate, theQuery)) {
return;
}
// setup
createObservationWithEffectiveDate(theResourceDate);
// run the query
boolean matched = isSearchMatch(theQuery);
String message =
"Expected " + theQuery + " to " +
(theExpectedMatch ? "" : "not ") + "match " + theResourceDate +
" (" + theFileName + ":" + theLineNumber + ")"; // wrap this in () so tools recognize the line reference.
assertEquals(theExpectedMatch, matched, message);
}
protected boolean isShouldSkip(String theResourceDate, String theQuery) {
return false;
}
// we need these from the test container
abstract protected FhirContext getMyFhirCtx();
abstract protected <T> T doInTransaction(TransactionCallback<T> daoMethodOutcomeTransactionCallback);
abstract protected <T extends IBaseResource> IFhirResourceDao<T> getObservationDao();
protected void createObservationWithEffectiveDate(String theResourceDate) {
IBaseResource obs = getMyFhirCtx().getResourceDefinition("Observation").newInstance();
FhirTerser fhirTerser = getMyFhirCtx().newTerser();
fhirTerser.addElement(obs, "effectiveDateTime", theResourceDate);
ourLog.info("obs {}", getMyFhirCtx().newJsonParser().encodeResourceToString(obs));
DaoMethodOutcome createOutcome = doInTransaction(s -> getObservationDao().create(obs));
myObservationId = createOutcome.getId();
}
/**
* Does the query string match the observation created during setup?
*/
protected boolean isSearchMatch(String theQuery) {
SearchParameterMap map = SearchParameterMap.newSynchronous();
map.add(Observation.SP_DATE, new DateParam(theQuery));
ourLog.info("Searching for observation {}", map);
IBundleProvider results = getObservationDao().search(map);
boolean matched = results.getAllResourceIds().contains(myObservationId.getIdPart());
return matched;
}
static List<Arguments> dateSearchCases() {
return DateSearchTestCase.ourCases.stream()
.map(DateSearchTestCase::toArguments)
.collect(Collectors.toList());
}
}

View File

@ -1,13 +1,11 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.BaseDAODateSearchTest;
import ca.uhn.fhir.jpa.dao.BaseDateSearchDaoTests;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
@ -5316,8 +5314,7 @@ public class FhirResourceDaoR4LegacySearchBuilderTest extends BaseJpaR4Test {
}
@Nested
public class DateSearchTests extends BaseDAODateSearchTest {
public class DateSearchTests extends BaseDateSearchDaoTests {
/**
* legacy builder didn't get the year/month date search fixes, so skip anything wider than a day.
*/
@ -5328,16 +5325,8 @@ public class FhirResourceDaoR4LegacySearchBuilderTest extends BaseJpaR4Test {
}
@Override
protected FhirContext getMyFhirCtx() {
return myFhirCtx;
}
@Override
protected <T> T doInTransaction(TransactionCallback<T> theCallback) {
return new TransactionTemplate(myTxManager).execute(theCallback);
}
@Override
protected IFhirResourceDao<Observation> getObservationDao() {
return myObservationDao;
protected Fixture getFixture() {
return new TestDataBuilderFixture(FhirResourceDaoR4LegacySearchBuilderTest.this, myObservationDao);
}
}

View File

@ -1,10 +1,13 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.config.TestR4WithLuceneDisabledConfig;
import ca.uhn.fhir.jpa.dao.BaseDAODateSearchTest;
import ca.uhn.fhir.jpa.dao.BaseDateSearchDaoTests;
import ca.uhn.fhir.jpa.dao.BaseJpaTest;
import ca.uhn.fhir.jpa.dao.DaoTestDataBuilder;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import org.hl7.fhir.r4.model.Observation;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.extension.ExtendWith;
@ -16,8 +19,6 @@ import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -33,6 +34,8 @@ public class FhirResourceDaoR4LuceneDisabledStandardQueries extends BaseJpaTest
@Autowired
@Qualifier("myObservationDaoR4")
IFhirResourceDao<Observation> myObservationDao;
@Autowired
protected DaoRegistry myDaoRegistry;
@Override
protected PlatformTransactionManager getTxManager() {
@ -45,22 +48,11 @@ public class FhirResourceDaoR4LuceneDisabledStandardQueries extends BaseJpaTest
}
@Nested
public class DateSearchTests extends BaseDAODateSearchTest {
public class DateSearchTests extends BaseDateSearchDaoTests {
@Override
protected FhirContext getMyFhirCtx() {
return myFhirCtx;
}
@Override
protected <T> T doInTransaction(TransactionCallback<T> theCallback) {
return new TransactionTemplate(myTxManager).execute(
theCallback
);
}
@Override
protected IFhirResourceDao<Observation> getObservationDao() {
return myObservationDao;
protected Fixture getFixture() {
DaoTestDataBuilder testDataBuilder = new DaoTestDataBuilder(myFhirCtx, myDaoRegistry, new SystemRequestDetails());
return new TestDataBuilderFixture<>(testDataBuilder, myObservationDao);
}
}

View File

@ -75,6 +75,13 @@
<artifactId>hapi-fhir-jpaserver-searchparam</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-test-utilities</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate.search</groupId>
<artifactId>hibernate-search-mapper-orm</artifactId>
@ -151,6 +158,18 @@
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>

View File

@ -0,0 +1,137 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.conformance.DateSearchTestCase;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.test.utilities.ITestDataBuilder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Run the tests defined in {@link DateSearchTestCase} in a DAO test as a @Nested suite.
*/
public abstract class BaseDateSearchDaoTests {
private static final Logger ourLog = LoggerFactory.getLogger(BaseDateSearchDaoTests.class);
/**
* Id of test Observation
*/
IIdType myObservationId;
/**
* Test for our date search operators.
* <p>
* Be careful - date searching is defined by set relations over intervals, not a simple number comparison.
* See http://hl7.org/fhir/search.html#prefix for details.
* <p>
* To debug, uncomment the @CsvSource line and comment @MethodSource to run a single case
*
*
* @param theResourceDate the date to use as Observation effective date
* @param theQuery the query parameter value including prefix (e.g. eq2020-01-01)
* @param theExpectedMatch true if theQuery should match theResourceDate.
* @param theFileName source file for test case
* @param theLineNumber source file line number for test case (-1 for inline tests)
*/
@ParameterizedTest
// use @CsvSource to debug individual cases.
//@CsvSource("2019-12-31T08:00:00,eq2020,false,inline,1")
@MethodSource("dateSearchCases")
public void testDateSearchMatching(String theResourceDate, String theQuery, boolean theExpectedMatch, String theFileName, int theLineNumber) {
Fixture fixture = getFixture();
if (isShouldSkip(theResourceDate, theQuery)) {
return;
}
// setup
myObservationId = fixture.createObservationWithEffectiveDate(theResourceDate);
// run the query
boolean matched = fixture.isObservationSearchMatch(theQuery, myObservationId);
assertExpectedMatch(theResourceDate, theQuery, theExpectedMatch, matched, theFileName, theLineNumber);
}
protected boolean isShouldSkip(String theResourceDate, String theQuery) {
return false;
}
protected static void assertExpectedMatch(String theResourceDate, String theQuery, boolean theExpectedMatch, boolean matched, String theFileName, int theLineNumber) {
String message =
"Expected " + theQuery + " to " +
(theExpectedMatch ? "" : "not ") + "match " + theResourceDate +
" (" + theFileName + ":" + theLineNumber + ")"; // wrap this in () so tools recognize the line reference.
assertEquals(theExpectedMatch, matched, message);
}
/**
* Turn the cases into expanded arguments for better reporting output and debugging
*/
public static List<Arguments> dateSearchCases() {
return DateSearchTestCase.ourCases.stream()
.map(DateSearchTestCase::toArguments)
.collect(Collectors.toList());
}
/**
* Helper to provide local setup and query services.
*
* Use an abstract method instead of a constructor because JUnit has a such a funky lifecycle.
*/
protected abstract Fixture getFixture();
public interface Fixture {
/**
* Create an observation and save it
*/
IIdType createObservationWithEffectiveDate(String theResourceDate);
/**
* Does date=theQuery match theObservationId created
*/
boolean isObservationSearchMatch(String theQuery, IIdType theObservationId);
}
public static class TestDataBuilderFixture<O extends IBaseResource> implements Fixture {
final ITestDataBuilder myTestDataBuilder;
final IFhirResourceDao<O> myObservationDao;
public TestDataBuilderFixture(ITestDataBuilder theTestDataBuilder, IFhirResourceDao<O> theObservationDao) {
myTestDataBuilder = theTestDataBuilder;
myObservationDao = theObservationDao;
}
@Override
public IIdType createObservationWithEffectiveDate(String theResourceDate) {
return myTestDataBuilder.createObservation(myTestDataBuilder.withEffectiveDate(theResourceDate));
}
@Override
public boolean isObservationSearchMatch(String theQuery, IIdType theObservationId) {
SearchParameterMap map = SearchParameterMap.newSynchronous();
map.add("date", new DateParam(theQuery));
ourLog.info("Searching for observation {}", map);
IBundleProvider results = myObservationDao.search(map);
boolean matched = results.getAllResourceIds().contains(theObservationId.getIdPart());
return matched;
}
}
}

View File

@ -0,0 +1,42 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.test.utilities.ITestDataBuilder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
public class DaoTestDataBuilder implements ITestDataBuilder {
final FhirContext myFhirCtx;
final DaoRegistry myDaoRegistry;
SystemRequestDetails mySrd;
public DaoTestDataBuilder(FhirContext theFhirCtx, DaoRegistry theDaoRegistry, SystemRequestDetails theSrd) {
myFhirCtx = theFhirCtx;
myDaoRegistry = theDaoRegistry;
mySrd = theSrd;
}
@Override
public IIdType doCreateResource(IBaseResource theResource) {
//noinspection rawtypes
IFhirResourceDao dao = myDaoRegistry.getResourceDao(theResource.getClass());
//noinspection unchecked
return dao.create(theResource, mySrd).getId().toUnqualifiedVersionless();
}
@Override
public IIdType doUpdateResource(IBaseResource theResource) {
//noinspection rawtypes
IFhirResourceDao dao = myDaoRegistry.getResourceDao(theResource.getClass());
//noinspection unchecked
return dao.update(theResource, mySrd).getId().toUnqualifiedVersionless();
}
@Override
public FhirContext getFhirContext() {
return myFhirCtx;
}
}

View File

@ -30,6 +30,7 @@ import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
@ -68,17 +69,23 @@ public class DateSearchTestCase {
*/
public final static List<DateSearchTestCase> ourCases;
static {
ourCases = new ArrayList<>();
ourCases.addAll(expandedCases());
ourCases.addAll(compactCases());
}
private static List<DateSearchTestCase> expandedCases() {
String csv = "DateSearchTestCase.csv";
InputStream resource = DateSearchTestCase.class.getResourceAsStream(csv);
assert resource != null;
InputStreamReader inputStreamReader = new InputStreamReader(resource, StandardCharsets.UTF_8);
ourCases = parseCsvCases(inputStreamReader, csv);
List<DateSearchTestCase> cases = parseCsvCases(inputStreamReader, csv);
try {
resource.close();
} catch (IOException e) {
e.printStackTrace();
}
ourCases.addAll(compactCases());
return cases;
}
static List<DateSearchTestCase> parseCsvCases(Reader theSource, String theFileName) {
@ -122,7 +129,7 @@ public class DateSearchTestCase {
String resourceValue = fields[0].trim();
String truePrefixes = fields[1].trim();
String queryValue = fields[2].trim();
Set<String> expectedTruePrefixes = Arrays.stream(truePrefixes.split(" +")).map(String::trim).collect(Collectors.toSet());
Set<String> expectedTruePrefixes = Arrays.stream(truePrefixes.split("\\s+")).map(String::trim).collect(Collectors.toSet());
// expand to one test case per supportedPrefixes
return supportedPrefixes.stream()

View File

@ -86,6 +86,13 @@ public interface ITestDataBuilder {
return t -> __setPrimitiveChild(getFhirContext(), t, "status", "code", theStatus);
}
/**
* Set Observation.effectiveDate
*/
default Consumer<IBaseResource> withEffectiveDate(String theDate) {
return t -> __setPrimitiveChild(getFhirContext(), t, "effectiveDateTime", "dateTime", theDate);
}
/**
* Set [Resource].identifier.system and [Resource].identifier.value
*/

View File

@ -3,3 +3,6 @@
2021, gt ge ne,2020,
2020, lt le ne,2021,
2021-01-01, ne gt ge,2020
1965-08-09, ne gt ge, 1918-11-11
1965-08-09, eq le ge, 1965-08-09
1965-08-09, ne lt le, 2020-12-08

1 #Resource Date, Matching prefixes, Query Date,
3 2021, gt ge ne,2020,
4 2020, lt le ne,2021,
5 2021-01-01, ne gt ge,2020
6 1965-08-09, ne gt ge, 1918-11-11
7 1965-08-09, eq le ge, 1965-08-09
8 1965-08-09, ne lt le, 2020-12-08

View File

@ -2913,7 +2913,7 @@
<profile>
<id>FASTINSTALL</id>
<properties>
<maven.test.skip>true</maven.test.skip>
<skipTests>true</skipTests>
</properties>
<!-- Profile for a quick local mvn install after a git pull.
We assume upstream ran these checks as part of the build. -->