From fa686bb593f2a3b85497546d3995c4050501ef61 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Thu, 29 Aug 2024 19:23:21 -0700 Subject: [PATCH] Support Testcontainer Redis with custom image names Update `RedisContainerConnectionDetailsFactory` so that it can also support `RedisContainer` with a custom name. Closes gh-41450 --- ...ontainerConnectionDetailsFactoryTests.java | 53 +++++++++++++++++++ .../ContainerConnectionDetailsFactory.java | 29 +++++++--- .../connection/ContainerConnectionSource.java | 12 ++++- ...edisContainerConnectionDetailsFactory.java | 10 +++- .../TestContainerConnectionSource.java | 43 +++++++++++++++ 5 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redis/CustomRedisContainerConnectionDetailsFactoryTests.java create mode 100644 spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/TestContainerConnectionSource.java diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redis/CustomRedisContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redis/CustomRedisContainerConnectionDetailsFactoryTests.java new file mode 100644 index 00000000000..af2d09016d4 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/redis/CustomRedisContainerConnectionDetailsFactoryTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2024 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.util.Map; + +import com.redis.testcontainers.RedisContainer; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails; +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactories; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testcontainers.service.connection.TestContainerConnectionSource; +import org.springframework.core.annotation.MergedAnnotation; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for {@link RedisContainerConnectionDetailsFactory} when using a custom contain + * without "redis" as the name. + * + * @author Phillip Webb + */ +class CustomRedisContainerConnectionDetailsFactoryTests { + + @Test + void getConnectionDetailsWhenRedisContainerWithCustomName() { + ConnectionDetailsFactories factories = new ConnectionDetailsFactories(); + MergedAnnotation annotation = MergedAnnotation.of(ServiceConnection.class, + Map.of("value", "")); + ContainerConnectionSource source = TestContainerConnectionSource.create("test", null, + RedisContainer.class, "mycustomimage", annotation, null); + Map, ConnectionDetails> connectionDetails = factories.getConnectionDetails(source, true); + assertThat(connectionDetails.get(RedisConnectionDetails.class)).isNotNull(); + } + +} 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 1e8e0d24ac7..4b601d423ab 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 @@ -104,12 +104,10 @@ public abstract class ContainerConnectionDetailsFactory, } try { Class[] generics = resolveGenerics(); - Class containerType = generics[0]; - Class connectionDetailsType = generics[1]; - for (String connectionName : this.connectionNames) { - if (source.accepts(connectionName, containerType, connectionDetailsType)) { - return getContainerConnectionDetails(source); - } + Class requiredContainerType = generics[0]; + Class requiredConnectionDetailsType = generics[1]; + if (sourceAccepts(source, requiredContainerType, requiredConnectionDetailsType)) { + return getContainerConnectionDetails(source); } } catch (NoClassDefFoundError ex) { @@ -118,6 +116,25 @@ public abstract class ContainerConnectionDetailsFactory, return null; } + /** + * Return if the give source accepts the connection. By default this method checks + * each connection name. + * @param source the container connection source + * @param requiredContainerType the required container type + * @param requiredConnectionDetailsType the required connection details type + * @return if the source accepts the connection + * @since 3.4.0 + */ + protected boolean sourceAccepts(ContainerConnectionSource source, Class requiredContainerType, + Class requiredConnectionDetailsType) { + for (String requiredConnectionName : this.connectionNames) { + if (source.accepts(requiredConnectionName, requiredContainerType, requiredConnectionDetailsType)) { + return true; + } + } + return false; + } + private boolean hasRequiredClasses() { return ObjectUtils.isEmpty(this.requiredClassNames) || Arrays.stream(this.requiredClassNames) .allMatch((requiredClassName) -> ClassUtils.isPresent(requiredClassName, null)); 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 30aece533d1..da480628d6e 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -90,7 +90,15 @@ public final class ContainerConnectionSource> implements return null; } - boolean accepts(String requiredConnectionName, Class requiredContainerType, + /** + * Return is this source accepts the given connection. + * @param requiredConnectionName the required connection name or {@code null} + * @param requiredContainerType the required container type + * @param requiredConnectionDetailsType the required connection details type + * @return if the connection is accepted by this source + * @since 3.4.0 + */ + public boolean accepts(String requiredConnectionName, Class requiredContainerType, Class requiredConnectionDetailsType) { if (StringUtils.hasText(requiredConnectionName) && !requiredConnectionName.equalsIgnoreCase(this.connectionName)) { 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 5d10886d696..9048b65f19c 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 @@ -18,6 +18,7 @@ package org.springframework.boot.testcontainers.service.connection.redis; import java.util.List; +import com.redis.testcontainers.RedisContainer; import org.testcontainers.containers.Container; import org.testcontainers.containers.GenericContainer; @@ -49,7 +50,14 @@ class RedisContainerConnectionDetailsFactory } @Override - public RedisConnectionDetails getContainerConnectionDetails(ContainerConnectionSource> source) { + protected boolean sourceAccepts(ContainerConnectionSource> source, Class requiredContainerType, + Class requiredConnectionDetailsType) { + return super.sourceAccepts(source, requiredContainerType, requiredConnectionDetailsType) + || source.accepts(ANY_CONNECTION_NAME, RedisContainer.class, requiredConnectionDetailsType); + } + + @Override + protected RedisConnectionDetails getContainerConnectionDetails(ContainerConnectionSource> source) { return new RedisContainerConnectionDetails(source); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/TestContainerConnectionSource.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/TestContainerConnectionSource.java new file mode 100644 index 00000000000..dda8088eceb --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/TestContainerConnectionSource.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2024 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.function.Supplier; + +import org.testcontainers.containers.Container; + +import org.springframework.boot.origin.Origin; +import org.springframework.core.annotation.MergedAnnotation; + +/** + * Factory for tests to create a {@link ContainerConnectionSource}. + * + * @author Phillip Webb + */ +public final class TestContainerConnectionSource { + + private TestContainerConnectionSource() { + } + + public static > ContainerConnectionSource create(String beanNameSuffix, Origin origin, + Class containerType, String containerImageName, MergedAnnotation annotation, + Supplier containerSupplier) { + return new ContainerConnectionSource<>(beanNameSuffix, origin, containerType, containerImageName, annotation, + containerSupplier); + } + +}