diff --git a/pom.xml b/pom.xml index 715172e7c..0740c0c2c 100644 --- a/pom.xml +++ b/pom.xml @@ -71,6 +71,7 @@ spring-cloud-aws-integration-test docs spring-cloud-aws-samples + spring-cloud-aws-test @@ -105,6 +106,13 @@ + + org.testcontainers + testcontainers-bom + 1.16.3 + pom + import + diff --git a/spring-cloud-aws-dependencies/pom.xml b/spring-cloud-aws-dependencies/pom.xml index caccf48af..336e6edb8 100644 --- a/spring-cloud-aws-dependencies/pom.xml +++ b/spring-cloud-aws-dependencies/pom.xml @@ -151,6 +151,11 @@ spring-cloud-aws-ses ${project.version} + + io.awspring.cloud + spring-cloud-aws-test + ${project.version} + diff --git a/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/pom.xml b/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/pom.xml index 43f4e254b..501597b5a 100644 --- a/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/pom.xml +++ b/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/pom.xml @@ -22,6 +22,11 @@ spring-boot-starter-json + + io.awspring.cloud + spring-cloud-starter-aws-messaging + + org.springframework.boot spring-boot-starter-test @@ -30,14 +35,27 @@ io.awspring.cloud - spring-cloud-starter-aws-messaging + spring-cloud-aws-test + test - org.springframework - spring-webmvc + org.awaitility + awaitility + test + + org.testcontainers + junit-jupiter + test + + + + org.testcontainers + localstack + test + diff --git a/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/src/main/java/io/awspring/cloud/sqs/sample/MessageSender.java b/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/src/main/java/io/awspring/cloud/sqs/sample/MessageSender.java new file mode 100644 index 000000000..ec2f63b0d --- /dev/null +++ b/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/src/main/java/io/awspring/cloud/sqs/sample/MessageSender.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013-2022 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 io.awspring.cloud.sqs.sample; + +import java.time.LocalDate; + +import io.awspring.cloud.messaging.core.QueueMessagingTemplate; + +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.stereotype.Component; + +@Component +class MessageSender { + + private final QueueMessagingTemplate queueMessagingTemplate; + + MessageSender(QueueMessagingTemplate queueMessagingTemplate) { + this.queueMessagingTemplate = queueMessagingTemplate; + } + + @EventListener(ApplicationReadyEvent.class) + public void sendMessage() { + this.queueMessagingTemplate.send("InfrastructureStack-spring-aws", + MessageBuilder.withPayload("Spring cloud Aws SQS sample!").build()); + this.queueMessagingTemplate.convertAndSend("InfrastructureStack-aws-pojo", + new Person("Joe", "Doe", LocalDate.of(2000, 1, 12))); + } + +} diff --git a/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/src/main/java/io/awspring/cloud/sqs/sample/SampleListener.java b/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/src/main/java/io/awspring/cloud/sqs/sample/SampleListener.java new file mode 100644 index 000000000..71b4c6d85 --- /dev/null +++ b/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/src/main/java/io/awspring/cloud/sqs/sample/SampleListener.java @@ -0,0 +1,40 @@ +/* + * Copyright 2013-2022 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 io.awspring.cloud.sqs.sample; + +import io.awspring.cloud.messaging.listener.annotation.SqsListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.stereotype.Component; + +@Component +class SampleListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(SampleListener.class); + + @SqsListener("InfrastructureStack-spring-aws") + public void listenToMessage(String message) { + LOGGER.info("This is message you want to see: {}", message); + } + + @SqsListener("InfrastructureStack-aws-pojo") + public void listenToPerson(Person person) { + LOGGER.info(person.toString()); + } + +} diff --git a/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/src/main/java/io/awspring/cloud/sqs/sample/SqsSampleApplication.java b/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/src/main/java/io/awspring/cloud/sqs/sample/SqsSampleApplication.java index 602054ecc..7c9c44f35 100644 --- a/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/src/main/java/io/awspring/cloud/sqs/sample/SqsSampleApplication.java +++ b/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/src/main/java/io/awspring/cloud/sqs/sample/SqsSampleApplication.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 the original author or authors. + * Copyright 2013-2022 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. @@ -16,50 +16,14 @@ package io.awspring.cloud.sqs.sample; -import java.time.LocalDate; - -import io.awspring.cloud.messaging.core.QueueMessagingTemplate; -import io.awspring.cloud.messaging.listener.annotation.SqsListener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.event.EventListener; -import org.springframework.messaging.support.MessageBuilder; @SpringBootApplication public class SqsSampleApplication { - private final QueueMessagingTemplate queueMessagingTemplate; - - SqsSampleApplication(QueueMessagingTemplate queueMessagingTemplate) { - this.queueMessagingTemplate = queueMessagingTemplate; - } - - private static final Logger LOGGER = LoggerFactory.getLogger(SqsSampleApplication.class); - public static void main(String[] args) { SpringApplication.run(SqsSampleApplication.class, args); } - @EventListener(ApplicationReadyEvent.class) - public void sendMessage() { - this.queueMessagingTemplate.send("InfrastructureStack-spring-aws", - MessageBuilder.withPayload("Spring cloud Aws SQS sample!").build()); - this.queueMessagingTemplate.convertAndSend("InfrastructureStack-aws-pojo", - new Person("Joe", "Doe", LocalDate.of(2000, 1, 12))); - } - - @SqsListener("InfrastructureStack-spring-aws") - private void listenToMessage(String message) { - LOGGER.info("This is message you want to see: {}", message); - } - - @SqsListener("InfrastructureStack-aws-pojo") - private void listenToPerson(Person person) { - LOGGER.info(person.toString()); - } - } diff --git a/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/src/test/java/io/awspring/cloud/sqs/sample/SqsSampleApplicationTests.java b/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/src/test/java/io/awspring/cloud/sqs/sample/SqsSampleApplicationTests.java new file mode 100644 index 000000000..7dc005b7e --- /dev/null +++ b/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/src/test/java/io/awspring/cloud/sqs/sample/SqsSampleApplicationTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2013-2022 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 io.awspring.cloud.sqs.sample; + +import java.io.IOException; + +import io.awspring.cloud.messaging.core.QueueMessagingTemplate; +import io.awspring.cloud.test.sqs.SqsTest; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.localstack.LocalStackContainer; +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.test.mock.mockito.SpyBean; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; + +import static org.awaitility.Awaitility.await; +import static org.mockito.Mockito.verify; +import static org.testcontainers.containers.localstack.LocalStackContainer.Service.SQS; + +@SqsTest(properties = { "cloud.aws.credentials.access-key=noop", "cloud.aws.credentials.secret-key=noop", + "cloud.aws.region.static=eu-west-1" }) +@Testcontainers +class SqsSampleApplicationTests { + + private static final String QUEUE_NAME = "InfrastructureStack-spring-aws"; + + // create an SQS locally running equivalent with Localstack and Testcontainers + @Container + static LocalStackContainer localstack = new LocalStackContainer( + DockerImageName.parse("localstack/localstack:0.14.0")).withServices(SQS).withReuse(true); + + @Autowired + private QueueMessagingTemplate queueMessagingTemplate; + + @SpyBean + private SampleListener sampleListener; + + @BeforeAll + static void beforeAll() throws IOException, InterruptedException { + // create needed queues in SQS + localstack.execInContainer("awslocal", "sqs", "create-queue", "--queue-name", QUEUE_NAME); + } + + @Test + void receivesMessage() { + // send a message + queueMessagingTemplate.convertAndSend(QUEUE_NAME, "hello"); + + // verify that bean handling the message was invoked + await().untilAsserted(() -> verify(sampleListener).listenToMessage("hello")); + } + + @DynamicPropertySource + static void registerSqsProperties(DynamicPropertyRegistry registry) { + // overwrite SQS endpoint with one provided by Localstack + registry.add("cloud.aws.sqs.endpoint", () -> localstack.getEndpointOverride(SQS).toString()); + } + +} diff --git a/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/src/test/resources/logback-test.xml b/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/src/test/resources/logback-test.xml new file mode 100644 index 000000000..3b961b063 --- /dev/null +++ b/spring-cloud-aws-samples/spring-cloud-aws-sqs-sample/src/test/resources/logback-test.xml @@ -0,0 +1,15 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + + diff --git a/spring-cloud-aws-test/pom.xml b/spring-cloud-aws-test/pom.xml new file mode 100644 index 000000000..8f3e577bd --- /dev/null +++ b/spring-cloud-aws-test/pom.xml @@ -0,0 +1,78 @@ + + + + + 4.0.0 + + io.awspring.cloud + spring-cloud-aws + 2.4.0-SNAPSHOT + + spring-cloud-aws-test + Spring Cloud AWS Test + Spring Cloud AWS Test + https://projects.spring.io/spring-cloud + + + ${basedir}/../.. + + + + io.awspring.cloud + spring-cloud-aws-autoconfigure + + + io.awspring.cloud + spring-cloud-aws-messaging + + + org.springframework.boot + spring-boot-test-autoconfigure + + + org.springframework + spring-test + + + + org.junit.jupiter + junit-jupiter-api + + + org.assertj + assertj-core + + + + org.awaitility + awaitility + test + + + org.testcontainers + localstack + test + + + org.testcontainers + junit-jupiter + test + + + diff --git a/spring-cloud-aws-test/src/main/java/io/awspring/cloud/test/sqs/AutoConfigureSqs.java b/spring-cloud-aws-test/src/main/java/io/awspring/cloud/test/sqs/AutoConfigureSqs.java new file mode 100644 index 000000000..13a0e7575 --- /dev/null +++ b/spring-cloud-aws-test/src/main/java/io/awspring/cloud/test/sqs/AutoConfigureSqs.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2022 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 io.awspring.cloud.test.sqs; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; + +/** + * {@link ImportAutoConfiguration Auto-configuration imports} for typical SQS tests. Most + * tests should consider using {@link SqsTest @SqsTest} rather than using this annotation + * directly. + * + * @author Maciej Walkowiak + * @since 2.4.0 + * @see SqsTest + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@ImportAutoConfiguration +public @interface AutoConfigureSqs { + +} diff --git a/spring-cloud-aws-test/src/main/java/io/awspring/cloud/test/sqs/SqsTest.java b/spring-cloud-aws-test/src/main/java/io/awspring/cloud/test/sqs/SqsTest.java new file mode 100644 index 000000000..3bed391ff --- /dev/null +++ b/spring-cloud-aws-test/src/main/java/io/awspring/cloud/test/sqs/SqsTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 2013-2022 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 io.awspring.cloud.test.sqs; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration; +import org.springframework.boot.test.autoconfigure.filter.TypeExcludeFilters; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.core.annotation.AliasFor; +import org.springframework.core.env.Environment; +import org.springframework.test.context.BootstrapWith; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * Annotation for a SQS test that focuses only on SQS-based components. + *

+ * Using this annotation will disable full auto-configuration and instead apply only + * configuration relevant to SQS tests. + *

+ * When using JUnit 4, this annotation should be used in combination with + * {@code @RunWith(SpringRunner.class)}. + * + * @author Maciej Walkowiak + * @since 2.4.0 + * @see AutoConfigureSqs + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@BootstrapWith(SqsTestContextBootstrapper.class) +@OverrideAutoConfiguration(enabled = false) +@TypeExcludeFilters(SqsTypeExcludeFilter.class) +@ExtendWith(SpringExtension.class) +@AutoConfigureSqs +@ImportAutoConfiguration +public @interface SqsTest { + + /** + * Determines if default filtering should be used with + * {@link SpringBootApplication @SpringBootApplication}. By default no beans are + * included. + * @see #includeFilters() + * @see #excludeFilters() + * @return if default filters should be used + */ + boolean useDefaultFilters() default true; + + /** + * Specifies the listeners to test. This is an alias of {@link #listeners()} which can + * be used for brevity if no other attributes are defined. See {@link #listeners()} + * for details. + * @see #listeners() + * @return the listeners to test + */ + @AliasFor("listeners") + Class[] value() default {}; + + /** + * Specifies the listeners to test. + * @see #value() + * @return the listeners to test + */ + @AliasFor("value") + Class[] listeners() default {}; + + /** + * Properties in form {@literal key=value} that should be added to the Spring + * {@link Environment} before the test runs. + * @return the properties to add + */ + String[] properties() default {}; + + /** + * A set of include filters which can be used to add otherwise filtered beans to the + * application context. + * @return include filters to apply + */ + ComponentScan.Filter[] includeFilters() default {}; + + /** + * A set of exclude filters which can be used to filter beans that would otherwise be + * added to the application context. + * @return exclude filters to apply + */ + ComponentScan.Filter[] excludeFilters() default {}; + +} diff --git a/spring-cloud-aws-test/src/main/java/io/awspring/cloud/test/sqs/SqsTestContextBootstrapper.java b/spring-cloud-aws-test/src/main/java/io/awspring/cloud/test/sqs/SqsTestContextBootstrapper.java new file mode 100644 index 000000000..89df86b82 --- /dev/null +++ b/spring-cloud-aws-test/src/main/java/io/awspring/cloud/test/sqs/SqsTestContextBootstrapper.java @@ -0,0 +1,37 @@ +/* + * Copyright 2013-2022 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 io.awspring.cloud.test.sqs; + +import org.springframework.boot.test.context.SpringBootTestContextBootstrapper; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.test.context.TestContextBootstrapper; + +/** + * {@link TestContextBootstrapper} for {@link SqsTest @SqsTest} support. + * + * @author Maciej Walkowiak + * @since 2.4.0 + */ +public class SqsTestContextBootstrapper extends SpringBootTestContextBootstrapper { + + @Override + protected String[] getProperties(Class testClass) { + return MergedAnnotations.from(testClass, MergedAnnotations.SearchStrategy.INHERITED_ANNOTATIONS) + .get(SqsTest.class).getValue("properties", String[].class).orElse(null); + } + +} diff --git a/spring-cloud-aws-test/src/main/java/io/awspring/cloud/test/sqs/SqsTypeExcludeFilter.java b/spring-cloud-aws-test/src/main/java/io/awspring/cloud/test/sqs/SqsTypeExcludeFilter.java new file mode 100644 index 000000000..12553e851 --- /dev/null +++ b/spring-cloud-aws-test/src/main/java/io/awspring/cloud/test/sqs/SqsTypeExcludeFilter.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013-2022 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 io.awspring.cloud.test.sqs; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.boot.context.TypeExcludeFilter; +import org.springframework.boot.test.autoconfigure.filter.StandardAnnotationCustomizableTypeExcludeFilter; + +/** + * {@link TypeExcludeFilter} for {@link SqsTest @SqsTest}. + * + * @author Maciej Walkowiak + * @since 2.4.0 + */ +public class SqsTypeExcludeFilter extends StandardAnnotationCustomizableTypeExcludeFilter { + + private static final Class[] NO_LISTENERS = {}; + + private final Class[] listeners; + + SqsTypeExcludeFilter(Class testClass) { + super(testClass); + this.listeners = getAnnotation().getValue("listeners", Class[].class).orElse(NO_LISTENERS); + } + + @Override + protected Set> getComponentIncludes() { + return new LinkedHashSet<>(Arrays.asList(this.listeners)); + } + +} diff --git a/spring-cloud-aws-test/src/main/resources/META-INF/spring.factories b/spring-cloud-aws-test/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..b5be89d27 --- /dev/null +++ b/spring-cloud-aws-test/src/main/resources/META-INF/spring.factories @@ -0,0 +1,5 @@ +io.awspring.cloud.test.sqs.AutoConfigureSqs=\ +org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ +io.awspring.cloud.autoconfigure.messaging.SqsAutoConfiguration,\ +io.awspring.cloud.autoconfigure.context.ContextRegionProviderAutoConfiguration,\ +io.awspring.cloud.autoconfigure.context.ContextCredentialsAutoConfiguration diff --git a/spring-cloud-aws-test/src/test/java/io/awspring/cloud/test/sqs/BaseSqsIntegrationTest.java b/spring-cloud-aws-test/src/test/java/io/awspring/cloud/test/sqs/BaseSqsIntegrationTest.java new file mode 100644 index 000000000..dd32949be --- /dev/null +++ b/spring-cloud-aws-test/src/test/java/io/awspring/cloud/test/sqs/BaseSqsIntegrationTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2022 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 io.awspring.cloud.test.sqs; + +import java.io.IOException; + +import org.junit.jupiter.api.BeforeAll; +import org.testcontainers.containers.localstack.LocalStackContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; + +import static io.awspring.cloud.test.sqs.SqsSampleListener.QUEUE_NAME; +import static org.testcontainers.containers.localstack.LocalStackContainer.Service.SQS; + +@Testcontainers +abstract class BaseSqsIntegrationTest { + + @Container + static LocalStackContainer localstack = new LocalStackContainer( + DockerImageName.parse("localstack/localstack:0.14.0")).withServices(SQS).withReuse(true); + + @BeforeAll + static void beforeAll() throws IOException, InterruptedException { + // create needed queues in SQS + localstack.execInContainer("awslocal", "sqs", "create-queue", "--queue-name", QUEUE_NAME); + } + + @DynamicPropertySource + static void registerSqsProperties(DynamicPropertyRegistry registry) { + // overwrite SQS endpoint with one provided by Localstack + registry.add("cloud.aws.sqs.endpoint", () -> localstack.getEndpointOverride(SQS).toString()); + } + +} diff --git a/spring-cloud-aws-test/src/test/java/io/awspring/cloud/test/sqs/SampleComponent.java b/spring-cloud-aws-test/src/test/java/io/awspring/cloud/test/sqs/SampleComponent.java new file mode 100644 index 000000000..6d11df253 --- /dev/null +++ b/spring-cloud-aws-test/src/test/java/io/awspring/cloud/test/sqs/SampleComponent.java @@ -0,0 +1,28 @@ +/* + * Copyright 2013-2022 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 io.awspring.cloud.test.sqs; + +import org.springframework.stereotype.Component; + +@Component +class SampleComponent { + + void save(String message) { + + } + +} diff --git a/spring-cloud-aws-test/src/test/java/io/awspring/cloud/test/sqs/SqsSampleListener.java b/spring-cloud-aws-test/src/test/java/io/awspring/cloud/test/sqs/SqsSampleListener.java new file mode 100644 index 000000000..720cc64f6 --- /dev/null +++ b/spring-cloud-aws-test/src/test/java/io/awspring/cloud/test/sqs/SqsSampleListener.java @@ -0,0 +1,39 @@ +/* + * Copyright 2013-2022 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 io.awspring.cloud.test.sqs; + +import io.awspring.cloud.messaging.listener.annotation.SqsListener; + +import org.springframework.stereotype.Component; + +@Component +class SqsSampleListener { + + static final String QUEUE_NAME = "my-queue"; + + private final SampleComponent sampleComponent; + + SqsSampleListener(SampleComponent sampleComponent) { + this.sampleComponent = sampleComponent; + } + + @SqsListener(QUEUE_NAME) + void handle(String message) { + sampleComponent.save(message); + } + +} diff --git a/spring-cloud-aws-test/src/test/java/io/awspring/cloud/test/sqs/SqsTestApplication.java b/spring-cloud-aws-test/src/test/java/io/awspring/cloud/test/sqs/SqsTestApplication.java new file mode 100644 index 000000000..f136227fe --- /dev/null +++ b/spring-cloud-aws-test/src/test/java/io/awspring/cloud/test/sqs/SqsTestApplication.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-2022 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 io.awspring.cloud.test.sqs; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SqsTestApplication { + + public static void main(String[] args) { + SpringApplication.run(SqsTestApplication.class, args); + } + +} diff --git a/spring-cloud-aws-test/src/test/java/io/awspring/cloud/test/sqs/SqsTestListenersDefinedTest.java b/spring-cloud-aws-test/src/test/java/io/awspring/cloud/test/sqs/SqsTestListenersDefinedTest.java new file mode 100644 index 000000000..f16cb2c45 --- /dev/null +++ b/spring-cloud-aws-test/src/test/java/io/awspring/cloud/test/sqs/SqsTestListenersDefinedTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013-2022 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 io.awspring.cloud.test.sqs; + +import io.awspring.cloud.messaging.core.QueueMessagingTemplate; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.ApplicationContext; + +import static io.awspring.cloud.test.sqs.SqsSampleListener.QUEUE_NAME; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.awaitility.Awaitility.await; +import static org.mockito.Mockito.verify; + +@SqsTest(listeners = SqsSampleListener.class, properties = { "cloud.aws.credentials.access-key=noop", + "cloud.aws.credentials.secret-key=noop", "cloud.aws.region.static=eu-west-1" }) +class SqsTestListenersDefinedTest extends BaseSqsIntegrationTest { + + @Autowired + private ApplicationContext ctx; + + @Autowired + private QueueMessagingTemplate queueMessagingTemplate; + + @MockBean + private SampleComponent sampleComponent; + + @Test + void createsQueueMessagingTemplate() { + assertThatNoException().isThrownBy(() -> this.ctx.getBean(QueueMessagingTemplate.class)); + } + + @Test + void createsListener() { + assertThatNoException().isThrownBy(() -> this.ctx.getBean(SqsSampleListener.class)); + } + + @Test + void listenerHandlesMessage() { + queueMessagingTemplate.convertAndSend(QUEUE_NAME, "message"); + + await().untilAsserted(() -> verify(sampleComponent).save("message")); + } + +} diff --git a/spring-cloud-aws-test/src/test/java/io/awspring/cloud/test/sqs/SqsTestNoListenersDefinedTest.java b/spring-cloud-aws-test/src/test/java/io/awspring/cloud/test/sqs/SqsTestNoListenersDefinedTest.java new file mode 100644 index 000000000..c2661f43f --- /dev/null +++ b/spring-cloud-aws-test/src/test/java/io/awspring/cloud/test/sqs/SqsTestNoListenersDefinedTest.java @@ -0,0 +1,67 @@ +/* + * Copyright 2013-2022 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 io.awspring.cloud.test.sqs; + +import io.awspring.cloud.autoconfigure.context.ContextCredentialsAutoConfiguration; +import io.awspring.cloud.autoconfigure.context.ContextRegionProviderAutoConfiguration; +import io.awspring.cloud.autoconfigure.messaging.SqsAutoConfiguration; +import io.awspring.cloud.messaging.core.QueueMessagingTemplate; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.context.ApplicationContext; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@SqsTest +class SqsTestNoListenersDefinedTest extends BaseSqsIntegrationTest { + + private static final Class[] EXPECTED_AUTOCONFIGURATION_CLASSES = { SqsAutoConfiguration.class, + JacksonAutoConfiguration.class, ContextRegionProviderAutoConfiguration.class, + ContextCredentialsAutoConfiguration.class }; + + @Autowired + private ApplicationContext ctx; + + @Test + void usesAutoConfigurations() { + for (Class clazz : EXPECTED_AUTOCONFIGURATION_CLASSES) { + assertThatNoException().isThrownBy(() -> this.ctx.getBean(clazz)); + } + } + + @Test + void createsQueueMessagingTemplate() { + assertThatNoException().isThrownBy(() -> this.ctx.getBean(QueueMessagingTemplate.class)); + } + + @Test + void doesNotCreateListenerBean() { + assertThatThrownBy(() -> this.ctx.getBean(SqsSampleListener.class)) + .isInstanceOf(NoSuchBeanDefinitionException.class); + } + + @Test + void doesNotCreateOtherBeans() { + assertThatThrownBy(() -> this.ctx.getBean(SampleComponent.class)) + .isInstanceOf(NoSuchBeanDefinitionException.class); + } + +} diff --git a/spring-cloud-aws-test/src/test/resources/logback-test.xml b/spring-cloud-aws-test/src/test/resources/logback-test.xml new file mode 100644 index 000000000..3b961b063 --- /dev/null +++ b/spring-cloud-aws-test/src/test/resources/logback-test.xml @@ -0,0 +1,15 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + +