Files
hapi-fhir-jpaserver-starter/src/test/java/ca/uhn/fhir/jpa/starter/ElasticsearchLastNR4IT.java
darth.cav b81e2abe81 Add Elasticsearch index prefix configuration (#891)
* Add Elasticsearch configuration section to application.yaml

* Add configuration for reindexing resources upon search parameter change

* Add Elasticsearch client configuration for testing

* Update logback dependencies because of a security issue and enable resource filtering in pom.xml

* Add application name and tester configuration to application.yaml. Refactor hibernate properties

* Add custom Elasticsearch configuration to create ElasticsearchClient bean

* Add index prefix configuration for Elasticsearch indices

* Fix Elasticsearch URI format in test initialization

* Refactor ElasticsearchConfig for improved readability and code style consistency (spotless:apply)

* Refactor ElasticsearchConfig for improved readability and code style consistency (spotless:apply)

* Update index prefix in application.yaml for Elasticsearch configuration

* Enhance index prefix validation and add sanitization method for Elasticsearch index names

* Merged with master in upstream

* Refactor ElasticsearchBootSvcImpl and ElasticsearchConfig for improved readability (mvn spotless)

* Comment out custom_content_path in application.yaml for clarity

* - Refactor application.yaml for improved structure and readability;
- Add PostgresLucenePatientIT integration test; and
- Update testcontainers dependencies to avoid compatibility issues with newer Docker APIs.

* Refactor PostgresLucenePatientIT to use external configuration file for test properties

* Add integration tests for PostgreSQL and Elasticsearch with dynamic configuration

* Increase socket timeout in CdsHooksServletIT for improved stability during tests

* Remove deprecated tester configuration from application.yaml
2026-01-20 15:32:24 +01:00

180 lines
7.7 KiB
Java

package ca.uhn.fhir.jpa.starter;
import static org.junit.jupiter.api.Assertions.assertEquals;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchRestClientFactory;
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl;
import ca.uhn.fhir.jpa.starter.elastic.ElasticsearchBootSvcImpl;
import ca.uhn.fhir.jpa.test.config.TestElasticsearchContainerHelper;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import java.io.IOException;
import java.util.Date;
import java.util.GregorianCalendar;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.indices.IndexSettings;
import jakarta.annotation.PreDestroy;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.testcontainers.elasticsearch.ElasticsearchContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@ExtendWith(SpringExtension.class)
@Testcontainers
@ActiveProfiles("test")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class, ElasticsearchLastNR4IT.TestConfig.class}, properties =
{
"spring.datasource.url=jdbc:h2:mem:dbr4",
"hapi.fhir.fhir_version=r4",
"hapi.fhir.lastn_enabled=true",
"hapi.fhir.store_resource_in_lucene_index_enabled=true",
"hapi.fhir.advanced_lucene_indexing=true",
"hapi.fhir.search_index_full_text_enabled=true",
"hapi.fhir.cr_enabled=false",
// Because the port is set randomly, we will set the rest_url using the Initializer.
// "elasticsearch.rest_url='http://localhost:9200'",
"spring.elasticsearch.uris=http://localhost:9200",
"spring.elasticsearch.username=elastic",
"spring.elasticsearch.password=changeme",
"spring.main.allow-bean-definition-overriding=true",
"spring.jpa.properties.hibernate.search.enabled=true",
"spring.jpa.properties.hibernate.search.backend.type=elasticsearch",
"spring.jpa.properties.hibernate.search.backend.hosts=localhost:9200",
"spring.jpa.properties.hibernate.search.backend.protocol=http",
"spring.jpa.properties.hibernate.search.backend.analysis.configurer=ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiElasticsearchAnalysisConfigurer"
})
@ContextConfiguration(initializers = ElasticsearchLastNR4IT.Initializer.class)
class ElasticsearchLastNR4IT {
private IGenericClient ourClient;
private FhirContext ourCtx;
@Container
public static ElasticsearchContainer embeddedElastic = TestElasticsearchContainerHelper.getEmbeddedElasticSearch();
@Autowired
private ElasticsearchBootSvcImpl myElasticsearchSvc;
@BeforeAll
public static void beforeClass() throws IOException {
//Given
ElasticsearchClient elasticsearchHighLevelRestClient = ElasticsearchRestClientFactory.createElasticsearchHighLevelRestClient(
"http", embeddedElastic.getHost() + ":" + embeddedElastic.getMappedPort(9200), "", "");
/* As of 2023-08-10, HAPI FHIR sets SubscriptionConstants.MAX_SUBSCRIPTION_RESULTS to 50000
which is in excess of elastic's default max_result_window. If MAX_SUBSCRIPTION_RESULTS is changed
to a value <= 10000, the following will no longer be necessary. - dotasek
*/
elasticsearchHighLevelRestClient.indices().putTemplate(t->{
t.name("hapi_fhir_template");
t.indexPatterns("*");
t.settings(new IndexSettings.Builder().maxNgramDiff(50).maxResultWindow(50000).build());
return t;
});
}
@PreDestroy
public void stop() {
embeddedElastic.stop();
}
@LocalServerPort
private int port;
@Test
void testLastN() throws IOException, InterruptedException {
Thread.sleep(2000);
Patient pt = new Patient();
pt.addName().setFamily("Lastn").addGiven("Arthur");
IIdType id = ourClient.create().resource(pt).execute().getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.getSubject().setReferenceElement(id);
String observationCode = "testobservationcode";
String codeSystem = "http://testobservationcodesystem";
obs.getCode().addCoding().setCode(observationCode).setSystem(codeSystem);
obs.setValue(new StringType(observationCode));
Date effectiveDtm = new GregorianCalendar().getTime();
obs.setEffective(new DateTimeType(effectiveDtm));
obs.getCategoryFirstRep().addCoding().setCode("testcategorycode").setSystem("http://testcategorycodesystem");
IIdType obsId = ourClient.create().resource(obs).execute().getId().toUnqualifiedVersionless();
myElasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX);
Thread.sleep(2000);
Parameters output = ourClient.operation().onType(Observation.class).named("lastn")
.withParameter(Parameters.class, "max", new IntegerType(1))
.andParameter("subject", new StringType("Patient/" + id.getIdPart()))
.execute();
Bundle b = (Bundle) output.getParameter().get(0).getResource();
assertEquals(1, b.getTotal());
assertEquals(obsId, b.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless());
}
@BeforeEach
void beforeEach() throws IOException {
ourCtx = FhirContext.forR4();
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
String ourServerBase = "http://localhost:" + port + "/fhir/";
ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
ourClient.registerInterceptor(new LoggingInterceptor(true));
}
@Configuration
static class TestConfig {
@Bean
public ElasticsearchClient elasticsearchClient() throws IOException {
return ElasticsearchRestClientFactory.createElasticsearchHighLevelRestClient(
"http", embeddedElastic.getHost() + ":" + embeddedElastic.getMappedPort(9200), "", "");
}
}
static class Initializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(
ConfigurableApplicationContext configurableApplicationContext) {
// Since the port is dynamically generated, replace the URL with one that has the correct port
TestPropertyValues.of("spring.elasticsearch.uris=http://" + embeddedElastic.getHost() + ":" + embeddedElastic.getMappedPort(9200))
.applyTo(configurableApplicationContext.getEnvironment());
TestPropertyValues.of("spring.jpa.properties.hibernate.search.backend.hosts=" + embeddedElastic.getHost() +":" + embeddedElastic.getMappedPort(9200))
.applyTo(configurableApplicationContext.getEnvironment());
}
}
}