diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/service/connection/ConnectionDetailsFactories.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/service/connection/ConnectionDetailsFactories.java index 1a16ac5fb0..80333d0f45 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/service/connection/ConnectionDetailsFactories.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/service/connection/ConnectionDetailsFactories.java @@ -17,14 +17,17 @@ package org.springframework.boot.autoconfigure.service.connection; import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.stream.Stream; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.io.support.SpringFactoriesLoader; -import org.springframework.core.style.ToStringCreator; +import org.springframework.util.Assert; /** * A registry of {@link ConnectionDetailsFactory} instances. @@ -49,30 +52,55 @@ public class ConnectionDetailsFactories { registrations.filter(Objects::nonNull).forEach(this.registrations::add); } - public ConnectionDetails getConnectionDetails(S source) { - return getConnectionDetailsFactory(source).getConnectionDetails(source); + /** + * Return a {@link Map} of {@link ConnectionDetails} interface type to + * {@link ConnectionDetails} instance created from the factories associated with the + * given source. + * @param the source type + * @param source the source + * @return a list of {@link ConnectionDetails} instances. + */ + public Map, ConnectionDetails> getConnectionDetails(S source) { + List> registrations = getRegistrations(source); + Map, ConnectionDetails> result = new LinkedHashMap<>(); + for (Registration registration : registrations) { + ConnectionDetails connectionDetails = registration.factory().getConnectionDetails(source); + if (connectionDetails != null) { + Class connectionDetailsType = registration.connectionDetailsType(); + ConnectionDetails previous = result.put(connectionDetailsType, connectionDetails); + Assert.state(previous == null, () -> "Duplicate connection details supplied for %s" + .formatted(connectionDetailsType.getName())); + } + } + return Map.copyOf(result); } @SuppressWarnings("unchecked") - public ConnectionDetailsFactory getConnectionDetailsFactory(S source) { + List> getRegistrations(S source) { Class sourceType = (Class) source.getClass(); - List> result = new ArrayList<>(); + List> result = new ArrayList<>(); for (Registration candidate : this.registrations) { if (candidate.sourceType().isAssignableFrom(sourceType)) { - result.add((ConnectionDetailsFactory) candidate.factory()); + result.add((Registration) candidate); } } if (result.isEmpty()) { throw new ConnectionDetailsFactoryNotFoundException(source); } - AnnotationAwareOrderComparator.sort(result); - return (result.size() != 1) ? new CompositeConnectionDetailsFactory<>(result) : result.get(0); + result.sort(Comparator.comparing(Registration::factory, AnnotationAwareOrderComparator.INSTANCE)); + return List.copyOf(result); } /** * A {@link ConnectionDetailsFactory} registration. + * + * @param the source type + * @param the connection details type + * @param sourceType the source type + * @param connectionDetailsType the connection details type + * @param factory the factory */ - private record Registration(Class sourceType, Class connectionDetailsType, + record Registration(Class sourceType, Class connectionDetailsType, ConnectionDetailsFactory factory) { @SuppressWarnings("unchecked") @@ -87,37 +115,4 @@ public class ConnectionDetailsFactories { } - /** - * Composite {@link ConnectionDetailsFactory} implementation. - * - * @param the source type - */ - static class CompositeConnectionDetailsFactory implements ConnectionDetailsFactory { - - private final List> delegates; - - CompositeConnectionDetailsFactory(List> delegates) { - this.delegates = delegates; - } - - @Override - public ConnectionDetails getConnectionDetails(S source) { - return this.delegates.stream() - .map((delegate) -> delegate.getConnectionDetails(source)) - .filter(Objects::nonNull) - .findFirst() - .orElse(null); - } - - List> getDelegates() { - return this.delegates; - } - - @Override - public String toString() { - return new ToStringCreator(this).append("delegates", this.delegates).toString(); - } - - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/service/connection/ConnectionDetailsFactoriesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/service/connection/ConnectionDetailsFactoriesTests.java index 42fe9a672d..785f7707d0 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/service/connection/ConnectionDetailsFactoriesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/service/connection/ConnectionDetailsFactoriesTests.java @@ -16,15 +16,18 @@ package org.springframework.boot.autoconfigure.service.connection; -import org.assertj.core.api.InstanceOfAssertFactories; +import java.util.List; +import java.util.Map; + import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactories.CompositeConnectionDetailsFactory; +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactories.Registration; import org.springframework.core.Ordered; import org.springframework.core.test.io.support.MockSpringFactoriesLoader; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link ConnectionDetailsFactories}. @@ -38,43 +41,50 @@ class ConnectionDetailsFactoriesTests { private final MockSpringFactoriesLoader loader = new MockSpringFactoriesLoader(); @Test - void getConnectionDetailsFactoryShouldThrowWhenNoFactoryForSource() { + void getConnectionDetailsWhenNoFactoryForSourceThrowsException() { ConnectionDetailsFactories factories = new ConnectionDetailsFactories(this.loader); assertThatExceptionOfType(ConnectionDetailsFactoryNotFoundException.class) - .isThrownBy(() -> factories.getConnectionDetailsFactory("source")); + .isThrownBy(() -> factories.getConnectionDetails("source")); } @Test - void getConnectionDetailsFactoryShouldReturnSingleFactoryWhenSourceHasOneMatch() { + void getConnectionDetailsWhenSourceHasOneMatchReturnsSingleResult() { this.loader.addInstance(ConnectionDetailsFactory.class, new TestConnectionDetailsFactory()); ConnectionDetailsFactories factories = new ConnectionDetailsFactories(this.loader); - ConnectionDetailsFactory factory = factories.getConnectionDetailsFactory("source"); - assertThat(factory).isInstanceOf(TestConnectionDetailsFactory.class); + Map, ConnectionDetails> connectionDetails = factories.getConnectionDetails("source"); + assertThat(connectionDetails).hasSize(1); + assertThat(connectionDetails.get(TestConnectionDetails.class)).isInstanceOf(TestConnectionDetailsImpl.class); + } + + @Test + void getConnectionDetailsWhenSourceHasMultipleMatchesReturnsMultipleResults() { + this.loader.addInstance(ConnectionDetailsFactory.class, new TestConnectionDetailsFactory(), + new OtherConnectionDetailsFactory()); + ConnectionDetailsFactories factories = new ConnectionDetailsFactories(this.loader); + Map, ConnectionDetails> connectionDetails = factories.getConnectionDetails("source"); + assertThat(connectionDetails).hasSize(2); } @Test - @SuppressWarnings("unchecked") - void getConnectionDetailsFactoryShouldReturnCompositeFactoryWhenSourceHasMultipleMatches() { + void getConnectionDetailsWhenDuplicatesThrowsException() { this.loader.addInstance(ConnectionDetailsFactory.class, new TestConnectionDetailsFactory(), new TestConnectionDetailsFactory()); ConnectionDetailsFactories factories = new ConnectionDetailsFactories(this.loader); - ConnectionDetailsFactory factory = factories.getConnectionDetailsFactory("source"); - assertThat(factory).asInstanceOf(InstanceOfAssertFactories.type(CompositeConnectionDetailsFactory.class)) - .satisfies((composite) -> assertThat(composite.getDelegates()).hasSize(2)); + assertThatIllegalStateException().isThrownBy(() -> factories.getConnectionDetails("source")) + .withMessage("Duplicate connection details supplied for " + TestConnectionDetails.class.getName()); } @Test - @SuppressWarnings("unchecked") - void compositeFactoryShouldHaveOrderedDelegates() { + void getRegistrationsReturnsOrderedDelegates() { TestConnectionDetailsFactory orderOne = new TestConnectionDetailsFactory(1); TestConnectionDetailsFactory orderTwo = new TestConnectionDetailsFactory(2); TestConnectionDetailsFactory orderThree = new TestConnectionDetailsFactory(3); this.loader.addInstance(ConnectionDetailsFactory.class, orderOne, orderThree, orderTwo); ConnectionDetailsFactories factories = new ConnectionDetailsFactories(this.loader); - ConnectionDetailsFactory factory = factories.getConnectionDetailsFactory("source"); - assertThat(factory).asInstanceOf(InstanceOfAssertFactories.type(CompositeConnectionDetailsFactory.class)) - .satisfies((composite) -> assertThat(composite.getDelegates()).containsExactly(orderOne, orderTwo, - orderThree)); + List> registrations = factories.getRegistrations("source"); + assertThat(registrations.get(0).factory()).isEqualTo(orderOne); + assertThat(registrations.get(1).factory()).isEqualTo(orderTwo); + assertThat(registrations.get(2).factory()).isEqualTo(orderThree); } private static final class TestConnectionDetailsFactory @@ -92,7 +102,7 @@ class ConnectionDetailsFactoriesTests { @Override public TestConnectionDetails getConnectionDetails(String source) { - return new TestConnectionDetails(); + return new TestConnectionDetailsImpl(); } @Override @@ -102,11 +112,30 @@ class ConnectionDetailsFactoriesTests { } - private static final class TestConnectionDetails implements ConnectionDetails { + private static final class OtherConnectionDetailsFactory + implements ConnectionDetailsFactory { - private TestConnectionDetails() { + @Override + public OtherConnectionDetails getConnectionDetails(String source) { + return new OtherConnectionDetailsImpl(); } } + private interface TestConnectionDetails extends ConnectionDetails { + + } + + private static final class TestConnectionDetailsImpl implements TestConnectionDetails { + + } + + private interface OtherConnectionDetails extends ConnectionDetails { + + } + + private static final class OtherConnectionDetailsImpl implements OtherConnectionDetails { + + } + } diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/testing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/testing.adoc index c9aed299bf..af6fac0ce9 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/testing.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/testing.adoc @@ -18,12 +18,12 @@ For additional details on Spring Security's testing support, see Spring Security - [[howto.testing.testcontainers]] === Use Testcontainers for Integration Testing The https://www.testcontainers.org/[Testcontainers] library provides a way to manage services running inside Docker containers. It integrates with JUnit, allowing you to write a test class that can start up a container before any of the tests run. Testcontainers is especially useful for writing integration tests that talk to a real backend service such as MySQL, MongoDB, Cassandra and others. + Testcontainers can be used in a Spring Boot test as follows: include::code:vanilla/MyIntegrationTests[] @@ -32,6 +32,7 @@ This will start up a docker container running Neo4j (if Docker is running locall In most cases, you will need to configure the application to connect to the service running in the container. + [[howto.testing.testcontainers.service-connections]] ==== Service Connections A service connection is a connection to any remote service. @@ -42,24 +43,69 @@ When using Testcontainers, connection details can be automatically created for a include::code:MyIntegrationTests[] -Thanks to `@Neo4jServiceConnection`, the above configuration allows Neo4j-related beans in the application to communicate with Neo4j running inside the Testcontainers-managed Docker container. +Thanks to `@ServiceConnection`, the above configuration allows Neo4j-related beans in the application to communicate with Neo4j running inside the Testcontainers-managed Docker container. This is done by automatically defining a `Neo4jConnectionDetails` bean which is then used by the Neo4j auto-configuration, overriding any connection-related configuration properties. -The following service connection annotations are provided by `spring-boot-test-autoconfigure`: - -- `@CassandraServiceConnection` -- `@CouchbaseServiceConnection` -- `@ElasticsearchServiceConnection` -- `@InfluxDbServiceConnection` -- `@JdbcServiceConnection` -- `@KafkaServiceConnection` -- `@MongoServiceConnection` -- `@Neo4jServiceConnection` -- `@R2dbcServiceConnection` -- `@RabbitServiceConnection` -- `@RedisServiceConnection` - -As with the earlier `@Neo4jConnectionDetails` example, each can be used on a container field. Doing so will automatically configure the application to connect to the service running in the container. +Service connection annotations are processed by `ContainerConnectionDetailsFactory` classes registered with `spring.factories`. +A `ContainerConnectionDetailsFactory` can create a `ConnectionDetails` bean based on a specific `Container` subclass, or the Docker image name. + +The following service connection factories are provided in the `spring-boot-testcontainers` jar: + +|=== +| Connection Details | Matched on + +| `CassandraConnectionDetails` +| Containers of type `CassandraContainer` + +| `CouchbaseConnectionDetails` +| Containers of type `CouchbaseContainer` + +| `ElasticsearchConnectionDetails` +| Containers of type `ElasticsearchContainer` + +| `FlywayConnectionDetails` +| Containers of type `JdbcDatabaseContainer` + +| `InfluxDbConnectionDetails` +| Containers of type `InfluxDBContainer` + +| `JdbcConnectionDetails` +| Containers of type `JdbcDatabaseContainer` + +| `KafkaConnectionDetails` +| Containers of type `KafkaContainer` + +| `LiquibaseConnectionDetails` +| Containers of type `JdbcDatabaseContainer` + +| `MongoConnectionDetails` +| Containers of type `MongoDBContainer` + +| `Neo4jConnectionDetails` +| Containers of type `Neo4jContainer` + +| `R2dbcConnectionDetails` +| Containers of type `MariaDBContainer`, `MSSQLServerContainer`, `MySQLContainer` or `PostgreSQLContainer` + +| `RabbitConnectionDetails` +| Containers of type `RabbitMQContainer` + +| `RedisConnectionDetails` +| Containers named "redis" +|=== + +[TIP] +==== +By default all applicable connection details beans will be created for a given `Container`. +For example, a `PostgreSQLContainer` will create both `JdbcConnectionDetails` and `R2dbcConnectionDetails`. + +If you want to create only a subset of the applicable types, you can use the `type` attribute of `@ServiceConnection`. +==== + +By default `Container.getDockerImageName()` is used to obtain the name used to find connection details. +If you are using a custom docker image, you can use the `name` attribute of `@ServiceConnection` to override it. + +For example, if you have a `GenericContainer` using a Docker image of `registry.mycompany.com/mirror/myredis`, you'd use `@ServiceConnection(name="redis")` to ensure `RedisConnectionDetails` are created. diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/testing/testcontainers/serviceconnections/MyIntegrationTests.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/testing/testcontainers/serviceconnections/MyIntegrationTests.java index 967f002549..ed83e6ad09 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/testing/testcontainers/serviceconnections/MyIntegrationTests.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/testing/testcontainers/serviceconnections/MyIntegrationTests.java @@ -22,14 +22,14 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.testcontainers.service.connection.neo4j.Neo4jServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; @SpringBootTest @Testcontainers class MyIntegrationTests { @Container - @Neo4jServiceConnection + @ServiceConnection static Neo4jContainer neo4j = new Neo4jContainer<>("neo4j:5"); @Test diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/testing/testcontainers/serviceconnections/MyIntegrationTests.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/testing/testcontainers/serviceconnections/MyIntegrationTests.kt index a63bb59dd7..f79279a34c 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/testing/testcontainers/serviceconnections/MyIntegrationTests.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/testing/testcontainers/serviceconnections/MyIntegrationTests.kt @@ -17,14 +17,13 @@ package org.springframework.boot.docs.howto.testing.testcontainers.serviceconnection import org.junit.jupiter.api.Test -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.testcontainers.service.connection.neo4j.Neo4jServiceConnection; -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource import org.testcontainers.containers.Neo4jContainer import org.testcontainers.junit.jupiter.Container import org.testcontainers.junit.jupiter.Testcontainers +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + @SpringBootTest @Testcontainers internal class MyIntegrationTests { @@ -37,7 +36,7 @@ internal class MyIntegrationTests { companion object { @Container - @Neo4jServiceConnection + @ServiceConnection var neo4j: Neo4jContainer<*> = Neo4jContainer("neo4j:5") } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestIntegrationTests.java index 226f7a381e..c4cbf3cb61 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestIntegrationTests.java @@ -28,7 +28,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.redis.ExampleService; import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.boot.testcontainers.service.connection.cassandra.CassandraServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.CassandraContainer; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -52,7 +52,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; class DataCassandraTestIntegrationTests { @Container - @CassandraServiceConnection + @ServiceConnection static final CassandraContainer cassandra = new CassandraContainer(); @Autowired diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestWithIncludeFilterIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestWithIncludeFilterIntegrationTests.java index 853a77475b..05741e8a91 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestWithIncludeFilterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/cassandra/DataCassandraTestWithIncludeFilterIntegrationTests.java @@ -26,7 +26,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.boot.testcontainers.service.connection.cassandra.CassandraServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.CassandraContainer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan.Filter; @@ -51,7 +51,7 @@ import static org.assertj.core.api.Assertions.assertThat; class DataCassandraTestWithIncludeFilterIntegrationTests { @Container - @CassandraServiceConnection + @ServiceConnection static final CassandraContainer cassandra = new CassandraContainer(); @Autowired diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestIntegrationTests.java index f423937534..950b59ad99 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestIntegrationTests.java @@ -26,7 +26,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.testcontainers.service.connection.couchbase.CouchbaseServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.ApplicationContext; import org.springframework.data.couchbase.core.CouchbaseTemplate; @@ -50,7 +50,7 @@ class DataCouchbaseTestIntegrationTests { private static final String BUCKET_NAME = "cbbucket"; @Container - @CouchbaseServiceConnection + @ServiceConnection static final CouchbaseContainer couchbase = new CouchbaseContainer(DockerImageNames.couchbase()) .withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)) diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestReactiveIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestReactiveIntegrationTests.java index d21c094231..3fdc33b3ac 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestReactiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestReactiveIntegrationTests.java @@ -25,7 +25,7 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.testcontainers.service.connection.couchbase.CouchbaseServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate; @@ -47,7 +47,7 @@ class DataCouchbaseTestReactiveIntegrationTests { private static final String BUCKET_NAME = "cbbucket"; @Container - @CouchbaseServiceConnection + @ServiceConnection static final CouchbaseContainer couchbase = new CouchbaseContainer(DockerImageNames.couchbase()) .withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)) diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestWithIncludeFilterIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestWithIncludeFilterIntegrationTests.java index b2404c0af3..4931fe3990 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestWithIncludeFilterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/couchbase/DataCouchbaseTestWithIncludeFilterIntegrationTests.java @@ -25,7 +25,7 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.testcontainers.service.connection.couchbase.CouchbaseServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.stereotype.Service; @@ -48,7 +48,7 @@ class DataCouchbaseTestWithIncludeFilterIntegrationTests { private static final String BUCKET_NAME = "cbbucket"; @Container - @CouchbaseServiceConnection + @ServiceConnection static final CouchbaseContainer couchbase = new CouchbaseContainer(DockerImageNames.couchbase()) .withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)) diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestIntegrationTests.java index 41c96c69d8..224692f131 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestIntegrationTests.java @@ -26,7 +26,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.testcontainers.service.connection.elasticsearch.ElasticsearchServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.ApplicationContext; import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate; @@ -47,7 +47,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; class DataElasticsearchTestIntegrationTests { @Container - @ElasticsearchServiceConnection + @ServiceConnection static final ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) .withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestPropertiesIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestPropertiesIntegrationTests.java index 70d68ea63e..115185676a 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestPropertiesIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestPropertiesIntegrationTests.java @@ -25,7 +25,7 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.testcontainers.service.connection.elasticsearch.ElasticsearchServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.core.env.Environment; @@ -45,7 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat; class DataElasticsearchTestPropertiesIntegrationTests { @Container - @ElasticsearchServiceConnection + @ServiceConnection static final ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) .withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestReactiveIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestReactiveIntegrationTests.java index e81e2653d5..30f7e53a2b 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestReactiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestReactiveIntegrationTests.java @@ -24,7 +24,7 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.testcontainers.service.connection.elasticsearch.ElasticsearchServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchTemplate; @@ -44,7 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat; class DataElasticsearchTestReactiveIntegrationTests { @Container - @ElasticsearchServiceConnection + @ServiceConnection static final ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) .withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestWithIncludeFilterIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestWithIncludeFilterIntegrationTests.java index c384f7989a..f56eb228f9 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestWithIncludeFilterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/elasticsearch/DataElasticsearchTestWithIncludeFilterIntegrationTests.java @@ -25,7 +25,7 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.testcontainers.service.connection.elasticsearch.ElasticsearchServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.stereotype.Service; @@ -46,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; class DataElasticsearchTestWithIncludeFilterIntegrationTests { @Container - @ElasticsearchServiceConnection + @ServiceConnection static final ElasticsearchContainer elasticsearch = new ElasticsearchContainer(DockerImageNames.elasticsearch()) .withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestIntegrationTests.java index de79ab6c25..9fcb57b86e 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestIntegrationTests.java @@ -25,7 +25,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.testcontainers.service.connection.mongo.MongoServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.ApplicationContext; import org.springframework.data.mongodb.core.MongoTemplate; @@ -46,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; class DataMongoTestIntegrationTests { @Container - @MongoServiceConnection + @ServiceConnection static final MongoDBContainer mongoDB = new MongoDBContainer(DockerImageNames.mongo()).withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(5)); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestReactiveIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestReactiveIntegrationTests.java index 8310515d5e..8adb740b45 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestReactiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestReactiveIntegrationTests.java @@ -24,7 +24,7 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.testcontainers.service.connection.mongo.MongoServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.data.mongodb.core.ReactiveMongoTemplate; @@ -43,7 +43,7 @@ import static org.assertj.core.api.Assertions.assertThat; class DataMongoTestReactiveIntegrationTests { @Container - @MongoServiceConnection + @ServiceConnection static final MongoDBContainer mongoDB = new MongoDBContainer(DockerImageNames.mongo()).withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(5)); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestWithIncludeFilterIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestWithIncludeFilterIntegrationTests.java index a68a56762f..24e0239196 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestWithIncludeFilterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/DataMongoTestWithIncludeFilterIntegrationTests.java @@ -24,7 +24,7 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.testcontainers.service.connection.mongo.MongoServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.stereotype.Service; @@ -44,7 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat; class DataMongoTestWithIncludeFilterIntegrationTests { @Container - @MongoServiceConnection + @ServiceConnection static final MongoDBContainer mongoDB = new MongoDBContainer(DockerImageNames.mongo()).withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(5)); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/TransactionalDataMongoTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/TransactionalDataMongoTestIntegrationTests.java index a899acd05e..c76dad56e3 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/TransactionalDataMongoTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/mongo/TransactionalDataMongoTestIntegrationTests.java @@ -26,7 +26,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.boot.testcontainers.service.connection.mongo.MongoServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.annotation.Bean; import org.springframework.data.mongodb.MongoDatabaseFactory; @@ -49,7 +49,7 @@ import static org.assertj.core.api.Assertions.assertThat; class TransactionalDataMongoTestIntegrationTests { @Container - @MongoServiceConnection + @ServiceConnection static final MongoDBContainer mongoDB = new MongoDBContainer(DockerImageNames.mongo()).withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(5)); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestIntegrationTests.java index db56ee04c9..b61612322c 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestIntegrationTests.java @@ -25,7 +25,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.testcontainers.service.connection.neo4j.Neo4jServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.ApplicationContext; import org.springframework.data.neo4j.core.Neo4jTemplate; @@ -48,7 +48,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; class DataNeo4jTestIntegrationTests { @Container - @Neo4jServiceConnection + @ServiceConnection static final Neo4jContainer neo4j = new Neo4jContainer<>(DockerImageNames.neo4j()).withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestPropertiesIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestPropertiesIntegrationTests.java index b421ceb3d0..0f97ff5f4d 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestPropertiesIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestPropertiesIntegrationTests.java @@ -25,7 +25,7 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.testcontainers.service.connection.neo4j.Neo4jServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.core.env.Environment; @@ -45,7 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat; class DataNeo4jTestPropertiesIntegrationTests { @Container - @Neo4jServiceConnection + @ServiceConnection static final Neo4jContainer neo4j = new Neo4jContainer<>(DockerImageNames.neo4j()).withoutAuthentication() .withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestReactiveIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestReactiveIntegrationTests.java index ecabdb7198..809e8a1b45 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestReactiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestReactiveIntegrationTests.java @@ -29,7 +29,7 @@ import reactor.test.StepVerifier; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.boot.testcontainers.service.connection.neo4j.Neo4jServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -56,7 +56,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; class DataNeo4jTestReactiveIntegrationTests { @Container - @Neo4jServiceConnection + @ServiceConnection static final Neo4jContainer neo4j = new Neo4jContainer<>(DockerImageNames.neo4j()).withoutAuthentication() .withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestWithIncludeFilterIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestWithIncludeFilterIntegrationTests.java index f241da3387..f2a4a822ad 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestWithIncludeFilterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestWithIncludeFilterIntegrationTests.java @@ -24,7 +24,7 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.testcontainers.service.connection.neo4j.Neo4jServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.stereotype.Service; @@ -45,7 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat; class DataNeo4jTestWithIncludeFilterIntegrationTests { @Container - @Neo4jServiceConnection + @ServiceConnection static final Neo4jContainer neo4j = new Neo4jContainer<>(DockerImageNames.neo4j()).withoutAuthentication() .withStartupAttempts(5) .withStartupTimeout(Duration.ofMinutes(10)); diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestIntegrationTests.java index 495ee2315b..98331d0d21 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestIntegrationTests.java @@ -25,7 +25,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.testcontainers.service.connection.redis.RedisServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.RedisContainer; import org.springframework.context.ApplicationContext; import org.springframework.data.redis.core.RedisOperations; @@ -48,7 +48,7 @@ class DataRedisTestIntegrationTests { private static final Charset CHARSET = StandardCharsets.UTF_8; @Container - @RedisServiceConnection + @ServiceConnection static RedisContainer redis = new RedisContainer(); @Autowired diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestPropertiesIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestPropertiesIntegrationTests.java index 1e5b6879dc..d4897ebaaf 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestPropertiesIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestPropertiesIntegrationTests.java @@ -22,7 +22,7 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.testcontainers.service.connection.redis.RedisServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.RedisContainer; import org.springframework.core.env.Environment; @@ -42,7 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat; class DataRedisTestPropertiesIntegrationTests { @Container - @RedisServiceConnection + @ServiceConnection static final RedisContainer redis = new RedisContainer(); @Autowired diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestReactiveIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestReactiveIntegrationTests.java index 8f9a511959..b0c15b6afa 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestReactiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestReactiveIntegrationTests.java @@ -25,7 +25,7 @@ import reactor.test.StepVerifier; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.testcontainers.service.connection.redis.RedisServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.RedisContainer; import org.springframework.context.ApplicationContext; import org.springframework.data.redis.core.ReactiveRedisOperations; @@ -45,7 +45,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; class DataRedisTestReactiveIntegrationTests { @Container - @RedisServiceConnection + @ServiceConnection static RedisContainer redis = new RedisContainer(); @Autowired diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestWithIncludeFilterIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestWithIncludeFilterIntegrationTests.java index 0c02ada4da..c46659b9db 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestWithIncludeFilterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestWithIncludeFilterIntegrationTests.java @@ -21,7 +21,7 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.testcontainers.service.connection.redis.RedisServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.RedisContainer; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.stereotype.Service; @@ -41,7 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat; class DataRedisTestWithIncludeFilterIntegrationTests { @Container - @RedisServiceConnection + @ServiceConnection static final RedisContainer redis = new RedisContainer(); @Autowired diff --git a/spring-boot-project/spring-boot-testcontainers/build.gradle b/spring-boot-project/spring-boot-testcontainers/build.gradle index a74d44d9bc..d7b70f7132 100644 --- a/spring-boot-project/spring-boot-testcontainers/build.gradle +++ b/spring-boot-project/spring-boot-testcontainers/build.gradle @@ -1,5 +1,6 @@ plugins { id "java-library" + id "org.springframework.boot.auto-configuration" id "org.springframework.boot.conventions" id "org.springframework.boot.deployed" id "org.springframework.boot.optional-dependencies" @@ -28,6 +29,7 @@ dependencies { optional("org.testcontainers:rabbitmq") optional("org.testcontainers:r2dbc") + testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation("ch.qos.logback:logback-classic") testImplementation("org.assertj:assertj-core") testImplementation("org.awaitility:awaitility") diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactory.java index f01aee72a6..486b97ebe3 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactory.java @@ -16,9 +16,9 @@ package org.springframework.boot.testcontainers.service.connection; -import java.lang.annotation.Annotation; +import java.util.Arrays; -import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Container; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory; @@ -26,13 +26,13 @@ import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.OriginProvider; import org.springframework.core.ResolvableType; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; /** * Base class for {@link ConnectionDetailsFactory} implementations that provide * {@link ConnectionDetails} from a {@link ContainerConnectionSource}. * - * @param the source annotation type. The annotation will be mergable to a - * {@link ServiceConnection @ServiceConnection}. * @param the connection details type * @param the generic container type * @author Moritz Halbritter @@ -40,17 +40,58 @@ import org.springframework.util.Assert; * @author Phillip Webb * @since 3.1.0 */ -public abstract class ContainerConnectionDetailsFactory> - implements ConnectionDetailsFactory, D> { +public abstract class ContainerConnectionDetailsFactory> + implements ConnectionDetailsFactory, D> { + + /** + * Constant passed to the constructor when any connection name is accepted. + */ + protected static final String ANY_CONNECTION_NAME = null; + + private final String connectionName; + + private final String[] requiredClassNames; + + /** + * Create a new {@link ContainerConnectionDetailsFactory} instance that accepts + * {@link #ANY_CONNECTION_NAME any connection name}. + */ + protected ContainerConnectionDetailsFactory() { + this(ANY_CONNECTION_NAME); + } + + /** + * Create a new {@link ContainerConnectionDetailsFactory} instance with the given + * connection name restriction. + * @param connectionName the required connection name or {@link #ANY_CONNECTION_NAME} + * @param requiredClassNames the names of classes that must be present + */ + protected ContainerConnectionDetailsFactory(String connectionName, String... requiredClassNames) { + this.connectionName = connectionName; + this.requiredClassNames = requiredClassNames; + } @Override - public final D getConnectionDetails(ContainerConnectionSource source) { - Class[] generics = resolveGenerics(); - Class annotationType = generics[0]; - Class connectionDetailsType = generics[1]; - Class containerType = generics[2]; - return (!source.accepts(annotationType, connectionDetailsType, containerType)) ? null - : getContainerConnectionDetails(source); + public final D getConnectionDetails(ContainerConnectionSource source) { + if (!hasRequiredClasses()) { + return null; + } + try { + Class[] generics = resolveGenerics(); + Class connectionDetailsType = generics[0]; + Class containerType = generics[1]; + if (source.accepts(this.connectionName, connectionDetailsType, containerType)) { + return getContainerConnectionDetails(source); + } + } + catch (NoClassDefFoundError ex) { + } + return null; + } + + private boolean hasRequiredClasses() { + return ObjectUtils.isEmpty(this.requiredClassNames) || Arrays.stream(this.requiredClassNames) + .allMatch((requiredClassName) -> ClassUtils.isPresent(requiredClassName, null)); } private Class[] resolveGenerics() { @@ -64,7 +105,7 @@ public abstract class ContainerConnectionDetailsFactory source); + protected abstract D getContainerConnectionDetails(ContainerConnectionSource source); /** * Convenient base class for {@link ConnectionDetails} results that are backed by a @@ -78,7 +119,7 @@ public abstract class ContainerConnectionDetailsFactory source) { + protected ContainerConnectionDetails(ContainerConnectionSource source) { Assert.notNull(source, "Source must not be null"); this.origin = source.getOrigin(); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionSource.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionSource.java index 0f3634cfe7..d10ca54480 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionSource.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionSource.java @@ -16,23 +16,25 @@ package org.springframework.boot.testcontainers.service.connection; -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; +import java.util.Set; -import org.testcontainers.containers.GenericContainer; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.testcontainers.containers.Container; +import org.testcontainers.utility.DockerImageName; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.OriginProvider; import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.log.LogMessage; +import org.springframework.util.StringUtils; /** * Passed to {@link ContainerConnectionDetailsFactory} to provide details of the - * {@link ServiceConnection @ServiceConnection} annotation {@link GenericContainer} field - * that provides the service. + * {@link ServiceConnection @ServiceConnection} annotated {@link Container} that provides + * the service. * - * @param the source annotation type. The annotation will mergable to a - * {@link ServiceConnection @ServiceConnection} * @param the connection details type * @param the generic container type * @author Moritz Halbritter @@ -41,58 +43,77 @@ import org.springframework.core.annotation.MergedAnnotation; * @since 3.1.0 * @see ContainerConnectionDetailsFactory */ -public final class ContainerConnectionSource> +public final class ContainerConnectionSource> implements OriginProvider { - private final Class connectionDetailsType; + private static final Log logger = LogFactory.getLog(ContainerConnectionSource.class); - private final Field field; + private final String beanNameSuffix; - private final A annotation; + private final Origin origin; private final C container; - private final AnnotatedFieldOrigin origin; + private String acceptedConnectionName; - @SuppressWarnings("unchecked") - ContainerConnectionSource(Class connectionDetailsType, Field field, - MergedAnnotation annotation, C container) { - this(connectionDetailsType, field, (A) annotation.getRoot().synthesize(), container); - } + private Set> acceptedConnectionDetailsTypes; - ContainerConnectionSource(Class connectionDetailsType, Field field, A annotation, C container) { - this.connectionDetailsType = connectionDetailsType; - this.field = field; - this.annotation = annotation; + ContainerConnectionSource(String beanNameSuffix, Origin origin, C container, + MergedAnnotation annotation) { + this.beanNameSuffix = beanNameSuffix; + this.origin = origin; this.container = container; - this.origin = new AnnotatedFieldOrigin(field, annotation); + this.acceptedConnectionName = getConnectionName(container, annotation.getString("name")); + this.acceptedConnectionDetailsTypes = Set.of(annotation.getClassArray("type")); } - boolean accepts(Class annotationType, Class connectionDetailsType, Class containerType) { - return annotationType.isInstance(this.annotation) - && connectionDetailsType.isAssignableFrom(this.connectionDetailsType) - && containerType.isInstance(this.container); + ContainerConnectionSource(String beanNameSuffix, Origin origin, C container, ServiceConnection annotation) { + this.beanNameSuffix = beanNameSuffix; + this.origin = origin; + this.container = container; + this.acceptedConnectionName = getConnectionName(container, annotation.name()); + this.acceptedConnectionDetailsTypes = Set.of(annotation.type()); } - String getBeanName() { - return this.field.getName() + this.connectionDetailsType.getSimpleName() + "ConnectedContainer"; + private static String getConnectionName(Container container, String connectionName) { + if (StringUtils.hasLength(connectionName)) { + return connectionName; + } + try { + DockerImageName imageName = DockerImageName.parse(container.getDockerImageName()); + imageName.assertValid(); + return imageName.getRepository(); + } + catch (IllegalArgumentException ex) { + return container.getDockerImageName(); + } } - /** - * Return the source annotation that provided the connection to the container. This - * annotation will be mergable to {@link ServiceConnection @ServiceConnection}. - * @return the source annotation - */ - public A getAnnotation() { - return this.annotation; + boolean accepts(String connectionName, Class connectionDetailsType, Class containerType) { + if (!containerType.isInstance(this.container)) { + logger.trace(LogMessage.of(() -> "%s not accepted as %s is not an instance of %s".formatted(this, + this.container.getClass().getName(), containerType.getName()))); + return false; + } + if (StringUtils.hasLength(connectionName) && !connectionName.equalsIgnoreCase(this.acceptedConnectionName)) { + logger.trace(LogMessage.of(() -> "%s not accepted as connection names '%s' and '%s' do not match" + .formatted(this, connectionName, this.acceptedConnectionName))); + return false; + } + if (!this.acceptedConnectionDetailsTypes.isEmpty() && !this.acceptedConnectionDetailsTypes.stream() + .anyMatch((candidate) -> candidate.isAssignableFrom(connectionDetailsType))) { + logger.trace(LogMessage.of(() -> "%s not accepted as connection details type %s not in %s".formatted(this, + connectionDetailsType, this.acceptedConnectionDetailsTypes))); + return false; + } + logger.trace(LogMessage + .of(() -> "%s accepted for connection name '%s', connection details type %s, container type %s" + .formatted(this, connectionName, connectionDetailsType.getName(), containerType.getName()))); + return true; } - /** - * Return the {@link GenericContainer} that implements the service being connected to. - * @return the {@link GenericContainer} providing the service - */ - public C getContainer() { - return this.container; + String getBeanNameSuffix() { + return this.beanNameSuffix; } @Override @@ -100,9 +121,17 @@ public final class ContainerConnectionSource> sources; + + ContainerConnectionSourcesRegistrar(ListableBeanFactory beanFactory, + ConnectionDetailsFactories connectionDetailsFactories, List> sources) { + this.beanFactory = beanFactory; + this.connectionDetailsFactories = connectionDetailsFactories; + this.sources = sources; + } + + void registerBeanDefinitions(BeanDefinitionRegistry registry) { + this.sources.forEach((source) -> registerBeanDefinition(registry, source)); + } + + private void registerBeanDefinition(BeanDefinitionRegistry registry, ContainerConnectionSource source) { + getConnectionDetails(source) + .forEach((connectionDetailsType, connectionDetails) -> registerBeanDefinition(registry, source, + connectionDetailsType, connectionDetails)); + } + + private Map, ConnectionDetails> getConnectionDetails(S source) { + Map, ConnectionDetails> connectionDetails = this.connectionDetailsFactories + .getConnectionDetails(source); + Assert.state(!connectionDetails.isEmpty(), () -> "No connection details created for %s".formatted(source)); + return connectionDetails; + } + + @SuppressWarnings("unchecked") + private void registerBeanDefinition(BeanDefinitionRegistry registry, ContainerConnectionSource source, + Class connectionDetailsType, ConnectionDetails connectionDetails) { + String[] existingBeans = this.beanFactory.getBeanNamesForType(connectionDetailsType); + if (!ObjectUtils.isEmpty(existingBeans)) { + logger.debug(LogMessage.of(() -> "Skipping registration of %s due to existing beans %s".formatted(source, + Arrays.asList(existingBeans)))); + return; + } + String beanName = getBeanName(source, connectionDetails); + Class beanType = (Class) connectionDetails.getClass(); + Supplier beanSupplier = () -> (T) connectionDetails; + logger.debug(LogMessage.of(() -> "Registering '%s' for %s".formatted(beanName, source))); + registry.registerBeanDefinition(beanName, new RootBeanDefinition(beanType, beanSupplier)); + } + + private String getBeanName(ContainerConnectionSource source, ConnectionDetails connectionDetails) { + List parts = new ArrayList<>(); + parts.add(ClassUtils.getShortNameAsProperty(connectionDetails.getClass())); + parts.add("for"); + parts.add(source.getBeanNameSuffix()); + return StringUtils.uncapitalize(parts.stream().map(StringUtils::capitalize).collect(Collectors.joining())); + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/AnnotatedFieldOrigin.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/FieldOrigin.java similarity index 67% rename from spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/AnnotatedFieldOrigin.java rename to spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/FieldOrigin.java index e6aa734574..3205aa4eb9 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/AnnotatedFieldOrigin.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/FieldOrigin.java @@ -16,31 +16,26 @@ package org.springframework.boot.testcontainers.service.connection; -import java.lang.annotation.Annotation; import java.lang.reflect.Field; -import java.util.Objects; import org.springframework.boot.origin.Origin; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; /** - * {@link Origin} backed by a {@link Field} and an {@link Annotation}. + * {@link Origin} backed by a {@link Field}. * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb */ -class AnnotatedFieldOrigin implements Origin { +class FieldOrigin implements Origin { private final Field field; - private final Annotation annotation; - - AnnotatedFieldOrigin(Field field, Annotation annotation) { + FieldOrigin(Field field) { Assert.notNull(field, "Field must not be null"); - Assert.notNull(annotation, "Annotation must not be null"); this.field = field; - this.annotation = annotation; } @Override @@ -51,18 +46,18 @@ class AnnotatedFieldOrigin implements Origin { if (obj == null || getClass() != obj.getClass()) { return false; } - AnnotatedFieldOrigin other = (AnnotatedFieldOrigin) obj; - return this.field.equals(other.field) && this.annotation.equals(other.annotation); + FieldOrigin other = (FieldOrigin) obj; + return this.field.equals(other.field); } @Override public int hashCode() { - return Objects.hash(this.field, this.annotation); + return this.field.hashCode(); } @Override public String toString() { - return this.annotation + " " + this.field; + return ClassUtils.getShortName(this.field.getDeclaringClass()) + "." + this.field.getName(); } } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnection.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnection.java index 9c00786d7a..199d0c958f 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnection.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnection.java @@ -21,15 +21,14 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.testcontainers.containers.Container; + import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; -import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory; +import org.springframework.core.annotation.AliasFor; /** - * Annotation used to indicate that a field provides a service that can be connected to. - * Typically used to meta-annotate a higher-level annotation. - *

- * When used, a {@link ConnectionDetailsFactory} must be registered in - * {@code spring.factories} to provide {@link ConnectionDetails} for the field value. + * Annotation used to indicate that a field or method is a + * {@link ContainerConnectionSource} which provides a service that can be connected to. * * @author Moritz Halbritter * @author Andy Wilkinson @@ -37,14 +36,35 @@ import org.springframework.boot.autoconfigure.service.connection.ConnectionDetai * @since 3.1.0 */ @Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE }) +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) public @interface ServiceConnection { /** - * The type of {@link ConnectionDetails} that can describe how to connect to the - * service. - * @return the connection type + * The name of the service being connected to. If not specified, the image name will + * be used. Container names are used to determine the connection details that should + * be created when a technology-specific {@link Container} subclass is not available. + * This attribute is an alias for {@link #name()}. + * @return the name of the service + * @see #name() + */ + @AliasFor("name") + String value() default ""; + + /** + * The name of the service being connected to. If not specified, the image name will + * be used. Container names are used to determine the connection details that should + * be created when a technology-specific {@link Container} subclass is not available. + * @return the name of the service + * @see #value() + */ + @AliasFor("value") + String name() default ""; + + /** + * A restriction to types of {@link ConnectionDetails} that can be created from this + * connection. The default value does not restrict the types that can be created. + * @return the connection detail types that can be created to establish the connection */ - Class value(); + Class[] type() default {}; } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizer.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizer.java index 2ec0491822..c5e8162090 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizer.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizer.java @@ -17,21 +17,17 @@ package org.springframework.boot.testcontainers.service.connection; import java.util.List; -import java.util.function.Supplier; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactories; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.MergedContextConfiguration; -import org.springframework.util.Assert; /** - * {@link ContextCustomizer} to support registering {@link ConnectionDetails}. + * Spring Test {@link ContextCustomizer} to support registering {@link ConnectionDetails}. * * @author Moritz Halbritter * @author Andy Wilkinson @@ -39,46 +35,30 @@ import org.springframework.util.Assert; */ class ServiceConnectionContextCustomizer implements ContextCustomizer { - private final ConnectionDetailsFactories factories = new ConnectionDetailsFactories(); + private final List> sources; - private final List> sources; + private final ConnectionDetailsFactories connectionDetailsFactories; - ServiceConnectionContextCustomizer(List> sources) { + ServiceConnectionContextCustomizer(List> sources) { + this(sources, new ConnectionDetailsFactories()); + } + + ServiceConnectionContextCustomizer(List> sources, + ConnectionDetailsFactories connectionDetailsFactories) { this.sources = sources; + this.connectionDetailsFactories = connectionDetailsFactories; } @Override public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); if (beanFactory instanceof BeanDefinitionRegistry registry) { - registerServiceConnections(registry); + new ContainerConnectionSourcesRegistrar(beanFactory, this.connectionDetailsFactories, this.sources) + .registerBeanDefinitions(registry); } } - private void registerServiceConnections(BeanDefinitionRegistry registry) { - this.sources.forEach((source) -> registerServiceConnection(registry, source)); - } - - private void registerServiceConnection(BeanDefinitionRegistry registry, ContainerConnectionSource source) { - ConnectionDetails connectionDetails = getConnectionDetails(source); - register(connectionDetails, registry, source.getBeanName()); - } - - @SuppressWarnings("unchecked") - private void register(ConnectionDetails connectionDetails, BeanDefinitionRegistry registry, String beanName) { - Class beanType = (Class) connectionDetails.getClass(); - Supplier beanSupplier = () -> (T) connectionDetails; - BeanDefinition beanDefinition = new RootBeanDefinition(beanType, beanSupplier); - registry.registerBeanDefinition(beanName, beanDefinition); - } - - private ConnectionDetails getConnectionDetails(S source) { - ConnectionDetails connectionDetails = this.factories.getConnectionDetails(source); - Assert.state(connectionDetails != null, () -> "No connection details created for %s".formatted(source)); - return connectionDetails; - } - - List> getSources() { + List> getSources() { return this.sources; } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactory.java index c48dab3c85..09146036a1 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactory.java @@ -21,9 +21,9 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; -import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Container; -import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; +import org.springframework.boot.origin.Origin; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.test.context.ContextConfigurationAttributes; @@ -31,10 +31,14 @@ import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.ContextCustomizerFactory; import org.springframework.test.context.TestContextAnnotationUtils; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; /** - * {@link ContextCustomizerFactory} to support service connections in tests. + * Spring Test {@link ContextCustomizerFactory} to support + * {@link ServiceConnection @ServiceConnection} annotated {@link Container} fields in + * tests. * * @author Moritz Halbritter * @author Andy Wilkinson @@ -45,12 +49,12 @@ class ServiceConnectionContextCustomizerFactory implements ContextCustomizerFact @Override public ContextCustomizer createContextCustomizer(Class testClass, List configAttributes) { - List> sources = new ArrayList<>(); + List> sources = new ArrayList<>(); findSources(testClass, sources); return (sources.isEmpty()) ? null : new ServiceConnectionContextCustomizer(sources); } - private void findSources(Class clazz, List> sources) { + private void findSources(Class clazz, List> sources) { ReflectionUtils.doWithFields(clazz, (field) -> { MergedAnnotations annotations = MergedAnnotations.from(field); annotations.stream(ServiceConnection.class) @@ -61,23 +65,17 @@ class ServiceConnectionContextCustomizerFactory implements ContextCustomizerFact } } - private ContainerConnectionSource createSource(Field field, - MergedAnnotation annotation) { - if (!Modifier.isStatic(field.getModifiers())) { - throw new IllegalStateException("@ServiceConnection field '%s' must be static".formatted(field.getName())); - } - Class connectionDetailsType = getConnectionDetailsType(annotation); + private ContainerConnectionSource createSource(Field field, MergedAnnotation annotation) { + Assert.state(Modifier.isStatic(field.getModifiers()), + () -> "@ServiceConnection field '%s' must be static".formatted(field.getName())); + String beanNameSuffix = StringUtils.capitalize(ClassUtils.getShortNameAsProperty(field.getDeclaringClass())) + + StringUtils.capitalize(field.getName()); + Origin origin = new FieldOrigin(field); Object fieldValue = getFieldValue(field); - Assert.isInstanceOf(GenericContainer.class, fieldValue, - "Field %s must be a %s".formatted(field.getName(), GenericContainer.class.getName())); - GenericContainer container = (GenericContainer) fieldValue; - return new ContainerConnectionSource<>(connectionDetailsType, field, annotation, container); - } - - @SuppressWarnings("unchecked") - private Class getConnectionDetailsType( - MergedAnnotation annotation) { - return (Class) annotation.getClass(MergedAnnotation.VALUE); + Assert.state(Container.class.isInstance(fieldValue), () -> "Field '%s' in %s must be a %s" + .formatted(field.getName(), field.getDeclaringClass().getName(), Container.class.getName())); + Container container = (Container) fieldValue; + return new ContainerConnectionSource<>(beanNameSuffix, origin, container, annotation); } private Object getFieldValue(Field field) { diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitContainerConnectionDetailsFactory.java index 5694541d3e..b4cdd9d142 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitContainerConnectionDetailsFactory.java @@ -24,22 +24,23 @@ import org.testcontainers.containers.RabbitMQContainer; import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** - * {@link ContainerConnectionDetailsFactory} for - * {@link RabbitServiceConnection @RabbitServiceConnection}-annotated - * {@link RabbitMQContainer} fields. + * {@link ContainerConnectionDetailsFactory} to create {@link RabbitConnectionDetails} + * from a {@link ServiceConnection @ServiceConnection}-annotated + * {@link RabbitMQContainer}. * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb */ class RabbitContainerConnectionDetailsFactory - extends ContainerConnectionDetailsFactory { + extends ContainerConnectionDetailsFactory { @Override protected RabbitConnectionDetails getContainerConnectionDetails( - ContainerConnectionSource source) { + ContainerConnectionSource source) { return new RabbitMqContainerConnectionDetails(source); } @@ -52,7 +53,7 @@ class RabbitContainerConnectionDetailsFactory private final RabbitMQContainer container; private RabbitMqContainerConnectionDetails( - ContainerConnectionSource source) { + ContainerConnectionSource source) { super(source); this.container = source.getContainer(); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitServiceConnection.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitServiceConnection.java deleted file mode 100644 index 7731306d57..0000000000 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitServiceConnection.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.testcontainers.service.connection.amqp; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; - -/** - * Annotation that indicates that a field provides a RabbitMQ service connection. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - * @since 3.1.0 - * @see RabbitConnectionDetails - * @see ServiceConnection - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.FIELD, ElementType.TYPE }) -@ServiceConnection(RabbitConnectionDetails.class) -public @interface RabbitServiceConnection { - -} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/cassandra/CassandraContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/cassandra/CassandraContainerConnectionDetailsFactory.java index 1c4f93eaa8..bdd5a5c87c 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/cassandra/CassandraContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/cassandra/CassandraContainerConnectionDetailsFactory.java @@ -23,22 +23,23 @@ import org.testcontainers.containers.CassandraContainer; import org.springframework.boot.autoconfigure.cassandra.CassandraConnectionDetails; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** - * {@link ContainerConnectionDetailsFactory} for - * {@link CassandraServiceConnection @CassandraServiceConnection}-annotated - * {@link CassandraContainer} fields. + * {@link ContainerConnectionDetailsFactory} to create {@link CassandraConnectionDetails} + * from a {@link ServiceConnection @ServiceConnection}-annotated + * {@link CassandraContainer}. * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb */ -class CassandraContainerConnectionDetailsFactory extends - ContainerConnectionDetailsFactory> { +class CassandraContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory> { @Override protected CassandraConnectionDetails getContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { return new CassandraContainerConnectionDetails(source); } @@ -51,7 +52,7 @@ class CassandraContainerConnectionDetailsFactory extends private final CassandraContainer container; private CassandraContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { super(source); this.container = source.getContainer(); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/cassandra/CassandraServiceConnection.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/cassandra/CassandraServiceConnection.java deleted file mode 100644 index 79ae572f23..0000000000 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/cassandra/CassandraServiceConnection.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.testcontainers.service.connection.cassandra; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.boot.autoconfigure.cassandra.CassandraConnectionDetails; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; - -/** - * Annotation that indicates that a field provides a Cassandra service connection. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - * @since 3.1.0 - * @see CassandraConnectionDetails - * @see ServiceConnection - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.FIELD, ElementType.TYPE }) -@ServiceConnection(CassandraConnectionDetails.class) -public @interface CassandraServiceConnection { - -} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/couchbase/CouchbaseContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/couchbase/CouchbaseContainerConnectionDetailsFactory.java index 590a27c40c..6c53d3dabc 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/couchbase/CouchbaseContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/couchbase/CouchbaseContainerConnectionDetailsFactory.java @@ -21,22 +21,23 @@ import org.testcontainers.couchbase.CouchbaseContainer; import org.springframework.boot.autoconfigure.couchbase.CouchbaseConnectionDetails; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** - * {@link ContainerConnectionDetailsFactory} for - * {@link CouchbaseServiceConnection @CouchbaseServiceConnection}-annotated - * {@link CouchbaseContainer} fields. + * {@link ContainerConnectionDetailsFactory} to create {@link CouchbaseConnectionDetails} + * from a {@link ServiceConnection @ServiceConnection}-annotated + * {@link CouchbaseContainer}. * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb */ -class CouchbaseContainerConnectionDetailsFactory extends - ContainerConnectionDetailsFactory { +class CouchbaseContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { @Override protected CouchbaseConnectionDetails getContainerConnectionDetails( - ContainerConnectionSource source) { + ContainerConnectionSource source) { return new CouchbaseContainerConnectionDetails(source); } @@ -49,7 +50,7 @@ class CouchbaseContainerConnectionDetailsFactory extends private final CouchbaseContainer container; private CouchbaseContainerConnectionDetails( - ContainerConnectionSource source) { + ContainerConnectionSource source) { super(source); this.container = source.getContainer(); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/couchbase/CouchbaseServiceConnection.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/couchbase/CouchbaseServiceConnection.java deleted file mode 100644 index e78a809f4e..0000000000 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/couchbase/CouchbaseServiceConnection.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.testcontainers.service.connection.couchbase; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.boot.autoconfigure.couchbase.CouchbaseConnectionDetails; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; - -/** - * Annotation that indicates that a field provides a Couchbase service connection. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - * @since 3.1.0 - * @see CouchbaseConnectionDetails - * @see ServiceConnection - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.FIELD, ElementType.TYPE }) -@ServiceConnection(CouchbaseConnectionDetails.class) -public @interface CouchbaseServiceConnection { - -} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/elasticsearch/ElasticsearchContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/elasticsearch/ElasticsearchContainerConnectionDetailsFactory.java index 4598219b4d..4b38651921 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/elasticsearch/ElasticsearchContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/elasticsearch/ElasticsearchContainerConnectionDetailsFactory.java @@ -18,30 +18,31 @@ package org.springframework.boot.testcontainers.service.connection.elasticsearch import java.util.List; -import org.testcontainers.containers.GenericContainer; +import org.testcontainers.elasticsearch.ElasticsearchContainer; import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails; import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails.Node.Protocol; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** - * {@link ContainerConnectionDetailsFactory} for - * {@link ElasticsearchServiceConnection @ElasticsearchServiceConnection}-annotated - * {@link GenericContainer} fields. + * {@link ContainerConnectionDetailsFactory} to create + * {@link ElasticsearchConnectionDetails} from a + * {@link ServiceConnection @ServiceConnection}-annotated {@link ElasticsearchContainer}. * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb */ -class ElasticsearchContainerConnectionDetailsFactory extends - ContainerConnectionDetailsFactory> { +class ElasticsearchContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { private static final int DEFAULT_PORT = 9200; @Override protected ElasticsearchConnectionDetails getContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource source) { return new ElasticsearchContainerConnectionDetails(source); } @@ -55,7 +56,7 @@ class ElasticsearchContainerConnectionDetailsFactory extends private final List nodes; private ElasticsearchContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource source) { super(source); this.nodes = List.of(new Node(source.getContainer().getHost(), source.getContainer().getMappedPort(DEFAULT_PORT), Protocol.HTTP, null, null)); diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/elasticsearch/ElasticsearchServiceConnection.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/elasticsearch/ElasticsearchServiceConnection.java deleted file mode 100644 index ff2b807bd5..0000000000 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/elasticsearch/ElasticsearchServiceConnection.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.testcontainers.service.connection.elasticsearch; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; - -/** - * Annotation that indicates that a field provides a Elasticsearch service connection. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - * @since 3.1.0 - * @see ElasticsearchConnectionDetails - * @see ServiceConnection - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.FIELD, ElementType.TYPE }) -@ServiceConnection(ElasticsearchConnectionDetails.class) -public @interface ElasticsearchServiceConnection { - -} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/flyway/FlywayContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/flyway/FlywayContainerConnectionDetailsFactory.java index 5ee8e23849..b95b1f6026 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/flyway/FlywayContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/flyway/FlywayContainerConnectionDetailsFactory.java @@ -24,18 +24,18 @@ import org.springframework.boot.testcontainers.service.connection.ContainerConne import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** - * {@link ContainerConnectionDetailsFactory} for - * {@link ServiceConnection @ServiceConnection}-annotated {@link JdbcDatabaseContainer} - * fields that should produce {@link FlywayConnectionDetails}. + * {@link ContainerConnectionDetailsFactory} to create {@link FlywayConnectionDetails} + * from a {@link ServiceConnection @ServiceConnection}-annotated + * {@link JdbcDatabaseContainer}. * * @author Andy Wilkinson */ -class FlywayContainerConnectionDetailsFactory extends - ContainerConnectionDetailsFactory> { +class FlywayContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory> { @Override protected FlywayConnectionDetails getContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { return new FlywayContainerConnectionDetails(source); } @@ -48,7 +48,7 @@ class FlywayContainerConnectionDetailsFactory extends private final JdbcDatabaseContainer container; private FlywayContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { super(source); this.container = source.getContainer(); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/influx/InfluxDbContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/influx/InfluxDbContainerConnectionDetailsFactory.java index 4c7f9d28b8..cb5ecfe7be 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/influx/InfluxDbContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/influx/InfluxDbContainerConnectionDetailsFactory.java @@ -23,22 +23,23 @@ import org.testcontainers.containers.InfluxDBContainer; import org.springframework.boot.autoconfigure.influx.InfluxDbConnectionDetails; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** - * {@link ContainerConnectionDetailsFactory} for - * {@link InfluxDbServiceConnection @InfluxDbServiceConnection}-annotated - * {@link InfluxDBContainer} fields. + * {@link ContainerConnectionDetailsFactory} to create {@link InfluxDbConnectionDetails} + * from a {@link ServiceConnection @ServiceConnection}-annotated + * {@link InfluxDBContainer}. * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb */ -class InfluxDbContainerConnectionDetailsFactory extends - ContainerConnectionDetailsFactory> { +class InfluxDbContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory> { @Override protected InfluxDbConnectionDetails getContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { return new InfluxDbContainerConnectionDetails(source); } @@ -51,7 +52,7 @@ class InfluxDbContainerConnectionDetailsFactory extends private final InfluxDBContainer container; private InfluxDbContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { super(source); this.container = source.getContainer(); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/influx/InfluxDbServiceConnection.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/influx/InfluxDbServiceConnection.java deleted file mode 100644 index d4144ee696..0000000000 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/influx/InfluxDbServiceConnection.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.testcontainers.service.connection.influx; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.boot.autoconfigure.influx.InfluxDbConnectionDetails; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; - -/** - * Annotation that indicates that a field provides an InfluxDB service connection. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - * @since 3.1.0 - * @see InfluxDbConnectionDetails - * @see ServiceConnection - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.FIELD, ElementType.TYPE }) -@ServiceConnection(InfluxDbConnectionDetails.class) -public @interface InfluxDbServiceConnection { - -} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/jdbc/JdbcContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/jdbc/JdbcContainerConnectionDetailsFactory.java index 6e7b72e5c9..da0e073438 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/jdbc/JdbcContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/jdbc/JdbcContainerConnectionDetailsFactory.java @@ -21,22 +21,22 @@ import org.testcontainers.containers.JdbcDatabaseContainer; import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** - * {@link ContainerConnectionDetailsFactory} for - * {@link JdbcServiceConnection @JdbcServiceConnection}-annotated - * {@link JdbcDatabaseContainer} fields. + * {@link ContainerConnectionDetailsFactory} to create {@link JdbcConnectionDetails} from + * a {@link ServiceConnection @ServiceConnection}-annotated {@link JdbcDatabaseContainer}. * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb */ -class JdbcContainerConnectionDetailsFactory extends - ContainerConnectionDetailsFactory> { +class JdbcContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory> { @Override protected JdbcConnectionDetails getContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { return new JdbcContainerConnectionDetails(source); } @@ -49,7 +49,7 @@ class JdbcContainerConnectionDetailsFactory extends private final JdbcDatabaseContainer container; private JdbcContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { super(source); this.container = source.getContainer(); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/jdbc/JdbcServiceConnection.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/jdbc/JdbcServiceConnection.java deleted file mode 100644 index 97d12bf0ca..0000000000 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/jdbc/JdbcServiceConnection.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.testcontainers.service.connection.jdbc; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; - -/** - * Annotation that indicates that a field provides a JDBC database service connection. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @since 3.1.0 - * @see JdbcConnectionDetails - * @see ServiceConnection - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.FIELD, ElementType.TYPE }) -@ServiceConnection(JdbcConnectionDetails.class) -public @interface JdbcServiceConnection { - -} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaContainerConnectionDetailsFactory.java index 465fef1f72..e3ed982a33 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaContainerConnectionDetailsFactory.java @@ -24,22 +24,22 @@ import org.testcontainers.containers.KafkaContainer; import org.springframework.boot.autoconfigure.kafka.KafkaConnectionDetails; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** - * {@link ContainerConnectionDetailsFactory} for - * {@link KafkaServiceConnection @KafkaServiceConnection}-annotated {@link KafkaContainer} - * fields. + * {@link ContainerConnectionDetailsFactory} to create {@link KafkaConnectionDetails} from + * a {@link ServiceConnection @ServiceConnection}-annotated {@link KafkaContainer}. * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb */ class KafkaContainerConnectionDetailsFactory - extends ContainerConnectionDetailsFactory { + extends ContainerConnectionDetailsFactory { @Override protected KafkaConnectionDetails getContainerConnectionDetails( - ContainerConnectionSource source) { + ContainerConnectionSource source) { return new KafkaContainerConnectionDetails(source); } @@ -52,7 +52,7 @@ class KafkaContainerConnectionDetailsFactory private final KafkaContainer container; private KafkaContainerConnectionDetails( - ContainerConnectionSource source) { + ContainerConnectionSource source) { super(source); this.container = source.getContainer(); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaServiceConnection.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaServiceConnection.java deleted file mode 100644 index c81926adb4..0000000000 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaServiceConnection.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.testcontainers.service.connection.kafka; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.boot.autoconfigure.kafka.KafkaConnectionDetails; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; - -/** - * Annotation that indicates that a field provides a Kafka service connection. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @since 3.1.0 - * @see KafkaConnectionDetails - * @see ServiceConnection - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.FIELD, ElementType.TYPE }) -@ServiceConnection(KafkaConnectionDetails.class) -public @interface KafkaServiceConnection { - -} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/liquibase/LiquibaseContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/liquibase/LiquibaseContainerConnectionDetailsFactory.java index 39661d909e..91e3a2d68f 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/liquibase/LiquibaseContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/liquibase/LiquibaseContainerConnectionDetailsFactory.java @@ -24,18 +24,18 @@ import org.springframework.boot.testcontainers.service.connection.ContainerConne import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** - * {@link ContainerConnectionDetailsFactory} for - * {@link ServiceConnection @ServiceConnection}-annotated {@link JdbcDatabaseContainer} - * fields that should produce {@link LiquibaseConnectionDetails}. + * {@link ContainerConnectionDetailsFactory} to create {@link LiquibaseConnectionDetails} + * from a {@link ServiceConnection @ServiceConnection}-annotated + * {@link JdbcDatabaseContainer}. * * @author Andy Wilkinson */ -class LiquibaseContainerConnectionDetailsFactory extends - ContainerConnectionDetailsFactory> { +class LiquibaseContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory> { @Override protected LiquibaseConnectionDetails getContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { return new LiquibaseContainerConnectionDetails(source); } @@ -48,7 +48,7 @@ class LiquibaseContainerConnectionDetailsFactory extends private final JdbcDatabaseContainer container; private LiquibaseContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { super(source); this.container = source.getContainer(); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/mongo/MongoContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/mongo/MongoContainerConnectionDetailsFactory.java index 5ab480122b..3413b2a863 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/mongo/MongoContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/mongo/MongoContainerConnectionDetailsFactory.java @@ -22,22 +22,26 @@ import org.testcontainers.containers.MongoDBContainer; import org.springframework.boot.autoconfigure.mongo.MongoConnectionDetails; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** - * {@link ContainerConnectionDetailsFactory} for - * {@link MongoServiceConnection @MongoServiceConnection}-annotated - * {@link MongoDBContainer} fields. + * {@link ContainerConnectionDetailsFactory} to create {@link MongoConnectionDetails} from + * a {@link ServiceConnection @ServiceConnection}-annotated {@link MongoDBContainer}. * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb */ class MongoContainerConnectionDetailsFactory - extends ContainerConnectionDetailsFactory { + extends ContainerConnectionDetailsFactory { + + MongoContainerConnectionDetailsFactory() { + super(ANY_CONNECTION_NAME, "com.mongodb.ConnectionString"); + } @Override protected MongoConnectionDetails getContainerConnectionDetails( - ContainerConnectionSource source) { + ContainerConnectionSource source) { return new MongoContainerConnectionDetails(source); } @@ -50,7 +54,7 @@ class MongoContainerConnectionDetailsFactory private final ConnectionString connectionString; private MongoContainerConnectionDetails( - ContainerConnectionSource source) { + ContainerConnectionSource source) { super(source); this.connectionString = new ConnectionString(source.getContainer().getReplicaSetUrl()); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/mongo/MongoServiceConnection.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/mongo/MongoServiceConnection.java deleted file mode 100644 index 5df42c68fc..0000000000 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/mongo/MongoServiceConnection.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.testcontainers.service.connection.mongo; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.boot.autoconfigure.mongo.MongoConnectionDetails; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; - -/** - * Annotation that indicates that a field provides a Mongo service connection. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @since 3.1.0 - * @see MongoConnectionDetails - * @see ServiceConnection - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.FIELD, ElementType.TYPE }) -@ServiceConnection(MongoConnectionDetails.class) -public @interface MongoServiceConnection { - -} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/neo4j/Neo4jContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/neo4j/Neo4jContainerConnectionDetailsFactory.java index bf9176de4a..315aa97af7 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/neo4j/Neo4jContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/neo4j/Neo4jContainerConnectionDetailsFactory.java @@ -25,22 +25,26 @@ import org.testcontainers.containers.Neo4jContainer; import org.springframework.boot.autoconfigure.neo4j.Neo4jConnectionDetails; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** - * {@link ContainerConnectionDetailsFactory} for - * {@link Neo4jServiceConnection @Neo4jServiceConnection}-annotated {@link Neo4jContainer} - * fields. + * {@link ContainerConnectionDetailsFactory} to create {@link Neo4jConnectionDetails} from + * a {@link ServiceConnection @ServiceConnection}-annotated {@link Neo4jContainer}. * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb */ class Neo4jContainerConnectionDetailsFactory - extends ContainerConnectionDetailsFactory> { + extends ContainerConnectionDetailsFactory> { + + Neo4jContainerConnectionDetailsFactory() { + super(ANY_CONNECTION_NAME, "org.neo4j.driver.AuthToken"); + } @Override protected Neo4jConnectionDetails getContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { return new Neo4jContainerConnectionDetails(source); } @@ -53,7 +57,7 @@ class Neo4jContainerConnectionDetailsFactory private final Neo4jContainer container; private Neo4jContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { super(source); this.container = source.getContainer(); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/neo4j/Neo4jServiceConnection.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/neo4j/Neo4jServiceConnection.java deleted file mode 100644 index 94df6fd95a..0000000000 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/neo4j/Neo4jServiceConnection.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.testcontainers.service.connection.neo4j; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.boot.autoconfigure.neo4j.Neo4jConnectionDetails; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; - -/** - * Annotation that indicates a field provides a Neo4j service connection. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @since 3.1.0 - * @see Neo4jConnectionDetails - * @see ServiceConnection - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.FIELD, ElementType.TYPE }) -@ServiceConnection(Neo4jConnectionDetails.class) -public @interface Neo4jServiceConnection { - -} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/package-info.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/package-info.java index 86467a2e29..9e6ee317d7 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/package-info.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/package-info.java @@ -15,6 +15,6 @@ */ /** - * General support for auto-configuration of service connections in tests. + * General support for service connections in tests. */ package org.springframework.boot.testcontainers.service.connection; diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/MariaDbR2dbcContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/MariaDbR2dbcContainerConnectionDetailsFactory.java index d71fd05c05..fc4798f323 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/MariaDbR2dbcContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/MariaDbR2dbcContainerConnectionDetailsFactory.java @@ -21,25 +21,28 @@ import org.testcontainers.containers.MariaDBContainer; import org.testcontainers.containers.MariaDBR2DBCDatabaseContainer; import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; -import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** - * {@link ConnectionDetailsFactory} for - * {@link R2dbcServiceConnection @R2dbcServiceConnection}- annotated - * {@link MariaDBContainer} fields. + * {@link ContainerConnectionDetailsFactory} to create {@link R2dbcConnectionDetails} from + * a {@link ServiceConnection @ServiceConnection}-annotated {@link MariaDBContainer}. * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb */ class MariaDbR2dbcContainerConnectionDetailsFactory - extends ContainerConnectionDetailsFactory> { + extends ContainerConnectionDetailsFactory> { + + MariaDbR2dbcContainerConnectionDetailsFactory() { + super(ANY_CONNECTION_NAME, "io.r2dbc.spi.ConnectionFactoryOptions"); + } @Override public R2dbcConnectionDetails getContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { return new R2dbcDatabaseContainerConnectionDetails(source.getContainer()); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/SqlServerR2dbcContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/MsSqlServerR2dbcContainerConnectionDetailsFactory.java similarity index 75% rename from spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/SqlServerR2dbcContainerConnectionDetailsFactory.java rename to spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/MsSqlServerR2dbcContainerConnectionDetailsFactory.java index 3824be164e..6a63a7ebb3 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/SqlServerR2dbcContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/MsSqlServerR2dbcContainerConnectionDetailsFactory.java @@ -21,25 +21,28 @@ import org.testcontainers.containers.MSSQLR2DBCDatabaseContainer; import org.testcontainers.containers.MSSQLServerContainer; import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; -import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** - * {@link ConnectionDetailsFactory} for - * {@link R2dbcServiceConnection @R2dbcServiceConnection}- annotated - * {@link MSSQLServerContainer} fields. + * {@link ContainerConnectionDetailsFactory} to create {@link R2dbcConnectionDetails} from + * a {@link ServiceConnection @ServiceConnection}-annotated {@link MSSQLServerContainer}. * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb */ -class SqlServerR2dbcContainerConnectionDetailsFactory extends - ContainerConnectionDetailsFactory> { +class MsSqlServerR2dbcContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory> { + + MsSqlServerR2dbcContainerConnectionDetailsFactory() { + super(ANY_CONNECTION_NAME, "io.r2dbc.spi.ConnectionFactoryOptions"); + } @Override public R2dbcConnectionDetails getContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { return new R2dbcDatabaseContainerConnectionDetails(source.getContainer()); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/MySqlR2dbcContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/MySqlR2dbcContainerConnectionDetailsFactory.java index c6f2837666..ebd31786df 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/MySqlR2dbcContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/MySqlR2dbcContainerConnectionDetailsFactory.java @@ -21,25 +21,28 @@ import org.testcontainers.containers.MySQLContainer; import org.testcontainers.containers.MySQLR2DBCDatabaseContainer; import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; -import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** - * {@link ConnectionDetailsFactory} for - * {@link R2dbcServiceConnection @R2dbcServiceConnection}- annotated - * {@link MySQLContainer} fields. + * {@link ContainerConnectionDetailsFactory} to create {@link R2dbcConnectionDetails} from + * a {@link ServiceConnection @ServiceConnection}-annotated {@link MySQLContainer}. * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb */ class MySqlR2dbcContainerConnectionDetailsFactory - extends ContainerConnectionDetailsFactory> { + extends ContainerConnectionDetailsFactory> { + + MySqlR2dbcContainerConnectionDetailsFactory() { + super(ANY_CONNECTION_NAME, "io.r2dbc.spi.ConnectionFactoryOptions"); + } @Override public R2dbcConnectionDetails getContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { return new R2dbcDatabaseContainerConnectionDetails(source.getContainer()); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/PostgresR2dbcContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/PostgresR2dbcContainerConnectionDetailsFactory.java index 449d4eb86e..637fb69c30 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/PostgresR2dbcContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/PostgresR2dbcContainerConnectionDetailsFactory.java @@ -21,24 +21,28 @@ import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.containers.PostgreSQLR2DBCDatabaseContainer; import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; -import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** - * {@link ConnectionDetailsFactory} for {@link R2dbcServiceConnection @R2dbcConnection} - * annotated {@link PostgreSQLContainer} fields. + * {@link ContainerConnectionDetailsFactory} to create {@link R2dbcConnectionDetails} from + * a {@link ServiceConnection @ServiceConnection}-annotated {@link PostgreSQLContainer}. * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb */ -class PostgresR2dbcContainerConnectionDetailsFactory extends - ContainerConnectionDetailsFactory> { +class PostgresR2dbcContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory> { + + PostgresR2dbcContainerConnectionDetailsFactory() { + super(ANY_CONNECTION_NAME, "io.r2dbc.spi.ConnectionFactoryOptions"); + } @Override public R2dbcConnectionDetails getContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { return new R2dbcDatabaseContainerConnectionDetails(source.getContainer()); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/R2dbcServiceConnection.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/R2dbcServiceConnection.java deleted file mode 100644 index 1cdec29dac..0000000000 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/r2dbc/R2dbcServiceConnection.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.testcontainers.service.connection.r2dbc; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.boot.autoconfigure.r2dbc.R2dbcConnectionDetails; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; - -/** - * Annotation that indicates a field provides a R2DBC database service connection. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @since 3.1.0 - * @see ServiceConnection - * @see R2dbcConnectionDetails - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.FIELD, ElementType.TYPE }) -@ServiceConnection(R2dbcConnectionDetails.class) -public @interface R2dbcServiceConnection { - -} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactory.java index b9d4706f64..77d1d97375 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactory.java @@ -16,27 +16,33 @@ package org.springframework.boot.testcontainers.service.connection.redis; +import org.testcontainers.containers.Container; import org.testcontainers.containers.GenericContainer; import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** - * {@link ContainerConnectionDetailsFactory} for - * {@link RedisServiceConnection @RedisServiceConnection}-annotated - * {@link GenericContainer} fields. + * {@link ContainerConnectionDetailsFactory} to create {@link RedisConnectionDetails} from + * a {@link ServiceConnection @ServiceConnection}-annotated {@link GenericContainer} using + * the {@code "redis"} image. * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb */ class RedisContainerConnectionDetailsFactory - extends ContainerConnectionDetailsFactory> { + extends ContainerConnectionDetailsFactory> { + + RedisContainerConnectionDetailsFactory() { + super("redis"); + } @Override public RedisConnectionDetails getContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { return new RedisContainerConnectionDetails(source); } @@ -49,7 +55,7 @@ class RedisContainerConnectionDetailsFactory private final Standalone standalone; private RedisContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { super(source); this.standalone = Standalone.of(source.getContainer().getHost(), source.getContainer().getFirstMappedPort()); diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/redis/RedisServiceConnection.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/redis/RedisServiceConnection.java deleted file mode 100644 index 198b651a6b..0000000000 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/redis/RedisServiceConnection.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.testcontainers.service.connection.redis; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; - -/** - * Annotation that indicates that a field provides a Redis service connection. - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @since 3.1.0 - * @see RedisConnectionDetails - * @see ServiceConnection - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.FIELD, ElementType.TYPE }) -@ServiceConnection(RedisConnectionDetails.class) -public @interface RedisServiceConnection { - -} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories index 91afe70086..e28a8d5b30 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories @@ -8,7 +8,6 @@ org.springframework.boot.testcontainers.service.connection.amqp.RabbitContainerC org.springframework.boot.testcontainers.service.connection.cassandra.CassandraContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.couchbase.CouchbaseContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.flyway.FlywayContainerConnectionDetailsFactory,\ -org.springframework.boot.testcontainers.service.connection.redis.RedisContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.elasticsearch.ElasticsearchContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.influx.InfluxDbContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.jdbc.JdbcContainerConnectionDetailsFactory,\ @@ -17,6 +16,7 @@ org.springframework.boot.testcontainers.service.connection.liquibase.LiquibaseCo org.springframework.boot.testcontainers.service.connection.mongo.MongoContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.neo4j.Neo4jContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.r2dbc.MariaDbR2dbcContainerConnectionDetailsFactory,\ +org.springframework.boot.testcontainers.service.connection.r2dbc.MsSqlServerR2dbcContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.r2dbc.MySqlR2dbcContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.r2dbc.PostgresR2dbcContainerConnectionDetailsFactory,\ -org.springframework.boot.testcontainers.service.connection.r2dbc.SqlServerR2dbcContainerConnectionDetailsFactory +org.springframework.boot.testcontainers.service.connection.redis.RedisContainerConnectionDetailsFactory diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactoryTests.java new file mode 100644 index 0000000000..f502eb9471 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactoryTests.java @@ -0,0 +1,156 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection; + +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.elasticsearch.ElasticsearchContainer; + +import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory; +import org.springframework.boot.origin.Origin; +import org.springframework.core.annotation.MergedAnnotation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link ContainerConnectionDetailsFactory}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + */ +class ContainerConnectionDetailsFactoryTests { + + private String beanNameSuffix; + + private Origin origin; + + private JdbcDatabaseContainer container; + + private MergedAnnotation annotation; + + private ContainerConnectionSource source; + + @BeforeEach + void setup() { + this.beanNameSuffix = "MyBean"; + this.origin = mock(Origin.class); + this.container = mock(PostgreSQLContainer.class); + this.annotation = MergedAnnotation.of(ServiceConnection.class, + Map.of("name", "myname", "type", new Class[0])); + this.source = new ContainerConnectionSource<>(this.beanNameSuffix, this.origin, this.container, + this.annotation); + } + + @Test + void getConnectionDetailsWhenTypesMatchAndNoNameRestrictionReturnsDetails() { + TestContainerConnectionDetailsFactory factory = new TestContainerConnectionDetailsFactory(); + ConnectionDetails connectionDetails = getConnectionDetails(factory, this.source); + assertThat(connectionDetails).isNotNull(); + } + + @Test + void getConnectionDetailsWhenTypesMatchAndNameRestrictionMatchesReturnsDetails() { + TestContainerConnectionDetailsFactory factory = new TestContainerConnectionDetailsFactory("myname"); + ConnectionDetails connectionDetails = getConnectionDetails(factory, this.source); + assertThat(connectionDetails).isNotNull(); + } + + @Test + void getConnectionDetailsWhenTypesMatchAndNameRestrictionDoesNotMatchReturnsNull() { + TestContainerConnectionDetailsFactory factory = new TestContainerConnectionDetailsFactory("notmyname"); + ConnectionDetails connectionDetails = getConnectionDetails(factory, this.source); + assertThat(connectionDetails).isNull(); + } + + @Test + void getConnectionDetailsWhenContainerTypeDoesNotMatchReturnsNull() { + ElasticsearchContainer container = mock(ElasticsearchContainer.class); + ContainerConnectionSource source = new ContainerConnectionSource<>(this.beanNameSuffix, this.origin, + container, this.annotation); + TestContainerConnectionDetailsFactory factory = new TestContainerConnectionDetailsFactory(); + ConnectionDetails connectionDetails = getConnectionDetails(factory, source); + assertThat(connectionDetails).isNull(); + } + + @Test + void getConnectionDetailsHasOrigin() { + TestContainerConnectionDetailsFactory factory = new TestContainerConnectionDetailsFactory(); + ConnectionDetails connectionDetails = getConnectionDetails(factory, this.source); + assertThat(Origin.from(connectionDetails)).isSameAs(this.origin); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private ConnectionDetails getConnectionDetails(ConnectionDetailsFactory factory, + ContainerConnectionSource source) { + return ((ConnectionDetailsFactory) factory).getConnectionDetails(source); + } + + /** + * Test {@link ContainerConnectionDetailsFactory}. + */ + static class TestContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory> { + + TestContainerConnectionDetailsFactory() { + this(ANY_CONNECTION_NAME); + } + + TestContainerConnectionDetailsFactory(String connectionName) { + super(connectionName); + } + + @Override + protected JdbcConnectionDetails getContainerConnectionDetails( + ContainerConnectionSource> source) { + return new TestContainerConnectionDetails(source); + } + + static class TestContainerConnectionDetails extends ContainerConnectionDetails + implements JdbcConnectionDetails { + + TestContainerConnectionDetails(ContainerConnectionSource source) { + super(source); + } + + @Override + public String getUsername() { + return "user"; + } + + @Override + public String getPassword() { + return "secret"; + } + + @Override + public String getJdbcUrl() { + return "jdbc:example"; + } + + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionSourceTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionSourceTests.java new file mode 100644 index 0000000000..a2c7bb047d --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionSourceTests.java @@ -0,0 +1,183 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection; + +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.elasticsearch.ElasticsearchContainer; + +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails; +import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; +import org.springframework.boot.origin.Origin; +import org.springframework.core.annotation.MergedAnnotation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link ContainerConnectionSource}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + */ +class ContainerConnectionSourceTests { + + private String beanNameSuffix; + + private Origin origin; + + private JdbcDatabaseContainer container; + + private MergedAnnotation annotation; + + private ContainerConnectionSource source; + + @BeforeEach + void setup() { + this.beanNameSuffix = "MyBean"; + this.origin = mock(Origin.class); + this.container = mock(PostgreSQLContainer.class); + given(this.container.getDockerImageName()).willReturn("postgres"); + this.annotation = MergedAnnotation.of(ServiceConnection.class, Map.of("name", "", "type", new Class[0])); + this.source = new ContainerConnectionSource<>(this.beanNameSuffix, this.origin, this.container, + this.annotation); + } + + @Test + void acceptsWhenContainerIsNotInstanceOfContainerTypeReturnsFalse() { + String connectionName = null; + Class connectionDetailsType = JdbcConnectionDetails.class; + Class containerType = ElasticsearchContainer.class; + assertThat(this.source.accepts(connectionName, connectionDetailsType, containerType)).isFalse(); + } + + @Test + void acceptsWhenContainerIsInstanceOfContainerTypeReturnsTrue() { + String connectionName = null; + Class connectionDetailsType = JdbcConnectionDetails.class; + Class containerType = JdbcDatabaseContainer.class; + assertThat(this.source.accepts(connectionName, connectionDetailsType, containerType)).isTrue(); + } + + @Test + void acceptsWhenConnectionNameDoesNotMatchNameTakenFromAnnotationReturnsFalse() { + setupSourceAnnotatedWithName("myname"); + String connectionName = "othername"; + Class connectionDetailsType = JdbcConnectionDetails.class; + Class containerType = JdbcDatabaseContainer.class; + assertThat(this.source.accepts(connectionName, connectionDetailsType, containerType)).isFalse(); + } + + @Test + void acceptsWhenConnectionNameDoesNotMatchNameTakenFromContainerReturnsFalse() { + String connectionName = "othername"; + Class connectionDetailsType = JdbcConnectionDetails.class; + Class containerType = JdbcDatabaseContainer.class; + assertThat(this.source.accepts(connectionName, connectionDetailsType, containerType)).isFalse(); + } + + @Test + void acceptsWhenConnectionNameIsUnrestrictedReturnsTrue() { + String connectionName = null; + Class connectionDetailsType = JdbcConnectionDetails.class; + Class containerType = JdbcDatabaseContainer.class; + assertThat(this.source.accepts(connectionName, connectionDetailsType, containerType)).isTrue(); + } + + @Test + void acceptsWhenConnectionNameMatchesNameTakenFromAnnotationReturnsTrue() { + setupSourceAnnotatedWithName("myname"); + String connectionName = "myname"; + Class connectionDetailsType = JdbcConnectionDetails.class; + Class containerType = JdbcDatabaseContainer.class; + assertThat(this.source.accepts(connectionName, connectionDetailsType, containerType)).isTrue(); + } + + @Test + void acceptsWhenConnectionNameMatchesNameTakenFromContainerReturnsTrue() { + String connectionName = "postgres"; + Class connectionDetailsType = JdbcConnectionDetails.class; + Class containerType = JdbcDatabaseContainer.class; + assertThat(this.source.accepts(connectionName, connectionDetailsType, containerType)).isTrue(); + } + + @Test + void acceptsWhenConnectionDetailsTypeNotInAnnotationRestrictionReturnsFalse() { + setupSourceAnnotatedWithType(ElasticsearchConnectionDetails.class); + String connectionName = null; + Class connectionDetailsType = JdbcConnectionDetails.class; + Class containerType = JdbcDatabaseContainer.class; + assertThat(this.source.accepts(connectionName, connectionDetailsType, containerType)).isFalse(); + } + + @Test + void acceptsWhenConnectionDetailsTypeInAnnotationRestrictionReturnsTrue() { + setupSourceAnnotatedWithType(JdbcConnectionDetails.class); + String connectionName = null; + Class connectionDetailsType = JdbcConnectionDetails.class; + Class containerType = JdbcDatabaseContainer.class; + assertThat(this.source.accepts(connectionName, connectionDetailsType, containerType)).isTrue(); + } + + @Test + void acceptsWhenConnectionDetailsTypeIsNotRestrictedReturnsTrue() { + String connectionName = null; + Class connectionDetailsType = JdbcConnectionDetails.class; + Class containerType = JdbcDatabaseContainer.class; + assertThat(this.source.accepts(connectionName, connectionDetailsType, containerType)).isTrue(); + } + + @Test + void getBeanNameSuffixReturnsBeanNameSuffix() { + assertThat(this.source.getBeanNameSuffix()).isEqualTo(this.beanNameSuffix); + } + + @Test + void getOriginReturnsOrigin() { + assertThat(this.source.getOrigin()).isEqualTo(this.origin); + } + + @Test + void getContainerReturnsContainer() { + assertThat(this.source.getContainer()).isSameAs(this.container); + } + + @Test + void toStringReturnsSensibleString() { + assertThat(this.source.toString()).startsWith("@ServiceConnection source for Mock for Origin"); + } + + private void setupSourceAnnotatedWithName(String name) { + this.annotation = MergedAnnotation.of(ServiceConnection.class, Map.of("name", name, "type", new Class[0])); + this.source = new ContainerConnectionSource<>(this.beanNameSuffix, this.origin, this.container, + this.annotation); + } + + private void setupSourceAnnotatedWithType(Class type) { + this.annotation = MergedAnnotation.of(ServiceConnection.class, + Map.of("name", "", "type", new Class[] { type })); + this.source = new ContainerConnectionSource<>(this.beanNameSuffix, this.origin, this.container, + this.annotation); + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/FieldOriginTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/FieldOriginTests.java new file mode 100644 index 0000000000..d3b89a8d61 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/FieldOriginTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.origin.Origin; +import org.springframework.util.ReflectionUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link FieldOrigin}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + */ +class FieldOriginTests { + + @Test + void createWhenFieldIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> new FieldOrigin(null)) + .withMessage("Field must not be null"); + } + + @Test + void equalsAndHashCode() { + Origin o1 = new FieldOrigin(ReflectionUtils.findField(Fields.class, "one")); + Origin o2 = new FieldOrigin(ReflectionUtils.findField(Fields.class, "one")); + Origin o3 = new FieldOrigin(ReflectionUtils.findField(Fields.class, "two")); + assertThat(o1).isEqualTo(o1).isEqualTo(o2).isNotEqualTo(o3); + assertThat(o1.hashCode()).isEqualTo(o2.hashCode()); + } + + @Test + void toStringReturnsSensibleString() { + Origin origin = new FieldOrigin(ReflectionUtils.findField(Fields.class, "one")); + assertThat(origin).hasToString("FieldOriginTests.Fields.one"); + } + + static class Fields { + + String one; + + String two; + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactoryTests.java index e8a0a8ffb3..9917652e44 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactoryTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactoryTests.java @@ -16,32 +16,35 @@ package org.springframework.boot.testcontainers.service.connection; +import java.io.ByteArrayInputStream; +import java.io.InputStream; + import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.testcontainers.containers.Container; import org.testcontainers.containers.GenericContainer; -import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; -import org.springframework.boot.testcontainers.service.connection.ServiceConnectionContextCustomizerFactoryTests.ServiceConnections.NestedClass; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link ServiceConnectionContextCustomizerFactory}. * + * @author Moritz Halbritter * @author Andy Wilkinson + * @author Phillip Webb */ -public class ServiceConnectionContextCustomizerFactoryTests { +class ServiceConnectionContextCustomizerFactoryTests { private final ServiceConnectionContextCustomizerFactory factory = new ServiceConnectionContextCustomizerFactory(); @Test - void whenClassHasNoServiceConnectionsThenCreateReturnsNull() { + void createContextCustomizerWhenNoServiceConnectionsReturnsNull() { assertThat(this.factory.createContextCustomizer(NoServiceConnections.class, null)).isNull(); } @Test - void whenClassHasServiceConnectionsThenCreateReturnsCustomizer() { + void createContextCustomizerWhenClassHasServiceConnectionsReturnsCustomizer() { ServiceConnectionContextCustomizer customizer = (ServiceConnectionContextCustomizer) this.factory .createContextCustomizer(ServiceConnections.class, null); assertThat(customizer).isNotNull(); @@ -49,37 +52,79 @@ public class ServiceConnectionContextCustomizerFactoryTests { } @Test - void whenEnclosingClassHasServiceConnectionsThenCreateReturnsCustomizer() { + void createContextCustomizerWhenEnclosingClassHasServiceConnectionsReturnsCustomizer() { ServiceConnectionContextCustomizer customizer = (ServiceConnectionContextCustomizer) this.factory - .createContextCustomizer(NestedClass.class, null); + .createContextCustomizer(ServiceConnections.NestedClass.class, null); assertThat(customizer).isNotNull(); assertThat(customizer.getSources()).hasSize(3); } @Test - void whenClassHasNonStaticServiceConnectionThenCreateShouldFailWithHelpfulIllegalStateException() { + void createContextCustomizerWhenClassHasNonStaticServiceConnectionFailsWithHepfulException() { assertThatIllegalStateException() .isThrownBy(() -> this.factory.createContextCustomizer(NonStaticServiceConnection.class, null)) .withMessage("@ServiceConnection field 'service' must be static"); + + } + + @Test + void createContextCustomizerWhenClassHasAnnotationOnNonConnectionFieldFailsWithHepfulException() { + assertThatIllegalStateException() + .isThrownBy(() -> this.factory.createContextCustomizer(ServiceConnectionOnWrongFieldType.class, null)) + .withMessage("Field 'service2' in " + ServiceConnectionOnWrongFieldType.class.getName() + + " must be a org.testcontainers.containers.Container"); + } + + @Test + void createContextCustomizerCreatesCustomizerSourceWithSensibleBeanNameSuffix() { + ServiceConnectionContextCustomizer customizer = (ServiceConnectionContextCustomizer) this.factory + .createContextCustomizer(SingleServiceConnection.class, null); + ContainerConnectionSource source = customizer.getSources().get(0); + assertThat(source.getBeanNameSuffix()).isEqualTo("SingleServiceConnectionService1"); + } + + @Test + void createContextCustomizerCreatesCustomizerSourceWithSensibleOrigin() { + ServiceConnectionContextCustomizer customizer = (ServiceConnectionContextCustomizer) this.factory + .createContextCustomizer(SingleServiceConnection.class, null); + ContainerConnectionSource source = customizer.getSources().get(0); + assertThat(source.getOrigin()) + .hasToString("ServiceConnectionContextCustomizerFactoryTests.SingleServiceConnection.service1"); + } + + @Test + void createContextCustomizerCreatesCustomizerSourceWithSensibleToString() { + ServiceConnectionContextCustomizer customizer = (ServiceConnectionContextCustomizer) this.factory + .createContextCustomizer(SingleServiceConnection.class, null); + ContainerConnectionSource source = customizer.getSources().get(0); + assertThat(source).hasToString( + "@ServiceConnection source for ServiceConnectionContextCustomizerFactoryTests.SingleServiceConnection.service1"); } static class NoServiceConnections { } + static class SingleServiceConnection { + + @ServiceConnection + private static GenericContainer service1 = new MockContainer(); + + } + static class ServiceConnections { - @ServiceConnection(TestConnectionDetails.class) - private static GenericContainer service1 = new GenericContainer<>("example"); + @ServiceConnection + private static Container service1 = new MockContainer(); - @ServiceConnection(TestConnectionDetails.class) - private static GenericContainer service2 = new GenericContainer<>("example"); + @ServiceConnection + private static Container service2 = new MockContainer(); @Nested class NestedClass { - @ServiceConnection(TestConnectionDetails.class) - private static GenericContainer service3 = new GenericContainer<>("example"); + @ServiceConnection + private static Container service3 = new MockContainer(); } @@ -87,12 +132,35 @@ public class ServiceConnectionContextCustomizerFactoryTests { static class NonStaticServiceConnection { - @ServiceConnection(TestConnectionDetails.class) - private GenericContainer service = new GenericContainer<>("example"); + @ServiceConnection + private Container service = new MockContainer("example"); } - static class TestConnectionDetails implements ConnectionDetails { + static class ServiceConnectionOnWrongFieldType { + + @ServiceConnection + private static InputStream service2 = new ByteArrayInputStream(new byte[0]); + + } + + static class MockContainer extends GenericContainer { + + private final String dockerImageName; + + MockContainer() { + this("example"); + } + + MockContainer(String dockerImageName) { + super(dockerImageName); + this.dockerImageName = dockerImageName; + } + + @Override + public String getDockerImageName() { + return this.dockerImageName; + } } diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerTests.java new file mode 100644 index 0000000000..efa0e9e80f --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerTests.java @@ -0,0 +1,133 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.service.connection; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.PostgreSQLContainer; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactories; +import org.springframework.boot.origin.Origin; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.test.context.MergedContextConfiguration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; + +/** + * Tests for {@link ServiceConnectionContextCustomizer}. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Phillip Webb + */ +class ServiceConnectionContextCustomizerTests { + + private String beanNameSuffix; + + private Origin origin; + + private JdbcDatabaseContainer container; + + private MergedAnnotation annotation; + + private ContainerConnectionSource source; + + private ConnectionDetailsFactories factories; + + @BeforeEach + void setup() { + this.beanNameSuffix = "MyBean"; + this.origin = mock(Origin.class); + this.container = mock(PostgreSQLContainer.class); + this.annotation = MergedAnnotation.of(ServiceConnection.class, + Map.of("name", "myname", "type", new Class[0])); + this.source = new ContainerConnectionSource<>(this.beanNameSuffix, this.origin, this.container, + this.annotation); + this.factories = mock(ConnectionDetailsFactories.class); + } + + @Test + void customizeContextRegistersServiceConnections() { + ServiceConnectionContextCustomizer customizer = new ServiceConnectionContextCustomizer(List.of(this.source), + this.factories); + ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class); + DefaultListableBeanFactory beanFactory = spy(new DefaultListableBeanFactory()); + given(context.getBeanFactory()).willReturn(beanFactory); + MergedContextConfiguration mergedConfig = mock(MergedContextConfiguration.class); + JdbcConnectionDetails connectionDetails = new TestJdbcConnectionDetails(); + given(this.factories.getConnectionDetails(this.source)) + .willReturn(Map.of(JdbcConnectionDetails.class, connectionDetails)); + customizer.customizeContext(context, mergedConfig); + ArgumentCaptor beanDefinitionCaptor = ArgumentCaptor.forClass(BeanDefinition.class); + then(beanFactory).should() + .registerBeanDefinition(eq("testJdbcConnectionDetailsForMyBean"), beanDefinitionCaptor.capture()); + RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionCaptor.getValue(); + assertThat(beanDefinition.getInstanceSupplier().get()).isSameAs(connectionDetails); + assertThat(beanDefinition.getBeanClass()).isEqualTo(TestJdbcConnectionDetails.class); + } + + @Test + void customizeContextWhenFactoriesHasNoConnectionDetailsThrowsException() { + ServiceConnectionContextCustomizer customizer = new ServiceConnectionContextCustomizer(List.of(this.source), + this.factories); + ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class); + DefaultListableBeanFactory beanFactory = spy(new DefaultListableBeanFactory()); + given(context.getBeanFactory()).willReturn(beanFactory); + MergedContextConfiguration mergedConfig = mock(MergedContextConfiguration.class); + assertThatIllegalStateException().isThrownBy(() -> customizer.customizeContext(context, mergedConfig)) + .withMessageStartingWith("No connection details created for @ServiceConnection source"); + } + + /** + * Test {@link JdbcConnectionDetails}. + */ + static class TestJdbcConnectionDetails implements JdbcConnectionDetails { + + @Override + public String getUsername() { + return null; + } + + @Override + public String getPassword() { + return null; + } + + @Override + public String getJdbcUrl() { + return null; + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitServiceConnectionTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitContainerConnectionDetailsFactoryIntegrationTests.java similarity index 88% rename from spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitServiceConnectionTests.java rename to spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitContainerConnectionDetailsFactoryIntegrationTests.java index 0b45d1a0f7..8567d2f03b 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitServiceConnectionTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/amqp/RabbitContainerConnectionDetailsFactoryIntegrationTests.java @@ -25,7 +25,6 @@ import org.junit.jupiter.api.Test; import org.testcontainers.containers.RabbitMQContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.utility.DockerImageName; import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.RabbitListener; @@ -34,6 +33,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -41,7 +42,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link RabbitServiceConnection}. + * Tests for {@link RabbitContainerConnectionDetailsFactory}. * * @author Moritz Halbritter * @author Andy Wilkinson @@ -49,11 +50,12 @@ import static org.assertj.core.api.Assertions.assertThat; */ @SpringJUnitConfig @Testcontainers(disabledWithoutDocker = true) -class RabbitServiceConnectionTests { +class RabbitContainerConnectionDetailsFactoryIntegrationTests { @Container - @RabbitServiceConnection - static final RabbitMQContainer rabbit = new RabbitMQContainer(DockerImageName.parse("rabbitmq:3.11-alpine")); + @ServiceConnection + static final RabbitMQContainer rabbit = new RabbitMQContainer(DockerImageNames.rabbit()) + .withStartupTimeout(Duration.ofMinutes(4)); @Autowired(required = false) private RabbitConnectionDetails connectionDetails; diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/influx/InfluxDbServiceConnectionTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/influx/InfluxDbContainerConnectionDetailsFactoryIntegrationTests.java similarity index 79% rename from spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/influx/InfluxDbServiceConnectionTests.java rename to spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/influx/InfluxDbContainerConnectionDetailsFactoryIntegrationTests.java index 50cb54114e..3d33d1688b 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/influx/InfluxDbServiceConnectionTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/influx/InfluxDbContainerConnectionDetailsFactoryIntegrationTests.java @@ -21,18 +21,19 @@ import org.junit.jupiter.api.Test; import org.testcontainers.containers.InfluxDBContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.utility.DockerImageName; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link InfluxDbServiceConnection}. + * Tests for {@link InfluxDbContainerConnectionDetailsFactory}. * * @author Moritz Halbritter * @author Andy Wilkinson @@ -40,21 +41,18 @@ import static org.assertj.core.api.Assertions.assertThat; */ @SpringJUnitConfig @Testcontainers(disabledWithoutDocker = true) -class InfluxDbServiceConnectionTests { - - private static final String INFLUXDB_VERSION = "2.6.1"; +class InfluxDbContainerConnectionDetailsFactoryIntegrationTests { @Container - @InfluxDbServiceConnection - static final InfluxDBContainer influxDbService = new InfluxDBContainer<>( - DockerImageName.parse("influxdb").withTag(INFLUXDB_VERSION)); + @ServiceConnection + static final InfluxDBContainer influxDbService = new InfluxDBContainer<>(DockerImageNames.influxDb()); @Autowired private InfluxDB influxDb; @Test void connectionCanBeMadeToInfluxDbContainer() { - assertThat(this.influxDb.version()).isEqualTo("v" + INFLUXDB_VERSION); + assertThat(this.influxDb.version()).isEqualTo("v" + DockerImageNames.influxDb().getVersionPart()); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaServiceConnectionTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaContainerConnectionDetailsFactoryIntegrationTests.java similarity index 89% rename from spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaServiceConnectionTests.java rename to spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaContainerConnectionDetailsFactoryIntegrationTests.java index 864ee31b21..713a140277 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaServiceConnectionTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/kafka/KafkaContainerConnectionDetailsFactoryIntegrationTests.java @@ -25,11 +25,12 @@ import org.junit.jupiter.api.Test; import org.testcontainers.containers.KafkaContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.utility.DockerImageName; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.kafka.annotation.KafkaListener; @@ -40,7 +41,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link KafkaServiceConnection}. + * Tests for {@link KafkaContainerConnectionDetailsFactory}. * * @author Moritz Halbritter * @author Andy Wilkinson @@ -50,11 +51,11 @@ import static org.assertj.core.api.Assertions.assertThat; @Testcontainers(disabledWithoutDocker = true) @TestPropertySource(properties = { "spring.kafka.consumer.group-id=test-group", "spring.kafka.consumer.auto-offset-reset=earliest" }) -class KafkaServiceConnectionTests { +class KafkaContainerConnectionDetailsFactoryIntegrationTests { @Container - @KafkaServiceConnection - static final KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:5.4.3")); + @ServiceConnection + static final KafkaContainer kafka = new KafkaContainer(DockerImageNames.kafka()); @Autowired private KafkaTemplate kafkaTemplate; diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-cache/src/redisTest/java/smoketest/cache/SampleCacheApplicationRedisTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-cache/src/redisTest/java/smoketest/cache/SampleCacheApplicationRedisTests.java index 51e0017da7..9bd8334830 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-cache/src/redisTest/java/smoketest/cache/SampleCacheApplicationRedisTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-cache/src/redisTest/java/smoketest/cache/SampleCacheApplicationRedisTests.java @@ -22,7 +22,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.testcontainers.service.connection.redis.RedisServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.RedisContainer; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; @@ -34,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; class SampleCacheApplicationRedisTests { @Container - @RedisServiceConnection + @ServiceConnection private static final RedisContainer redis = new RedisContainer(); @Autowired diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc-flyway/src/test/java/smoketest/data/r2dbc/CityRepositoryTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc-flyway/src/test/java/smoketest/data/r2dbc/CityRepositoryTests.java index df31e1cddb..3b3348a232 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc-flyway/src/test/java/smoketest/data/r2dbc/CityRepositoryTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc-flyway/src/test/java/smoketest/data/r2dbc/CityRepositoryTests.java @@ -23,10 +23,8 @@ import org.testcontainers.junit.jupiter.Testcontainers; import reactor.test.StepVerifier; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.flyway.FlywayConnectionDetails; import org.springframework.boot.test.autoconfigure.data.r2dbc.DataR2dbcTest; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testcontainers.service.connection.r2dbc.R2dbcServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import static org.assertj.core.api.Assertions.assertThat; @@ -43,8 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat; class CityRepositoryTests { @Container - @R2dbcServiceConnection - @ServiceConnection(FlywayConnectionDetails.class) + @ServiceConnection static PostgreSQLContainer postgresql = new PostgreSQLContainer<>(DockerImageNames.postgresql()) .withDatabaseName("test_flyway"); diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc-liquibase/src/test/java/smoketest/data/r2dbc/CityRepositoryTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc-liquibase/src/test/java/smoketest/data/r2dbc/CityRepositoryTests.java index 8ef2d20c5c..8b4291009a 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc-liquibase/src/test/java/smoketest/data/r2dbc/CityRepositoryTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc-liquibase/src/test/java/smoketest/data/r2dbc/CityRepositoryTests.java @@ -23,10 +23,8 @@ import org.testcontainers.junit.jupiter.Testcontainers; import reactor.test.StepVerifier; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.liquibase.LiquibaseConnectionDetails; import org.springframework.boot.test.autoconfigure.data.r2dbc.DataR2dbcTest; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.boot.testcontainers.service.connection.r2dbc.R2dbcServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import static org.assertj.core.api.Assertions.assertThat; @@ -43,8 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat; class CityRepositoryTests { @Container - @R2dbcServiceConnection - @ServiceConnection(LiquibaseConnectionDetails.class) + @ServiceConnection static PostgreSQLContainer postgresql = new PostgreSQLContainer<>(DockerImageNames.postgresql()) .withDatabaseName("test_liquibase"); diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-mongo/src/test/java/smoketest/session/mongodb/SampleSessionMongoApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-mongo/src/test/java/smoketest/session/mongodb/SampleSessionMongoApplicationTests.java index 0343fe9ad3..2846e2fb4b 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-mongo/src/test/java/smoketest/session/mongodb/SampleSessionMongoApplicationTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-mongo/src/test/java/smoketest/session/mongodb/SampleSessionMongoApplicationTests.java @@ -31,7 +31,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.boot.testcontainers.service.connection.mongo.MongoServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; @@ -65,7 +65,7 @@ class SampleSessionMongoApplicationTests { private int port; @Container - @MongoServiceConnection + @ServiceConnection static MongoDBContainer mongo = new MongoDBContainer(DockerImageNames.mongo()).withStartupAttempts(3) .withStartupTimeout(Duration.ofMinutes(2)); diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-redis/src/test/java/smoketest/session/redis/SampleSessionRedisApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-redis/src/test/java/smoketest/session/redis/SampleSessionRedisApplicationTests.java index 252d3e49da..d86eec6c97 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-redis/src/test/java/smoketest/session/redis/SampleSessionRedisApplicationTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-redis/src/test/java/smoketest/session/redis/SampleSessionRedisApplicationTests.java @@ -28,7 +28,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.testcontainers.service.connection.redis.RedisServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.RedisContainer; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; @@ -56,7 +56,7 @@ import static org.assertj.core.api.Assertions.assertThat; class SampleSessionRedisApplicationTests { @Container - @RedisServiceConnection + @ServiceConnection static RedisContainer redis = new RedisContainer(); @Autowired diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-webflux-mongo/src/test/java/smoketest/session/SampleSessionWebFluxMongoApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-webflux-mongo/src/test/java/smoketest/session/SampleSessionWebFluxMongoApplicationTests.java index d9c34ff172..f74385e377 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-webflux-mongo/src/test/java/smoketest/session/SampleSessionWebFluxMongoApplicationTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-webflux-mongo/src/test/java/smoketest/session/SampleSessionWebFluxMongoApplicationTests.java @@ -28,7 +28,7 @@ import reactor.util.function.Tuples; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.boot.testcontainers.service.connection.mongo.MongoServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.http.HttpStatus; import org.springframework.web.reactive.function.client.WebClient; @@ -49,7 +49,7 @@ import static org.assertj.core.api.Assertions.assertThat; class SampleSessionWebFluxMongoApplicationTests { @Container - @MongoServiceConnection + @ServiceConnection private static final MongoDBContainer mongo = new MongoDBContainer(DockerImageNames.mongo()).withStartupAttempts(3) .withStartupTimeout(Duration.ofMinutes(2)); diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-webflux-redis/src/test/java/smoketest/session/SampleSessionWebFluxRedisApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-webflux-redis/src/test/java/smoketest/session/SampleSessionWebFluxRedisApplicationTests.java index bd0084de2a..89707feed7 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-webflux-redis/src/test/java/smoketest/session/SampleSessionWebFluxRedisApplicationTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-webflux-redis/src/test/java/smoketest/session/SampleSessionWebFluxRedisApplicationTests.java @@ -27,7 +27,7 @@ import reactor.util.function.Tuples; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.boot.testcontainers.service.connection.redis.RedisServiceConnection; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.RedisContainer; import org.springframework.http.HttpStatus; import org.springframework.web.reactive.function.client.WebClient; @@ -48,7 +48,7 @@ import static org.assertj.core.api.Assertions.assertThat; class SampleSessionWebFluxRedisApplicationTests { @Container - @RedisServiceConnection + @ServiceConnection private static final RedisContainer redis = new RedisContainer(); @LocalServerPort