Search code examples
amazon-web-servicesspring-bootkotlintestcontainersspring-cloud-aws

Use Testcontainer SQS with spring-cloud-aws-starter-sqs 3.0.2


After moving to spring boot 3 and spring-cloud-aws-starter-sqs 3.0.2 I am now trying to create an integrationtest with SQS. I try to find an example, but so far none of the examples I found are this recent and adapting my test does not seem to work.

I have a listener and a publisher.

@Service
class Listener {

  val logger = LoggerFactory.getLogger(this::class.java)

  @SqsListener("list-queue")
  fun processCacheRequest(items: List<Item?>?) {
    logger.info("items: $items")
    return
  }

  @SqsListener("single-queue")
  fun processCacheRequest(item: Item?) {
    logger.info("item: $item")
    return
  }
}

@Service
class Publisher {

  @Autowired
  lateinit var sqsTemplate: SqsTemplate

  fun publish() {
    val items = listOf(Item("foo", "bar"), Item("foo"), Item(bar = "bar"))
    sqsTemplate.send("list-queue", items)
  }

  fun publishSingle() {
    sqsTemplate.send("single-queue", Item("foo", "bar"))
  }
}

I then have an integration test, that starts a SQS via testcontainer localstack.

@Testcontainers
@DirtiesContext
@WebAppConfiguration
@ContextConfiguration(initializers = [SqsUrlInitializer::class])
@SpringBootTest(
  classes = [DemoApplication::class, SqsIntegrationTest.TestConfig::class],
  properties = ["spring.main.allow-bean-definition-overriding=true"],
)
class SqsIntegrationTest : DescribeSpec() {

  @Autowired
  lateinit var publisher: Publisher

  @Value("\${cloud.aws.sqs.endpoint}")
  lateinit var endpoint: String

  init {

    it("should publish and read message") {
      shouldNotThrow<Exception> {
        publisher.publish()
      }
    }
  }

  @TestConfiguration
  class TestConfig {

    @Bean
    @Primary
    fun awsCredentialsProvider(): AwsCredentialsProvider {
      if (!localStack.isRunning) localStack.start()
      val creds = AwsBasicCredentials.create(localStack.accessKey, localStack.secretKey)
      return StaticCredentialsProvider.create(creds)
    }

    @Bean
    @Primary
    fun awsRegionProvider(): AwsRegionProvider {
      return StaticRegionProvider(Region.EU_CENTRAL_1.id())
    }

    /**
     * Just configures Localstack's SQS server endpoint in the application
     */
    companion object {

      @JvmStatic
      val localStack: LocalStackContainer = LocalStackContainer(DockerImageName.parse("localstack/localstack:latest"))
        .withServices(SQS)
    }

    class SqsUrlInitializer : ApplicationContextInitializer<ConfigurableApplicationContext> {

      override fun initialize(applicationContext: ConfigurableApplicationContext) {
        localStack.start()
        localStack.execInContainer("awslocal", "sqs", "create-queue", "--queue-name", "single-queue")
        localStack.execInContainer("awslocal", "sqs", "create-queue", "--queue-name", "list-queue")
        TestPropertyValues.of("cloud.aws.sqs.endpoint=${localStack.getEndpointOverride(SQS)}")
          .applyTo(applicationContext.environment)
      }
    }
  }
}

Unfortunately the test does not even start and shows the following error:

Failed to start bean 'io.awspring.cloud.messaging.internalEndpointRegistryBeanName'
org.springframework.context.ApplicationContextException: Failed to start bean 'io.awspring.cloud.messaging.internalEndpointRegistryBeanName'
    at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:182)
[...]
    at java.base/java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:482)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: io.awspring.cloud.sqs.QueueAttributesResolvingException: Error resolving attributes for queue single-queue with strategy CREATE and queueAttributesNames []
    at io.awspring.cloud.sqs.QueueAttributesResolver.wrapException(QueueAttributesResolver.java:90)
    at java.base/java.util.concurrent.CompletableFuture.uniExceptionally(CompletableFuture.java:990)
    at java.base/java.util.concurrent.CompletableFuture$UniExceptionally.tryFire(CompletableFuture.java:974)
    ... 41 more
Caused by: software.amazon.awssdk.services.sqs.model.SqsException: The security token included in the request is invalid. (Service: Sqs, Status Code: 403, Request ID: 2c38dd48-e9e1-5670-98ba-803c3343f84e)
    at software.amazon.awssdk.services.sqs.model.SqsException$BuilderImpl.build(SqsException.java:104)

When I create breakpoints I can see that the queues are created and docker exec <container> awslocal sqs list-queues showa:

{
    "QueueUrls": [
        "http://localhost:4566/000000000000/single-queue",
        "http://localhost:4566/000000000000/list-queue"
    ]
}

When I run this application against my AWS account it works fine.

Anybody any idea what I am doing wrong here? The whole project can be found here.


Solution

  • so I found out my problem had nothing to do with the credentials, but that I used the wrong property for the endpoint.

    instead of

    TestPropertyValues.of("cloud.aws.sqs.endpoint=${localStack.getEndpointOverride(SQS)}")
              .applyTo(applicationContext.environment)
    

    I need to use

    TestPropertyValues.of("spring.cloud.aws.sqs.endpoint=${localStack.getEndpointOverride(SQS)}")
              .applyTo(applicationContext.environment)