diff --git a/pom.xml b/pom.xml
index 13e9641d5..893b3c0f3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -79,9 +79,19 @@
storage/spring-cloud-azure-starter-storage-file-share/storage-file-sample
storage/spring-cloud-azure-starter-storage-queue/storage-queue-client
storage/spring-messaging-azure-storage-queue/storage-queue-spring-messaging
- testcontainers/spring-cloud-azure-testcontainers-for-cosmos-sample
- testcontainers/spring-cloud-azure-testcontainers-for-storage-blob-sample
- testcontainers/spring-cloud-azure-testcontainers-for-storage-queue-sample
+ spring-cloud-azure-docker-compose/event-hubs/client
+ spring-cloud-azure-docker-compose/event-hubs/spring-cloud-stream-binders
+ spring-cloud-azure-docker-compose/service-bus/spring-cloud-stream-binders
+ spring-cloud-azure-docker-compose/service-bus/spring-messaging
+ spring-cloud-azure-docker-compose/storage-blob
+ spring-cloud-azure-docker-compose/storage-queue
+ spring-cloud-azure-testcontainers/cosmos
+ spring-cloud-azure-testcontainers/event-hubs/client
+ spring-cloud-azure-testcontainers/event-hubs/spring-cloud-stream-binders
+ spring-cloud-azure-testcontainers/service-bus/spring-cloud-stream-binders
+ spring-cloud-azure-testcontainers/service-bus/spring-messaging
+ spring-cloud-azure-testcontainers/storage-blob
+ spring-cloud-azure-testcontainers/storage-queue
diff --git a/spring-cloud-azure-docker-compose/event-hubs/client/pom.xml b/spring-cloud-azure-docker-compose/event-hubs/client/pom.xml
new file mode 100644
index 000000000..bef4424bd
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/event-hubs/client/pom.xml
@@ -0,0 +1,55 @@
+
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.3
+
+
+
+ com.azure.spring
+ spring-cloud-azure-docker-compose-for-event-hubs-client-sample
+ 1.0.0
+ jar
+
+
+ 17
+ 17
+ 7.1.0
+
+
+
+
+
+ com.azure.spring
+ spring-cloud-azure-dependencies
+ ${version.spring.cloud.azure}
+ pom
+ import
+
+
+
+
+
+
+ com.azure.spring
+ spring-cloud-azure-starter-eventhubs
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-docker-compose
+ test
+
+
+
+
\ No newline at end of file
diff --git a/spring-cloud-azure-docker-compose/event-hubs/client/src/test/java/EventHubsDockerComposeTest.java b/spring-cloud-azure-docker-compose/event-hubs/client/src/test/java/EventHubsDockerComposeTest.java
new file mode 100644
index 000000000..943974b82
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/event-hubs/client/src/test/java/EventHubsDockerComposeTest.java
@@ -0,0 +1,57 @@
+import com.azure.messaging.eventhubs.EventData;
+import com.azure.messaging.eventhubs.EventHubProducerClient;
+import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.eventhubs.AzureEventHubsAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.eventhubs.properties.AzureEventHubsConnectionDetails;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Configuration;
+
+import java.time.Duration;
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.waitAtMost;
+
+@SpringBootTest(properties = {
+ "spring.docker.compose.skip.in-tests=false",
+ "spring.docker.compose.file=classpath:eventhubs-compose.yaml",
+ "spring.docker.compose.stop.command=down",
+ "spring.docker.compose.readiness.timeout=PT5M",
+ "spring.cloud.azure.eventhubs.event-hub-name=eh1",
+ "spring.cloud.azure.eventhubs.producer.event-hub-name=eh1"
+})
+class EventHubsDockerComposeTest {
+
+ @Autowired
+ private AzureEventHubsConnectionDetails connectionDetails;
+
+ @Autowired
+ private EventHubProducerClient producerClient;
+
+ @Test
+ void connectionDetailsShouldBeProvidedByFactory() {
+ assertThat(connectionDetails).isNotNull();
+ assertThat(connectionDetails.getConnectionString())
+ .isNotBlank()
+ .startsWith("Endpoint=sb://");
+ }
+
+ @Test
+ void producerClientCanSendMessage() {
+ // Wait for Event Hubs emulator to be fully ready and event hub entity to be available
+ waitAtMost(Duration.ofSeconds(120)).pollInterval(Duration.ofSeconds(2)).untilAsserted(() -> {
+ EventData event = new EventData("Hello World!");
+ this.producerClient.send(Collections.singletonList(event));
+ });
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @ImportAutoConfiguration(classes = {
+ AzureGlobalPropertiesAutoConfiguration.class,
+ AzureEventHubsAutoConfiguration.class})
+ static class Config {
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-azure-docker-compose/event-hubs/client/src/test/resources/Config.json b/spring-cloud-azure-docker-compose/event-hubs/client/src/test/resources/Config.json
new file mode 100644
index 000000000..4610f9a23
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/event-hubs/client/src/test/resources/Config.json
@@ -0,0 +1,20 @@
+{
+ "UserConfig": {
+ "NamespaceConfig": [
+ {
+ "Type": "EventHub",
+ "Name": "emulatorns1",
+ "Entities": [
+ {
+ "Name": "eh1",
+ "PartitionCount": "2",
+ "ConsumerGroups": []
+ }
+ ]
+ }
+ ],
+ "LoggingConfig": {
+ "Type": "File"
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-azure-docker-compose/event-hubs/client/src/test/resources/eventhubs-compose.yaml b/spring-cloud-azure-docker-compose/event-hubs/client/src/test/resources/eventhubs-compose.yaml
new file mode 100644
index 000000000..afd48b157
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/event-hubs/client/src/test/resources/eventhubs-compose.yaml
@@ -0,0 +1,34 @@
+services:
+ eventhubs:
+ image: mcr.microsoft.com/azure-messaging/eventhubs-emulator:latest
+ pull_policy: always
+ volumes:
+ # Mount the emulator configuration to the path expected by the emulator image
+ - "./Config.json:/Eventhubs_Emulator/ConfigFiles/Config.json"
+ ports:
+ - "5672"
+ environment:
+ # Event Hubs emulator requires external blob/metadata storage provided by azurite
+ BLOB_SERVER: azurite
+ METADATA_SERVER: azurite
+ ACCEPT_EULA: Y
+ depends_on:
+ - azurite
+ networks:
+ eh-emulator:
+ aliases:
+ - "eh-emulator"
+
+ azurite:
+ image: "mcr.microsoft.com/azure-storage/azurite:latest"
+ ports:
+ - "10000"
+ - "10001"
+ - "10002"
+ networks:
+ eh-emulator:
+ aliases:
+ - "azurite"
+
+networks:
+ eh-emulator:
\ No newline at end of file
diff --git a/spring-cloud-azure-docker-compose/event-hubs/spring-cloud-stream-binders/pom.xml b/spring-cloud-azure-docker-compose/event-hubs/spring-cloud-stream-binders/pom.xml
new file mode 100644
index 000000000..4eb0f3c2c
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/event-hubs/spring-cloud-stream-binders/pom.xml
@@ -0,0 +1,59 @@
+
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.3
+
+
+
+ com.azure.spring
+ spring-cloud-azure-docker-compose-for-event-hubs-spring-cloud-stream-sample
+ 1.0.0
+ jar
+
+
+ 17
+ 17
+ 7.1.0
+
+
+
+
+
+ com.azure.spring
+ spring-cloud-azure-dependencies
+ ${version.spring.cloud.azure}
+ pom
+ import
+
+
+
+
+
+
+ com.azure
+ azure-messaging-eventhubs
+
+
+ com.azure.spring
+ spring-cloud-azure-stream-binder-eventhubs
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-docker-compose
+ test
+
+
+
+
\ No newline at end of file
diff --git a/spring-cloud-azure-docker-compose/event-hubs/spring-cloud-stream-binders/src/test/java/EventHubsDockerComposeTest.java b/spring-cloud-azure-docker-compose/event-hubs/spring-cloud-stream-binders/src/test/java/EventHubsDockerComposeTest.java
new file mode 100644
index 000000000..9bc5289dc
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/event-hubs/spring-cloud-stream-binders/src/test/java/EventHubsDockerComposeTest.java
@@ -0,0 +1,112 @@
+import com.azure.messaging.eventhubs.checkpointstore.blob.BlobCheckpointStore;
+import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.eventhubs.AzureEventHubsAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.eventhubs.AzureEventHubsMessagingAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.storage.blob.properties.AzureStorageBlobConnectionDetails;
+import com.azure.spring.messaging.checkpoint.Checkpointer;
+import com.azure.storage.blob.BlobContainerAsyncClient;
+import com.azure.storage.blob.BlobServiceAsyncClient;
+import com.azure.storage.blob.BlobServiceClientBuilder;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+
+import java.time.Duration;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import static com.azure.spring.messaging.AzureHeaders.CHECKPOINTER;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.waitAtMost;
+
+@SpringBootTest(properties = {
+ "spring.docker.compose.skip.in-tests=false",
+ "spring.docker.compose.file=classpath:eventhubs-compose.yaml",
+ "spring.docker.compose.stop.command=down",
+ "spring.docker.compose.readiness.timeout=PT5M",
+ "spring.cloud.function.definition=consume;supply",
+ "spring.cloud.stream.bindings.consume-in-0.destination=eh1",
+ "spring.cloud.stream.bindings.consume-in-0.group=$Default",
+ "spring.cloud.stream.bindings.supply-out-0.destination=eh1",
+ "spring.cloud.stream.eventhubs.bindings.consume-in-0.consumer.checkpoint.mode=MANUAL",
+ "spring.cloud.stream.poller.fixed-delay=1000",
+ "spring.cloud.stream.poller.initial-delay=0"
+})
+class EventHubsDockerComposeTest {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(EventHubsDockerComposeTest.class);
+ private static final Set RECEIVED_MESSAGES = ConcurrentHashMap.newKeySet();
+ private static final AtomicInteger MESSAGE_SEQUENCE = new AtomicInteger(0);
+
+ @Test
+ void supplierAndConsumerShouldWorkThroughEventHub() {
+ waitAtMost(Duration.ofSeconds(120))
+ .pollDelay(Duration.ofSeconds(2))
+ .pollInterval(Duration.ofSeconds(2))
+ .untilAsserted(() -> {
+ assertThat(RECEIVED_MESSAGES).isNotEmpty();
+ LOGGER.info("✓ Test passed - Consumer received {} message(s)", RECEIVED_MESSAGES.size());
+ });
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @EnableAutoConfiguration
+ @ImportAutoConfiguration(classes = {
+ AzureGlobalPropertiesAutoConfiguration.class,
+ AzureEventHubsAutoConfiguration.class,
+ AzureEventHubsMessagingAutoConfiguration.class})
+ static class Config {
+
+ private static final String CHECKPOINT_CONTAINER_NAME = "eventhubs-checkpoint";
+
+ @Bean
+ public BlobCheckpointStore blobCheckpointStore(AzureStorageBlobConnectionDetails connectionDetails) {
+ BlobServiceAsyncClient blobServiceAsyncClient = new BlobServiceClientBuilder()
+ .connectionString(connectionDetails.getConnectionString())
+ .buildAsyncClient();
+ BlobContainerAsyncClient containerAsyncClient = blobServiceAsyncClient
+ .getBlobContainerAsyncClient(CHECKPOINT_CONTAINER_NAME);
+ if (Boolean.FALSE.equals(containerAsyncClient.exists().block(Duration.ofSeconds(3)))) {
+ containerAsyncClient.create().block(Duration.ofSeconds(3));
+ }
+ return new BlobCheckpointStore(containerAsyncClient);
+ }
+
+ @Bean
+ public Supplier> supply() {
+ return () -> {
+ int sequence = MESSAGE_SEQUENCE.getAndIncrement();
+ String payload = "Hello world, " + sequence;
+ LOGGER.info("[Supplier] Invoked - message sequence: {}", sequence);
+ return MessageBuilder.withPayload(payload).build();
+ };
+ }
+
+ @Bean
+ public Consumer> consume() {
+ return message -> {
+ String payload = message.getPayload();
+ RECEIVED_MESSAGES.add(payload);
+ LOGGER.info("[Consumer] Received message: {}", payload);
+
+ Checkpointer checkpointer = (Checkpointer) message.getHeaders().get(CHECKPOINTER);
+ if (checkpointer != null) {
+ checkpointer.success()
+ .doOnSuccess(s -> LOGGER.info("[Consumer] Message checkpointed"))
+ .doOnError(e -> LOGGER.error("[Consumer] Checkpoint failed", e))
+ .block();
+ }
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-azure-docker-compose/event-hubs/spring-cloud-stream-binders/src/test/resources/Config.json b/spring-cloud-azure-docker-compose/event-hubs/spring-cloud-stream-binders/src/test/resources/Config.json
new file mode 100644
index 000000000..4610f9a23
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/event-hubs/spring-cloud-stream-binders/src/test/resources/Config.json
@@ -0,0 +1,20 @@
+{
+ "UserConfig": {
+ "NamespaceConfig": [
+ {
+ "Type": "EventHub",
+ "Name": "emulatorns1",
+ "Entities": [
+ {
+ "Name": "eh1",
+ "PartitionCount": "2",
+ "ConsumerGroups": []
+ }
+ ]
+ }
+ ],
+ "LoggingConfig": {
+ "Type": "File"
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-azure-docker-compose/event-hubs/spring-cloud-stream-binders/src/test/resources/eventhubs-compose.yaml b/spring-cloud-azure-docker-compose/event-hubs/spring-cloud-stream-binders/src/test/resources/eventhubs-compose.yaml
new file mode 100644
index 000000000..021991832
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/event-hubs/spring-cloud-stream-binders/src/test/resources/eventhubs-compose.yaml
@@ -0,0 +1,35 @@
+services:
+ eventhubs:
+ image: mcr.microsoft.com/azure-messaging/eventhubs-emulator:latest
+ pull_policy: always
+ volumes:
+ # Mount the emulator configuration to the path expected by the emulator image
+ - "./Config.json:/Eventhubs_Emulator/ConfigFiles/Config.json"
+ ports:
+ - "5672"
+ environment:
+ # Event Hubs emulator requires external blob/metadata storage provided by azurite
+ BLOB_SERVER: azurite
+ METADATA_SERVER: azurite
+ ACCEPT_EULA: Y
+ depends_on:
+ - azurite
+ networks:
+ eh-emulator:
+ aliases:
+ - "eh-emulator"
+
+ azurite:
+ image: "mcr.microsoft.com/azure-storage/azurite:latest"
+ command: "azurite -l /data --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0 --skipApiVersionCheck"
+ ports:
+ - "10000"
+ - "10001"
+ - "10002"
+ networks:
+ eh-emulator:
+ aliases:
+ - "azurite"
+
+networks:
+ eh-emulator:
\ No newline at end of file
diff --git a/spring-cloud-azure-docker-compose/service-bus/spring-cloud-stream-binders/pom.xml b/spring-cloud-azure-docker-compose/service-bus/spring-cloud-stream-binders/pom.xml
new file mode 100644
index 000000000..514ba167d
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/service-bus/spring-cloud-stream-binders/pom.xml
@@ -0,0 +1,60 @@
+
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.3
+
+
+
+ com.azure.spring
+ spring-cloud-azure-docker-compose-for-service-bus-spring-cloud-stream-sample
+ 1.0.0
+ jar
+
+
+ 17
+ 17
+ 7.1.0
+
+
+
+
+
+ com.azure.spring
+ spring-cloud-azure-dependencies
+ ${version.spring.cloud.azure}
+ pom
+ import
+
+
+
+
+
+
+ com.azure.spring
+ spring-cloud-azure-stream-binder-servicebus
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-autoconfigure
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-docker-compose
+ test
+
+
+
+
\ No newline at end of file
diff --git a/spring-cloud-azure-docker-compose/service-bus/spring-cloud-stream-binders/src/test/java/ServiceBusDockerComposeTest.java b/spring-cloud-azure-docker-compose/service-bus/spring-cloud-stream-binders/src/test/java/ServiceBusDockerComposeTest.java
new file mode 100644
index 000000000..48b46c84c
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/service-bus/spring-cloud-stream-binders/src/test/java/ServiceBusDockerComposeTest.java
@@ -0,0 +1,90 @@
+import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.AzureServiceBusAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.AzureServiceBusMessagingAutoConfiguration;
+import com.azure.spring.messaging.checkpoint.Checkpointer;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+
+import java.time.Duration;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import static com.azure.spring.messaging.AzureHeaders.CHECKPOINTER;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.waitAtMost;
+
+@SpringBootTest(properties = {
+ "spring.docker.compose.skip.in-tests=false",
+ "spring.docker.compose.file=classpath:servicebus-compose.yaml",
+ "spring.docker.compose.stop.command=down",
+ "spring.cloud.function.definition=consume;supply",
+ "spring.cloud.stream.bindings.consume-in-0.destination=queue.1",
+ "spring.cloud.stream.bindings.supply-out-0.destination=queue.1",
+ "spring.cloud.stream.servicebus.bindings.consume-in-0.consumer.auto-complete=false",
+ "spring.cloud.stream.servicebus.bindings.supply-out-0.producer.entity-type=queue",
+ "spring.cloud.stream.poller.fixed-delay=1000",
+ "spring.cloud.stream.poller.initial-delay=0"
+})
+class ServiceBusDockerComposeTest {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ServiceBusDockerComposeTest.class);
+ private static final Set RECEIVED_MESSAGES = ConcurrentHashMap.newKeySet();
+ private static final AtomicInteger MESSAGE_SEQUENCE = new AtomicInteger(0);
+
+ @Test
+ void supplierAndConsumerShouldWorkThroughServiceBusQueue() {
+ waitAtMost(Duration.ofSeconds(60))
+ .pollDelay(Duration.ofSeconds(2))
+ .untilAsserted(() -> {
+ assertThat(RECEIVED_MESSAGES).isNotEmpty();
+ LOGGER.info("✓ Test passed - Consumer received {} message(s)", RECEIVED_MESSAGES.size());
+ });
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @EnableAutoConfiguration
+ @ImportAutoConfiguration(classes = {
+ AzureGlobalPropertiesAutoConfiguration.class,
+ AzureServiceBusAutoConfiguration.class,
+ AzureServiceBusMessagingAutoConfiguration.class})
+ static class Config {
+
+ @Bean
+ public Supplier> supply() {
+ return () -> {
+ int sequence = MESSAGE_SEQUENCE.getAndIncrement();
+ String payload = "Hello world, " + sequence;
+ LOGGER.info("[Supplier] Invoked - message sequence: {}", sequence);
+ return MessageBuilder.withPayload(payload).build();
+ };
+ }
+
+ @Bean
+ public Consumer> consume() {
+ return message -> {
+ String payload = message.getPayload();
+ RECEIVED_MESSAGES.add(payload);
+ LOGGER.info("[Consumer] Received message: {}", payload);
+
+ Checkpointer checkpointer = (Checkpointer) message.getHeaders().get(CHECKPOINTER);
+ if (checkpointer != null) {
+ checkpointer.success()
+ .doOnSuccess(s -> LOGGER.info("[Consumer] Message checkpointed"))
+ .doOnError(e -> LOGGER.error("[Consumer] Checkpoint failed", e))
+ .block();
+ }
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-azure-docker-compose/service-bus/spring-cloud-stream-binders/src/test/resources/Config.json b/spring-cloud-azure-docker-compose/service-bus/spring-cloud-stream-binders/src/test/resources/Config.json
new file mode 100644
index 000000000..45c368034
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/service-bus/spring-cloud-stream-binders/src/test/resources/Config.json
@@ -0,0 +1,108 @@
+{
+ "UserConfig": {
+ "Namespaces": [
+ {
+ "Name": "sbemulatorns",
+ "Queues": [
+ {
+ "Name": "queue.1",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "DuplicateDetectionHistoryTimeWindow": "PT20S",
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "RequiresDuplicateDetection": false,
+ "RequiresSession": false
+ }
+ }
+ ],
+
+ "Topics": [
+ {
+ "Name": "topic.1",
+ "Properties": {
+ "DefaultMessageTimeToLive": "PT1H",
+ "DuplicateDetectionHistoryTimeWindow": "PT20S",
+ "RequiresDuplicateDetection": false
+ },
+ "Subscriptions": [
+ {
+ "Name": "subscription.1",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "RequiresSession": false
+ },
+ "Rules": [
+ {
+ "Name": "app-prop-filter-1",
+ "Properties": {
+ "FilterType": "Correlation",
+ "CorrelationFilter": {
+ "ContentType": "application/text",
+ "CorrelationId": "id1",
+ "Label": "subject1",
+ "MessageId": "msgid1",
+ "ReplyTo": "someQueue",
+ "ReplyToSessionId": "sessionId",
+ "SessionId": "session1",
+ "To": "xyz"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "Name": "subscription.2",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "RequiresSession": false
+ },
+ "Rules": [
+ {
+ "Name": "user-prop-filter-1",
+ "Properties": {
+ "FilterType": "Correlation",
+ "CorrelationFilter": {
+ "Properties": {
+ "prop3": "value3"
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "Name": "subscription.3",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "RequiresSession": false
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "Logging": {
+ "Type": "File"
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-azure-docker-compose/service-bus/spring-cloud-stream-binders/src/test/resources/servicebus-compose.yaml b/spring-cloud-azure-docker-compose/service-bus/spring-cloud-stream-binders/src/test/resources/servicebus-compose.yaml
new file mode 100644
index 000000000..bda36d124
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/service-bus/spring-cloud-stream-binders/src/test/resources/servicebus-compose.yaml
@@ -0,0 +1,30 @@
+services:
+ servicebus:
+ image: mcr.microsoft.com/azure-messaging/servicebus-emulator:latest
+ pull_policy: always
+ volumes:
+ - "./Config.json:/ServiceBus_Emulator/ConfigFiles/Config.json"
+ ports:
+ - "5672"
+ environment:
+ SQL_SERVER: sqledge
+ MSSQL_SA_PASSWORD: A_Str0ng_Required_Password
+ ACCEPT_EULA: Y
+ depends_on:
+ - sqledge
+ networks:
+ sb-emulator:
+ aliases:
+ - "sb-emulator"
+ sqledge:
+ image: "mcr.microsoft.com/azure-sql-edge:latest"
+ networks:
+ sb-emulator:
+ aliases:
+ - "sqledge"
+ environment:
+ ACCEPT_EULA: Y
+ MSSQL_SA_PASSWORD: A_Str0ng_Required_Password
+
+networks:
+ sb-emulator:
\ No newline at end of file
diff --git a/spring-cloud-azure-docker-compose/service-bus/spring-messaging/pom.xml b/spring-cloud-azure-docker-compose/service-bus/spring-messaging/pom.xml
new file mode 100644
index 000000000..7a09085d8
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/service-bus/spring-messaging/pom.xml
@@ -0,0 +1,59 @@
+
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.3
+
+
+
+ com.azure.spring
+ spring-cloud-azure-docker-compose-for-service-bus-spring-messaging-sample
+ 1.0.0
+ jar
+
+
+ 17
+ 17
+ 7.1.0
+
+
+
+
+
+ com.azure.spring
+ spring-cloud-azure-dependencies
+ ${version.spring.cloud.azure}
+ pom
+ import
+
+
+
+
+
+
+ com.azure.spring
+ spring-messaging-azure-servicebus
+
+
+ com.azure.spring
+ spring-cloud-azure-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-docker-compose
+ test
+
+
+
+
\ No newline at end of file
diff --git a/spring-cloud-azure-docker-compose/service-bus/spring-messaging/src/test/java/ServiceBusDockerComposeTest.java b/spring-cloud-azure-docker-compose/service-bus/spring-messaging/src/test/java/ServiceBusDockerComposeTest.java
new file mode 100644
index 000000000..1e035d1ce
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/service-bus/spring-messaging/src/test/java/ServiceBusDockerComposeTest.java
@@ -0,0 +1,107 @@
+import com.azure.messaging.servicebus.ServiceBusMessage;
+import com.azure.messaging.servicebus.ServiceBusSenderClient;
+import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.AzureServiceBusAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.AzureServiceBusMessagingAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.properties.AzureServiceBusConnectionDetails;
+import com.azure.spring.cloud.service.servicebus.consumer.ServiceBusErrorHandler;
+import com.azure.spring.cloud.service.servicebus.consumer.ServiceBusRecordMessageListener;
+import com.azure.spring.messaging.servicebus.core.ServiceBusTemplate;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.support.MessageBuilder;
+
+import java.time.Duration;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.waitAtMost;
+
+@SpringBootTest(properties = {
+ "spring.docker.compose.skip.in-tests=false",
+ "spring.docker.compose.file=classpath:servicebus-compose.yaml",
+ "spring.docker.compose.stop.command=down",
+ "spring.docker.compose.readiness.timeout=PT5M",
+ "spring.cloud.azure.servicebus.namespace=sbemulatorns",
+ "spring.cloud.azure.servicebus.entity-name=queue.1",
+ "spring.cloud.azure.servicebus.entity-type=queue",
+ "spring.cloud.azure.servicebus.producer.entity-name=queue.1",
+ "spring.cloud.azure.servicebus.producer.entity-type=queue",
+ "spring.cloud.azure.servicebus.processor.entity-name=queue.1",
+ "spring.cloud.azure.servicebus.processor.entity-type=queue"
+})
+class ServiceBusDockerComposeTest {
+
+ @Autowired
+ private AzureServiceBusConnectionDetails connectionDetails;
+
+ @Autowired
+ private ServiceBusSenderClient senderClient;
+
+ @Autowired
+ private ServiceBusTemplate serviceBusTemplate;
+
+ @Test
+ void connectionDetailsShouldBeProvidedByFactory() {
+ assertThat(connectionDetails).isNotNull();
+ assertThat(connectionDetails.getConnectionString())
+ .isNotBlank()
+ .startsWith("Endpoint=sb://");
+ }
+
+ @Test
+ void senderClientCanSendMessage() {
+ // Wait for Service Bus emulator to be fully ready and queue entity to be available
+ // The emulator depends on SQL Edge and needs time to initialize the messaging entities
+ waitAtMost(Duration.ofSeconds(120)).pollInterval(Duration.ofSeconds(2)).untilAsserted(() -> {
+ this.senderClient.sendMessage(new ServiceBusMessage("Hello World!"));
+ });
+
+ waitAtMost(Duration.ofSeconds(30)).pollDelay(Duration.ofSeconds(5)).untilAsserted(() -> {
+ assertThat(Config.MESSAGES).contains("Hello World!");
+ });
+ }
+
+ @Test
+ void serviceBusTemplateCanSendMessage() {
+ // Wait for Service Bus emulator to be fully ready and queue entity to be available
+ // The emulator depends on SQL Edge and needs time to initialize the messaging entities
+ waitAtMost(Duration.ofSeconds(120)).pollInterval(Duration.ofSeconds(2)).untilAsserted(() -> {
+ this.serviceBusTemplate.sendAsync("queue.1",
+ MessageBuilder.withPayload("Hello from ServiceBusTemplate!").build()).block(Duration.ofSeconds(10));
+ });
+
+ waitAtMost(Duration.ofSeconds(30)).pollDelay(Duration.ofSeconds(5)).untilAsserted(() -> {
+ assertThat(Config.MESSAGES).contains("Hello from ServiceBusTemplate!");
+ });
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @ImportAutoConfiguration(classes = {
+ AzureGlobalPropertiesAutoConfiguration.class,
+ AzureServiceBusAutoConfiguration.class,
+ AzureServiceBusMessagingAutoConfiguration.class})
+ static class Config {
+
+ private static final Set MESSAGES = ConcurrentHashMap.newKeySet();
+
+ @Bean
+ ServiceBusRecordMessageListener processMessage() {
+ return context -> {
+ MESSAGES.add(context.getMessage().getBody().toString());
+ };
+ }
+
+ @Bean
+ ServiceBusErrorHandler errorHandler() {
+ // No-op error handler for tests: acknowledge errors without affecting test execution.
+ return (context) -> {
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-azure-docker-compose/service-bus/spring-messaging/src/test/resources/Config.json b/spring-cloud-azure-docker-compose/service-bus/spring-messaging/src/test/resources/Config.json
new file mode 100644
index 000000000..45c368034
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/service-bus/spring-messaging/src/test/resources/Config.json
@@ -0,0 +1,108 @@
+{
+ "UserConfig": {
+ "Namespaces": [
+ {
+ "Name": "sbemulatorns",
+ "Queues": [
+ {
+ "Name": "queue.1",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "DuplicateDetectionHistoryTimeWindow": "PT20S",
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "RequiresDuplicateDetection": false,
+ "RequiresSession": false
+ }
+ }
+ ],
+
+ "Topics": [
+ {
+ "Name": "topic.1",
+ "Properties": {
+ "DefaultMessageTimeToLive": "PT1H",
+ "DuplicateDetectionHistoryTimeWindow": "PT20S",
+ "RequiresDuplicateDetection": false
+ },
+ "Subscriptions": [
+ {
+ "Name": "subscription.1",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "RequiresSession": false
+ },
+ "Rules": [
+ {
+ "Name": "app-prop-filter-1",
+ "Properties": {
+ "FilterType": "Correlation",
+ "CorrelationFilter": {
+ "ContentType": "application/text",
+ "CorrelationId": "id1",
+ "Label": "subject1",
+ "MessageId": "msgid1",
+ "ReplyTo": "someQueue",
+ "ReplyToSessionId": "sessionId",
+ "SessionId": "session1",
+ "To": "xyz"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "Name": "subscription.2",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "RequiresSession": false
+ },
+ "Rules": [
+ {
+ "Name": "user-prop-filter-1",
+ "Properties": {
+ "FilterType": "Correlation",
+ "CorrelationFilter": {
+ "Properties": {
+ "prop3": "value3"
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "Name": "subscription.3",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "RequiresSession": false
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "Logging": {
+ "Type": "File"
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-azure-docker-compose/service-bus/spring-messaging/src/test/resources/servicebus-compose.yaml b/spring-cloud-azure-docker-compose/service-bus/spring-messaging/src/test/resources/servicebus-compose.yaml
new file mode 100644
index 000000000..bda36d124
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/service-bus/spring-messaging/src/test/resources/servicebus-compose.yaml
@@ -0,0 +1,30 @@
+services:
+ servicebus:
+ image: mcr.microsoft.com/azure-messaging/servicebus-emulator:latest
+ pull_policy: always
+ volumes:
+ - "./Config.json:/ServiceBus_Emulator/ConfigFiles/Config.json"
+ ports:
+ - "5672"
+ environment:
+ SQL_SERVER: sqledge
+ MSSQL_SA_PASSWORD: A_Str0ng_Required_Password
+ ACCEPT_EULA: Y
+ depends_on:
+ - sqledge
+ networks:
+ sb-emulator:
+ aliases:
+ - "sb-emulator"
+ sqledge:
+ image: "mcr.microsoft.com/azure-sql-edge:latest"
+ networks:
+ sb-emulator:
+ aliases:
+ - "sqledge"
+ environment:
+ ACCEPT_EULA: Y
+ MSSQL_SA_PASSWORD: A_Str0ng_Required_Password
+
+networks:
+ sb-emulator:
\ No newline at end of file
diff --git a/spring-cloud-azure-docker-compose/storage-blob/pom.xml b/spring-cloud-azure-docker-compose/storage-blob/pom.xml
new file mode 100644
index 000000000..f26c82524
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/storage-blob/pom.xml
@@ -0,0 +1,54 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.3
+
+
+
+ com.azure.spring
+ spring-cloud-azure-docker-compose-for-storage-blob-sample
+ 1.0.0
+ jar
+
+
+ 17
+ 17
+ 7.1.0
+
+
+
+
+
+ com.azure.spring
+ spring-cloud-azure-dependencies
+ ${version.spring.cloud.azure}
+ pom
+ import
+
+
+
+
+
+
+ com.azure.spring
+ spring-cloud-azure-starter-storage-blob
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-docker-compose
+ test
+
+
+
+
diff --git a/spring-cloud-azure-docker-compose/storage-blob/src/test/java/AzureBlobResourceDockerComposeTest.java b/spring-cloud-azure-docker-compose/storage-blob/src/test/java/AzureBlobResourceDockerComposeTest.java
new file mode 100644
index 000000000..8bfe2f3de
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/storage-blob/src/test/java/AzureBlobResourceDockerComposeTest.java
@@ -0,0 +1,46 @@
+import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.storage.blob.AzureStorageBlobAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.storage.blob.AzureStorageBlobResourceAutoConfiguration;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.WritableResource;
+import org.springframework.util.StreamUtils;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(properties = {
+ "spring.docker.compose.skip.in-tests=false",
+ "spring.docker.compose.file=classpath:storage-compose.yaml",
+ "spring.docker.compose.stop.command=down"
+})
+public class AzureBlobResourceDockerComposeTest {
+
+ @Value("azure-blob://testcontainers/message.txt")
+ private Resource blobFile;
+
+ @Test
+ void blobResourceShouldWriteAndReadContent() throws IOException {
+ String originalContent = "Hello World!";
+ try (OutputStream os = ((WritableResource) this.blobFile).getOutputStream()) {
+ os.write(originalContent.getBytes());
+ }
+ String resultContent = StreamUtils.copyToString(this.blobFile.getInputStream(), Charset.defaultCharset());
+ assertThat(resultContent).isEqualTo(originalContent);
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @ImportAutoConfiguration(classes = {
+ AzureGlobalPropertiesAutoConfiguration.class,
+ AzureStorageBlobAutoConfiguration.class,
+ AzureStorageBlobResourceAutoConfiguration.class})
+ static class Config {
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-azure-docker-compose/storage-blob/src/test/resources/storage-compose.yaml b/spring-cloud-azure-docker-compose/storage-blob/src/test/resources/storage-compose.yaml
new file mode 100644
index 000000000..ed3907da5
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/storage-blob/src/test/resources/storage-compose.yaml
@@ -0,0 +1,8 @@
+services:
+ storage:
+ image: mcr.microsoft.com/azure-storage/azurite:latest
+ ports:
+ - '10000'
+ - '10001'
+ - '10002'
+ command: azurite -l /data --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0 --skipApiVersionCheck
\ No newline at end of file
diff --git a/spring-cloud-azure-docker-compose/storage-queue/pom.xml b/spring-cloud-azure-docker-compose/storage-queue/pom.xml
new file mode 100644
index 000000000..48e27d8c8
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/storage-queue/pom.xml
@@ -0,0 +1,55 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.3
+
+
+
+ com.azure.spring
+ spring-cloud-azure-docker-compose-for-storage-queue-sample
+ 1.0.0
+ jar
+
+
+ 17
+ 17
+ 7.1.0
+
+
+
+
+
+ com.azure.spring
+ spring-cloud-azure-dependencies
+ ${version.spring.cloud.azure}
+ pom
+ import
+
+
+
+
+
+
+ com.azure.spring
+ spring-cloud-azure-starter-storage-queue
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-docker-compose
+ test
+
+
+
+
diff --git a/spring-cloud-azure-docker-compose/storage-queue/src/test/java/StorageQueueDockerComposeTest.java b/spring-cloud-azure-docker-compose/storage-queue/src/test/java/StorageQueueDockerComposeTest.java
new file mode 100644
index 000000000..188ce5249
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/storage-queue/src/test/java/StorageQueueDockerComposeTest.java
@@ -0,0 +1,39 @@
+import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.storage.queue.AzureStorageQueueAutoConfiguration;
+import com.azure.storage.queue.QueueClient;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Configuration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(properties = {
+ "spring.docker.compose.skip.in-tests=false",
+ "spring.docker.compose.file=classpath:storage-compose.yaml",
+ "spring.docker.compose.stop.command=down",
+ "spring.cloud.azure.storage.queue.queue-name=devstoreaccount1/tc-queue"
+})
+class StorageQueueDockerComposeTest {
+
+ @Autowired
+ private QueueClient queueClient;
+
+ @Test
+ void queueClientShouldSendAndReceiveMessage() {
+ String message = "Hello World!";
+ this.queueClient.create();
+ this.queueClient.sendMessage(message);
+ var messageItem = this.queueClient.receiveMessage();
+ assertThat(messageItem.getBody().toString()).isEqualTo(message);
+ }
+
+ @Configuration
+ @ImportAutoConfiguration(classes = {
+ AzureGlobalPropertiesAutoConfiguration.class,
+ AzureStorageQueueAutoConfiguration.class})
+ static class Config {
+ }
+
+}
diff --git a/spring-cloud-azure-docker-compose/storage-queue/src/test/resources/storage-compose.yaml b/spring-cloud-azure-docker-compose/storage-queue/src/test/resources/storage-compose.yaml
new file mode 100644
index 000000000..f84556f7d
--- /dev/null
+++ b/spring-cloud-azure-docker-compose/storage-queue/src/test/resources/storage-compose.yaml
@@ -0,0 +1,8 @@
+services:
+ storage:
+ image: mcr.microsoft.com/azure-storage/azurite:latest
+ ports:
+ - '10000'
+ - '10001'
+ - '10002'
+ command: azurite -l /data --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0 --skipApiVersionCheck
diff --git a/testcontainers/README.md b/spring-cloud-azure-testcontainers/README.md
similarity index 100%
rename from testcontainers/README.md
rename to spring-cloud-azure-testcontainers/README.md
diff --git a/testcontainers/spring-cloud-azure-testcontainers-for-cosmos-sample/pom.xml b/spring-cloud-azure-testcontainers/cosmos/pom.xml
similarity index 75%
rename from testcontainers/spring-cloud-azure-testcontainers-for-cosmos-sample/pom.xml
rename to spring-cloud-azure-testcontainers/cosmos/pom.xml
index 7b23492b2..4921f336f 100644
--- a/testcontainers/spring-cloud-azure-testcontainers-for-cosmos-sample/pom.xml
+++ b/spring-cloud-azure-testcontainers/cosmos/pom.xml
@@ -15,12 +15,9 @@
1.0.0
jar
- TestContainers for Azure Cosmos DB
-
17
17
- 3.4.0
7.1.0
@@ -38,13 +35,8 @@
- com.azure.spring
- spring-cloud-azure-starter-cosmos
- test
-
-
- com.azure.spring
- spring-cloud-azure-testcontainers
+ org.springframework.boot
+ spring-boot-starter-test
test
@@ -53,23 +45,13 @@
test
- org.testcontainers
- testcontainers-azure
- test
-
-
- org.springframework.boot
- spring-boot-test
- test
-
-
- org.junit.jupiter
- junit-jupiter-api
+ com.azure.spring
+ spring-cloud-azure-testcontainers
test
- org.assertj
- assertj-core
+ com.azure.spring
+ spring-cloud-azure-starter-cosmos
test
diff --git a/testcontainers/spring-cloud-azure-testcontainers-for-cosmos-sample/src/test/java/CosmosTestcontainersTest.java b/spring-cloud-azure-testcontainers/cosmos/src/test/java/CosmosTestcontainersTest.java
similarity index 100%
rename from testcontainers/spring-cloud-azure-testcontainers-for-cosmos-sample/src/test/java/CosmosTestcontainersTest.java
rename to spring-cloud-azure-testcontainers/cosmos/src/test/java/CosmosTestcontainersTest.java
diff --git a/spring-cloud-azure-testcontainers/event-hubs/client/pom.xml b/spring-cloud-azure-testcontainers/event-hubs/client/pom.xml
new file mode 100644
index 000000000..83edd2f79
--- /dev/null
+++ b/spring-cloud-azure-testcontainers/event-hubs/client/pom.xml
@@ -0,0 +1,61 @@
+
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.3
+
+
+
+ com.azure.spring
+ spring-cloud-azure-testcontainers-for-event-hubs-client-sample
+ 1.0.0
+ jar
+
+
+ 17
+ 17
+ 7.1.0
+
+
+
+
+
+ com.azure.spring
+ spring-cloud-azure-dependencies
+ ${version.spring.cloud.azure}
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.testcontainers
+ testcontainers-junit-jupiter
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-testcontainers
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-starter-eventhubs
+ test
+
+
+
+
\ No newline at end of file
diff --git a/spring-cloud-azure-testcontainers/event-hubs/client/src/test/java/EventHubsTestContainerTest.java b/spring-cloud-azure-testcontainers/event-hubs/client/src/test/java/EventHubsTestContainerTest.java
new file mode 100644
index 000000000..e68b2ee3d
--- /dev/null
+++ b/spring-cloud-azure-testcontainers/event-hubs/client/src/test/java/EventHubsTestContainerTest.java
@@ -0,0 +1,78 @@
+import com.azure.messaging.eventhubs.EventData;
+import com.azure.messaging.eventhubs.EventHubProducerClient;
+import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.eventhubs.AzureEventHubsAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.eventhubs.properties.AzureEventHubsConnectionDetails;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+import org.testcontainers.azure.AzuriteContainer;
+import org.testcontainers.azure.EventHubsEmulatorContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.MountableFile;
+
+import java.time.Duration;
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.waitAtMost;
+
+@SpringJUnitConfig
+@TestPropertySource(properties = { "spring.cloud.azure.eventhubs.event-hub-name=eh1",
+ "spring.cloud.azure.eventhubs.producer.event-hub-name=eh1" })
+@Testcontainers
+class EventHubsTestContainerTest {
+
+ private static final Network NETWORK = Network.newNetwork();
+
+ private static final AzuriteContainer AZURITE = new AzuriteContainer(
+ "mcr.microsoft.com/azure-storage/azurite:latest")
+ .withNetwork(NETWORK)
+ .withNetworkAliases("azurite");
+
+ @Container
+ @ServiceConnection
+ private static final EventHubsEmulatorContainer EVENT_HUBS = new EventHubsEmulatorContainer(
+ "mcr.microsoft.com/azure-messaging/eventhubs-emulator:latest")
+ .acceptLicense()
+ .withCopyFileToContainer(MountableFile.forClasspathResource("Config.json"),
+ "/Eventhubs_Emulator/ConfigFiles/Config.json")
+ .withNetwork(NETWORK)
+ .withAzuriteContainer(AZURITE);
+
+ @Autowired
+ private AzureEventHubsConnectionDetails connectionDetails;
+
+ @Autowired
+ private EventHubProducerClient producerClient;
+
+ @Test
+ void connectionDetailsShouldBeProvidedByFactory() {
+ assertThat(connectionDetails).isNotNull();
+ assertThat(connectionDetails.getConnectionString())
+ .isNotBlank()
+ .startsWith("Endpoint=sb://");
+ }
+
+ @Test
+ void producerClientCanSendMessage() {
+ // Wait for Event Hubs emulator to be fully ready and event hub entity to be available
+ waitAtMost(Duration.ofSeconds(120)).pollInterval(Duration.ofSeconds(2)).untilAsserted(() -> {
+ EventData event = new EventData("Hello World!");
+ this.producerClient.send(Collections.singletonList(event));
+ });
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @ImportAutoConfiguration(classes = {AzureGlobalPropertiesAutoConfiguration.class,
+ AzureEventHubsAutoConfiguration.class})
+ static class Config {
+
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-azure-testcontainers/event-hubs/client/src/test/resources/Config.json b/spring-cloud-azure-testcontainers/event-hubs/client/src/test/resources/Config.json
new file mode 100644
index 000000000..4610f9a23
--- /dev/null
+++ b/spring-cloud-azure-testcontainers/event-hubs/client/src/test/resources/Config.json
@@ -0,0 +1,20 @@
+{
+ "UserConfig": {
+ "NamespaceConfig": [
+ {
+ "Type": "EventHub",
+ "Name": "emulatorns1",
+ "Entities": [
+ {
+ "Name": "eh1",
+ "PartitionCount": "2",
+ "ConsumerGroups": []
+ }
+ ]
+ }
+ ],
+ "LoggingConfig": {
+ "Type": "File"
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-azure-testcontainers/event-hubs/spring-cloud-stream-binders/pom.xml b/spring-cloud-azure-testcontainers/event-hubs/spring-cloud-stream-binders/pom.xml
new file mode 100644
index 000000000..e1505bd62
--- /dev/null
+++ b/spring-cloud-azure-testcontainers/event-hubs/spring-cloud-stream-binders/pom.xml
@@ -0,0 +1,61 @@
+
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.3
+
+
+
+ com.azure.spring
+ spring-cloud-azure-testcontainers-for-event-hubs-spring-cloud-stream-sample
+ 1.0.0
+ jar
+
+
+ 17
+ 17
+ 7.1.0
+
+
+
+
+
+ com.azure.spring
+ spring-cloud-azure-dependencies
+ ${version.spring.cloud.azure}
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.testcontainers
+ testcontainers-junit-jupiter
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-testcontainers
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-stream-binder-eventhubs
+ test
+
+
+
+
\ No newline at end of file
diff --git a/spring-cloud-azure-testcontainers/event-hubs/spring-cloud-stream-binders/src/test/java/EventHubsTestContainerTest.java b/spring-cloud-azure-testcontainers/event-hubs/spring-cloud-stream-binders/src/test/java/EventHubsTestContainerTest.java
new file mode 100644
index 000000000..f9d54e858
--- /dev/null
+++ b/spring-cloud-azure-testcontainers/event-hubs/spring-cloud-stream-binders/src/test/java/EventHubsTestContainerTest.java
@@ -0,0 +1,137 @@
+import com.azure.messaging.eventhubs.checkpointstore.blob.BlobCheckpointStore;
+import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.eventhubs.AzureEventHubsAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.eventhubs.AzureEventHubsMessagingAutoConfiguration;
+import com.azure.spring.messaging.checkpoint.Checkpointer;
+import com.azure.storage.blob.BlobContainerAsyncClient;
+import com.azure.storage.blob.BlobServiceAsyncClient;
+import com.azure.storage.blob.BlobServiceClientBuilder;
+import com.azure.storage.blob.BlobServiceVersion;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+import org.testcontainers.azure.AzuriteContainer;
+import org.testcontainers.azure.EventHubsEmulatorContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.MountableFile;
+
+import java.time.Duration;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import static com.azure.spring.messaging.AzureHeaders.CHECKPOINTER;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.waitAtMost;
+
+@SpringJUnitConfig
+@TestPropertySource(properties = {
+ "spring.cloud.function.definition=consume;supply",
+ "spring.cloud.stream.bindings.consume-in-0.destination=eh1",
+ "spring.cloud.stream.bindings.consume-in-0.group=$Default",
+ "spring.cloud.stream.bindings.supply-out-0.destination=eh1",
+ "spring.cloud.stream.eventhubs.bindings.consume-in-0.consumer.checkpoint.mode=MANUAL",
+ "spring.cloud.stream.poller.fixed-delay=1000",
+ "spring.cloud.stream.poller.initial-delay=0"})
+@Testcontainers
+class EventHubsTestContainerTest {
+
+ private static final Network NETWORK = Network.newNetwork();
+
+ private static final AzuriteContainer AZURITE = new AzuriteContainer(
+ "mcr.microsoft.com/azure-storage/azurite:latest")
+ .withCommand("azurite", "--blobHost", "0.0.0.0", "--queueHost", "0.0.0.0", "--tableHost", "0.0.0.0",
+ "--skipApiVersionCheck")
+ .withNetwork(NETWORK)
+ .withNetworkAliases("azurite");
+
+ @Container
+ @ServiceConnection
+ private static final EventHubsEmulatorContainer EVENT_HUBS = new EventHubsEmulatorContainer(
+ "mcr.microsoft.com/azure-messaging/eventhubs-emulator:latest")
+ .acceptLicense()
+ .withCopyFileToContainer(MountableFile.forClasspathResource("Config.json"),
+ "/Eventhubs_Emulator/ConfigFiles/Config.json")
+ .withNetwork(NETWORK)
+ .withAzuriteContainer(AZURITE);
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(EventHubsTestContainerTest.class);
+ private static final Set RECEIVED_MESSAGES = ConcurrentHashMap.newKeySet();
+ private static final AtomicInteger MESSAGE_SEQUENCE = new AtomicInteger(0);
+
+ @Test
+ void supplierAndConsumerShouldWorkThroughEventHubs() {
+ waitAtMost(Duration.ofSeconds(120))
+ .pollDelay(Duration.ofSeconds(2))
+ .pollInterval(Duration.ofSeconds(2))
+ .untilAsserted(() -> {
+ assertThat(RECEIVED_MESSAGES).isNotEmpty();
+ LOGGER.info("✓ Test passed - Consumer received {} message(s)", RECEIVED_MESSAGES.size());
+ });
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @EnableAutoConfiguration
+ @ImportAutoConfiguration(classes = {
+ AzureGlobalPropertiesAutoConfiguration.class,
+ AzureEventHubsAutoConfiguration.class,
+ AzureEventHubsMessagingAutoConfiguration.class})
+ static class Config {
+
+ private static final String CHECKPOINT_CONTAINER_NAME = "eventhubs-checkpoint";
+
+ @Bean
+ public BlobCheckpointStore blobCheckpointStore() {
+ BlobServiceAsyncClient blobServiceAsyncClient = new BlobServiceClientBuilder()
+ .connectionString(AZURITE.getConnectionString())
+ .serviceVersion(BlobServiceVersion.V2025_01_05)
+ .buildAsyncClient();
+ BlobContainerAsyncClient containerAsyncClient = blobServiceAsyncClient
+ .getBlobContainerAsyncClient(CHECKPOINT_CONTAINER_NAME);
+ if (Boolean.FALSE.equals(containerAsyncClient.exists().block(Duration.ofSeconds(3)))) {
+ containerAsyncClient.create().block(Duration.ofSeconds(3));
+ }
+ return new BlobCheckpointStore(containerAsyncClient);
+ }
+
+ @Bean
+ public Supplier> supply() {
+ return () -> {
+ int sequence = MESSAGE_SEQUENCE.getAndIncrement();
+ String payload = "Hello world, " + sequence;
+ LOGGER.info("[Supplier] Invoked - message sequence: {}", sequence);
+ return MessageBuilder.withPayload(payload).build();
+ };
+ }
+
+ @Bean
+ public Consumer> consume() {
+ return message -> {
+ String payload = message.getPayload();
+ RECEIVED_MESSAGES.add(payload);
+ LOGGER.info("[Consumer] Received message: {}", payload);
+
+ Checkpointer checkpointer = (Checkpointer) message.getHeaders().get(CHECKPOINTER);
+ if (checkpointer != null) {
+ checkpointer.success()
+ .doOnSuccess(s -> LOGGER.info("[Consumer] Message checkpointed"))
+ .doOnError(e -> LOGGER.error("[Consumer] Checkpoint failed", e))
+ .block();
+ }
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-azure-testcontainers/event-hubs/spring-cloud-stream-binders/src/test/resources/Config.json b/spring-cloud-azure-testcontainers/event-hubs/spring-cloud-stream-binders/src/test/resources/Config.json
new file mode 100644
index 000000000..4610f9a23
--- /dev/null
+++ b/spring-cloud-azure-testcontainers/event-hubs/spring-cloud-stream-binders/src/test/resources/Config.json
@@ -0,0 +1,20 @@
+{
+ "UserConfig": {
+ "NamespaceConfig": [
+ {
+ "Type": "EventHub",
+ "Name": "emulatorns1",
+ "Entities": [
+ {
+ "Name": "eh1",
+ "PartitionCount": "2",
+ "ConsumerGroups": []
+ }
+ ]
+ }
+ ],
+ "LoggingConfig": {
+ "Type": "File"
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-azure-testcontainers/service-bus/spring-cloud-stream-binders/pom.xml b/spring-cloud-azure-testcontainers/service-bus/spring-cloud-stream-binders/pom.xml
new file mode 100644
index 000000000..6ff966caf
--- /dev/null
+++ b/spring-cloud-azure-testcontainers/service-bus/spring-cloud-stream-binders/pom.xml
@@ -0,0 +1,66 @@
+
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.3
+
+
+
+ com.azure.spring
+ spring-cloud-azure-testcontainers-for-service-bus-spring-cloud-stream-sample
+ 1.0.0
+ jar
+
+
+ 17
+ 17
+ 7.1.0
+
+
+
+
+
+ com.azure.spring
+ spring-cloud-azure-dependencies
+ ${version.spring.cloud.azure}
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.testcontainers
+ testcontainers-junit-jupiter
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-testcontainers
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-stream-binder-servicebus
+ test
+
+
+ com.microsoft.sqlserver
+ mssql-jdbc
+ test
+
+
+
+
\ No newline at end of file
diff --git a/spring-cloud-azure-testcontainers/service-bus/spring-cloud-stream-binders/src/test/java/ServiceBusTestContainerTest.java b/spring-cloud-azure-testcontainers/service-bus/spring-cloud-stream-binders/src/test/java/ServiceBusTestContainerTest.java
new file mode 100644
index 000000000..f7e28dcce
--- /dev/null
+++ b/spring-cloud-azure-testcontainers/service-bus/spring-cloud-stream-binders/src/test/java/ServiceBusTestContainerTest.java
@@ -0,0 +1,114 @@
+import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.AzureServiceBusAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.AzureServiceBusMessagingAutoConfiguration;
+import com.azure.spring.messaging.checkpoint.Checkpointer;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+import org.testcontainers.azure.ServiceBusEmulatorContainer;
+import org.testcontainers.containers.MSSQLServerContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.MountableFile;
+
+import java.time.Duration;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import static com.azure.spring.messaging.AzureHeaders.CHECKPOINTER;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.waitAtMost;
+
+@SpringJUnitConfig
+@TestPropertySource(properties = {
+ "spring.cloud.function.definition=consume;supply",
+ "spring.cloud.stream.bindings.consume-in-0.destination=queue.1",
+ "spring.cloud.stream.bindings.supply-out-0.destination=queue.1",
+ "spring.cloud.stream.servicebus.bindings.consume-in-0.consumer.auto-complete=false",
+ "spring.cloud.stream.servicebus.bindings.supply-out-0.producer.entity-type=queue",
+ "spring.cloud.stream.poller.fixed-delay=1000",
+ "spring.cloud.stream.poller.initial-delay=0"})
+@Testcontainers
+class ServiceBusTestContainerTest {
+
+ private static final Network NETWORK = Network.newNetwork();
+
+ private static final MSSQLServerContainer> SQLSERVER = new MSSQLServerContainer<>(
+ "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04")
+ .acceptLicense()
+ .withNetwork(NETWORK)
+ .withNetworkAliases("sqlserver");
+
+ @Container
+ @ServiceConnection
+ private static final ServiceBusEmulatorContainer SERVICE_BUS = new ServiceBusEmulatorContainer(
+ "mcr.microsoft.com/azure-messaging/servicebus-emulator:latest")
+ .acceptLicense()
+ .withCopyFileToContainer(MountableFile.forClasspathResource("Config.json"),
+ "/ServiceBus_Emulator/ConfigFiles/Config.json")
+ .withNetwork(NETWORK)
+ .withMsSqlServerContainer(SQLSERVER);
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ServiceBusTestContainerTest.class);
+ private static final Set RECEIVED_MESSAGES = ConcurrentHashMap.newKeySet();
+ private static final AtomicInteger MESSAGE_SEQUENCE = new AtomicInteger(0);
+
+ @Test
+ void supplierAndConsumerShouldWorkThroughServiceBusQueue() {
+ waitAtMost(Duration.ofSeconds(60))
+ .pollDelay(Duration.ofSeconds(2))
+ .untilAsserted(() -> {
+ assertThat(RECEIVED_MESSAGES).isNotEmpty();
+ LOGGER.info("✓ Test passed - Consumer received {} message(s)", RECEIVED_MESSAGES.size());
+ });
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @EnableAutoConfiguration
+ @ImportAutoConfiguration(classes = {
+ AzureGlobalPropertiesAutoConfiguration.class,
+ AzureServiceBusAutoConfiguration.class,
+ AzureServiceBusMessagingAutoConfiguration.class})
+ static class Config {
+
+ @Bean
+ public Supplier> supply() {
+ return () -> {
+ int sequence = MESSAGE_SEQUENCE.getAndIncrement();
+ String payload = "Hello world, " + sequence;
+ LOGGER.info("[Supplier] Invoked - message sequence: {}", sequence);
+ return MessageBuilder.withPayload(payload).build();
+ };
+ }
+
+ @Bean
+ public Consumer> consume() {
+ return message -> {
+ String payload = message.getPayload();
+ RECEIVED_MESSAGES.add(payload);
+ LOGGER.info("[Consumer] Received message: {}", payload);
+
+ Checkpointer checkpointer = (Checkpointer) message.getHeaders().get(CHECKPOINTER);
+ if (checkpointer != null) {
+ checkpointer.success()
+ .doOnSuccess(s -> LOGGER.info("[Consumer] Message checkpointed"))
+ .doOnError(e -> LOGGER.error("[Consumer] Checkpoint failed", e))
+ .block();
+ }
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-azure-testcontainers/service-bus/spring-cloud-stream-binders/src/test/resources/Config.json b/spring-cloud-azure-testcontainers/service-bus/spring-cloud-stream-binders/src/test/resources/Config.json
new file mode 100644
index 000000000..45c368034
--- /dev/null
+++ b/spring-cloud-azure-testcontainers/service-bus/spring-cloud-stream-binders/src/test/resources/Config.json
@@ -0,0 +1,108 @@
+{
+ "UserConfig": {
+ "Namespaces": [
+ {
+ "Name": "sbemulatorns",
+ "Queues": [
+ {
+ "Name": "queue.1",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "DuplicateDetectionHistoryTimeWindow": "PT20S",
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "RequiresDuplicateDetection": false,
+ "RequiresSession": false
+ }
+ }
+ ],
+
+ "Topics": [
+ {
+ "Name": "topic.1",
+ "Properties": {
+ "DefaultMessageTimeToLive": "PT1H",
+ "DuplicateDetectionHistoryTimeWindow": "PT20S",
+ "RequiresDuplicateDetection": false
+ },
+ "Subscriptions": [
+ {
+ "Name": "subscription.1",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "RequiresSession": false
+ },
+ "Rules": [
+ {
+ "Name": "app-prop-filter-1",
+ "Properties": {
+ "FilterType": "Correlation",
+ "CorrelationFilter": {
+ "ContentType": "application/text",
+ "CorrelationId": "id1",
+ "Label": "subject1",
+ "MessageId": "msgid1",
+ "ReplyTo": "someQueue",
+ "ReplyToSessionId": "sessionId",
+ "SessionId": "session1",
+ "To": "xyz"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "Name": "subscription.2",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "RequiresSession": false
+ },
+ "Rules": [
+ {
+ "Name": "user-prop-filter-1",
+ "Properties": {
+ "FilterType": "Correlation",
+ "CorrelationFilter": {
+ "Properties": {
+ "prop3": "value3"
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "Name": "subscription.3",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "RequiresSession": false
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "Logging": {
+ "Type": "File"
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-azure-testcontainers/service-bus/spring-messaging/pom.xml b/spring-cloud-azure-testcontainers/service-bus/spring-messaging/pom.xml
new file mode 100644
index 000000000..194ef92fe
--- /dev/null
+++ b/spring-cloud-azure-testcontainers/service-bus/spring-messaging/pom.xml
@@ -0,0 +1,71 @@
+
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.3
+
+
+
+ com.azure.spring
+ spring-cloud-azure-testcontainers-for-service-bus-spring-messaging-sample
+ 1.0.0
+ jar
+
+
+ 17
+ 17
+ 7.1.0
+
+
+
+
+
+ com.azure.spring
+ spring-cloud-azure-dependencies
+ ${version.spring.cloud.azure}
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.testcontainers
+ testcontainers-junit-jupiter
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-testcontainers
+ test
+
+
+ com.azure.spring
+ spring-cloud-azure-starter
+ test
+
+
+ com.azure.spring
+ spring-messaging-azure-servicebus
+ test
+
+
+ com.microsoft.sqlserver
+ mssql-jdbc
+ test
+
+
+
+
\ No newline at end of file
diff --git a/spring-cloud-azure-testcontainers/service-bus/spring-messaging/src/test/java/ServiceBusTestContainerTest.java b/spring-cloud-azure-testcontainers/service-bus/spring-messaging/src/test/java/ServiceBusTestContainerTest.java
new file mode 100644
index 000000000..275334ca8
--- /dev/null
+++ b/spring-cloud-azure-testcontainers/service-bus/spring-messaging/src/test/java/ServiceBusTestContainerTest.java
@@ -0,0 +1,123 @@
+import com.azure.messaging.servicebus.ServiceBusMessage;
+import com.azure.messaging.servicebus.ServiceBusSenderClient;
+import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.AzureServiceBusAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.AzureServiceBusMessagingAutoConfiguration;
+import com.azure.spring.cloud.autoconfigure.implementation.servicebus.properties.AzureServiceBusConnectionDetails;
+import com.azure.spring.cloud.service.servicebus.consumer.ServiceBusErrorHandler;
+import com.azure.spring.cloud.service.servicebus.consumer.ServiceBusRecordMessageListener;
+import com.azure.spring.messaging.servicebus.core.ServiceBusTemplate;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+import org.testcontainers.azure.ServiceBusEmulatorContainer;
+import org.testcontainers.containers.MSSQLServerContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.MountableFile;
+
+import java.time.Duration;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.waitAtMost;
+
+@SpringJUnitConfig
+@TestPropertySource(properties = { "spring.cloud.azure.servicebus.entity-name=queue.1",
+ "spring.cloud.azure.servicebus.entity-type=queue" })
+@Testcontainers
+class ServiceBusTestContainerTest {
+
+ private static final Network NETWORK = Network.newNetwork();
+
+ private static final MSSQLServerContainer> SQLSERVER = new MSSQLServerContainer<>(
+ "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04")
+ .acceptLicense()
+ .withNetwork(NETWORK)
+ .withNetworkAliases("sqlserver");
+
+ @Container
+ @ServiceConnection
+ private static final ServiceBusEmulatorContainer SERVICE_BUS = new ServiceBusEmulatorContainer(
+ "mcr.microsoft.com/azure-messaging/servicebus-emulator:latest")
+ .acceptLicense()
+ .withCopyFileToContainer(MountableFile.forClasspathResource("Config.json"),
+ "/ServiceBus_Emulator/ConfigFiles/Config.json")
+ .withNetwork(NETWORK)
+ .withMsSqlServerContainer(SQLSERVER);
+
+ @Autowired
+ private AzureServiceBusConnectionDetails connectionDetails;
+
+ @Autowired
+ private ServiceBusSenderClient senderClient;
+
+ @Autowired
+ private ServiceBusTemplate serviceBusTemplate;
+
+ @Test
+ void connectionDetailsShouldBeProvidedByFactory() {
+ assertThat(connectionDetails).isNotNull();
+ assertThat(connectionDetails.getConnectionString())
+ .isNotBlank()
+ .startsWith("Endpoint=sb://");
+ }
+
+ @Test
+ void senderClientCanSendMessage() {
+ // Wait for Service Bus emulator to be fully ready and queue entity to be available
+ waitAtMost(Duration.ofSeconds(120)).pollInterval(Duration.ofSeconds(2)).untilAsserted(() -> {
+ this.senderClient.sendMessage(new ServiceBusMessage("Hello World!"));
+ });
+
+ waitAtMost(Duration.ofSeconds(30)).untilAsserted(() -> {
+ assertThat(Config.MESSAGES).contains("Hello World!");
+ });
+ }
+
+ @Test
+ void serviceBusTemplateCanSendMessage() {
+ // Wait for Service Bus emulator to be fully ready and queue entity to be available
+ waitAtMost(Duration.ofSeconds(120)).pollInterval(Duration.ofSeconds(2)).untilAsserted(() -> {
+ this.serviceBusTemplate.sendAsync("queue.1",
+ MessageBuilder.withPayload("Hello from ServiceBusTemplate!").build()).block(Duration.ofSeconds(10));
+ });
+
+ waitAtMost(Duration.ofSeconds(30)).untilAsserted(() -> {
+ assertThat(Config.MESSAGES).contains("Hello from ServiceBusTemplate!");
+ });
+ }
+
+
+ @Configuration(proxyBeanMethods = false)
+ @ImportAutoConfiguration(classes = {AzureGlobalPropertiesAutoConfiguration.class,
+ AzureServiceBusAutoConfiguration.class,
+ AzureServiceBusMessagingAutoConfiguration.class})
+ static class Config {
+
+ private static final Set MESSAGES = ConcurrentHashMap.newKeySet();
+
+ @Bean
+ ServiceBusRecordMessageListener processMessage() {
+ return context -> {
+ MESSAGES.add(context.getMessage().getBody().toString());
+ };
+ }
+
+ @Bean
+ ServiceBusErrorHandler errorHandler() {
+ // No-op error handler for tests: acknowledge errors without affecting test execution.
+ return (context) -> {
+ };
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-azure-testcontainers/service-bus/spring-messaging/src/test/resources/Config.json b/spring-cloud-azure-testcontainers/service-bus/spring-messaging/src/test/resources/Config.json
new file mode 100644
index 000000000..45c368034
--- /dev/null
+++ b/spring-cloud-azure-testcontainers/service-bus/spring-messaging/src/test/resources/Config.json
@@ -0,0 +1,108 @@
+{
+ "UserConfig": {
+ "Namespaces": [
+ {
+ "Name": "sbemulatorns",
+ "Queues": [
+ {
+ "Name": "queue.1",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "DuplicateDetectionHistoryTimeWindow": "PT20S",
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "RequiresDuplicateDetection": false,
+ "RequiresSession": false
+ }
+ }
+ ],
+
+ "Topics": [
+ {
+ "Name": "topic.1",
+ "Properties": {
+ "DefaultMessageTimeToLive": "PT1H",
+ "DuplicateDetectionHistoryTimeWindow": "PT20S",
+ "RequiresDuplicateDetection": false
+ },
+ "Subscriptions": [
+ {
+ "Name": "subscription.1",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "RequiresSession": false
+ },
+ "Rules": [
+ {
+ "Name": "app-prop-filter-1",
+ "Properties": {
+ "FilterType": "Correlation",
+ "CorrelationFilter": {
+ "ContentType": "application/text",
+ "CorrelationId": "id1",
+ "Label": "subject1",
+ "MessageId": "msgid1",
+ "ReplyTo": "someQueue",
+ "ReplyToSessionId": "sessionId",
+ "SessionId": "session1",
+ "To": "xyz"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "Name": "subscription.2",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "RequiresSession": false
+ },
+ "Rules": [
+ {
+ "Name": "user-prop-filter-1",
+ "Properties": {
+ "FilterType": "Correlation",
+ "CorrelationFilter": {
+ "Properties": {
+ "prop3": "value3"
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "Name": "subscription.3",
+ "Properties": {
+ "DeadLetteringOnMessageExpiration": false,
+ "DefaultMessageTimeToLive": "PT1H",
+ "LockDuration": "PT1M",
+ "MaxDeliveryCount": 10,
+ "ForwardDeadLetteredMessagesTo": "",
+ "ForwardTo": "",
+ "RequiresSession": false
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "Logging": {
+ "Type": "File"
+ }
+ }
+}
\ No newline at end of file
diff --git a/testcontainers/spring-cloud-azure-testcontainers-for-storage-blob-sample/pom.xml b/spring-cloud-azure-testcontainers/storage-blob/pom.xml
similarity index 82%
rename from testcontainers/spring-cloud-azure-testcontainers-for-storage-blob-sample/pom.xml
rename to spring-cloud-azure-testcontainers/storage-blob/pom.xml
index ad5d25528..aba8de2df 100644
--- a/testcontainers/spring-cloud-azure-testcontainers-for-storage-blob-sample/pom.xml
+++ b/spring-cloud-azure-testcontainers/storage-blob/pom.xml
@@ -16,8 +16,6 @@
1.0.0
jar
- TestContainers for Azure Storage Blob
-
17
17
@@ -38,33 +36,23 @@
- com.azure.spring
- spring-cloud-azure-starter-storage-blob
- test
-
-
- com.azure.spring
- spring-cloud-azure-testcontainers
- test
-
-
- org.junit.jupiter
- junit-jupiter-api
+ org.springframework.boot
+ spring-boot-starter-test
test
- org.springframework.boot
- spring-boot-test
+ org.testcontainers
+ testcontainers-junit-jupiter
test
- org.assertj
- assertj-core
+ com.azure.spring
+ spring-cloud-azure-testcontainers
test
- org.testcontainers
- testcontainers-junit-jupiter
+ com.azure.spring
+ spring-cloud-azure-starter-storage-blob
test
diff --git a/testcontainers/spring-cloud-azure-testcontainers-for-storage-blob-sample/src/test/java/StorageBlobTestcontainersTest.java b/spring-cloud-azure-testcontainers/storage-blob/src/test/java/StorageBlobTestcontainersTest.java
similarity index 63%
rename from testcontainers/spring-cloud-azure-testcontainers-for-storage-blob-sample/src/test/java/StorageBlobTestcontainersTest.java
rename to spring-cloud-azure-testcontainers/storage-blob/src/test/java/StorageBlobTestcontainersTest.java
index c1e56840d..e438310c1 100644
--- a/testcontainers/spring-cloud-azure-testcontainers-for-storage-blob-sample/src/test/java/StorageBlobTestcontainersTest.java
+++ b/spring-cloud-azure-testcontainers/storage-blob/src/test/java/StorageBlobTestcontainersTest.java
@@ -1,19 +1,14 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
import com.azure.spring.cloud.autoconfigure.implementation.context.AzureGlobalPropertiesAutoConfiguration;
import com.azure.spring.cloud.autoconfigure.implementation.storage.blob.AzureStorageBlobAutoConfiguration;
import com.azure.spring.cloud.autoconfigure.implementation.storage.blob.AzureStorageBlobResourceAutoConfiguration;
-
-import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
-import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.WritableResource;
-import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.util.StreamUtils;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
@@ -22,30 +17,24 @@
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
+
import static org.assertj.core.api.Assertions.assertThat;
-@SpringBootTest(classes = StorageBlobTestcontainersTest.class)
+@SpringJUnitConfig
@Testcontainers
-@ExtendWith(SpringExtension.class)
-@ImportAutoConfiguration(classes = { AzureGlobalPropertiesAutoConfiguration.class, AzureStorageBlobAutoConfiguration.class, AzureStorageBlobResourceAutoConfiguration.class})
-public class StorageBlobTestcontainersTest {
+class StorageBlobTestcontainersTest {
@Container
@ServiceConnection
private static final GenericContainer> AZURITE_CONTAINER = new GenericContainer<>(
- "mcr.microsoft.com/azure-storage/azurite:latest")
- .withExposedPorts(10000)
- .withCommand("azurite-blob", "--blobHost", "0.0.0.0", "--skipApiVersionCheck");
+ "mcr.microsoft.com/azure-storage/azurite:latest")
+ .withExposedPorts(10000)
+ .withCommand("azurite --skipApiVersionCheck && azurite -l /data --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0");
@Value("azure-blob://testcontainers/message.txt")
private Resource blobFile;
- @BeforeAll
- public static void setup() {
- AZURITE_CONTAINER.start();
- }
-
@Test
- public void test() throws IOException {
+ void test() throws IOException {
String originalContent = "Hello World!";
try (OutputStream os = ((WritableResource) this.blobFile).getOutputStream()) {
os.write(originalContent.getBytes());
@@ -54,4 +43,8 @@ public void test() throws IOException {
assertThat(resultContent).isEqualTo(originalContent);
}
-}
+ @Configuration(proxyBeanMethods = false)
+ @ImportAutoConfiguration(classes = {AzureGlobalPropertiesAutoConfiguration.class, AzureStorageBlobAutoConfiguration.class, AzureStorageBlobResourceAutoConfiguration.class})
+ static class Config {
+ }
+}
\ No newline at end of file
diff --git a/testcontainers/spring-cloud-azure-testcontainers-for-storage-queue-sample/pom.xml b/spring-cloud-azure-testcontainers/storage-queue/pom.xml
similarity index 83%
rename from testcontainers/spring-cloud-azure-testcontainers-for-storage-queue-sample/pom.xml
rename to spring-cloud-azure-testcontainers/storage-queue/pom.xml
index 955d4c481..574876904 100644
--- a/testcontainers/spring-cloud-azure-testcontainers-for-storage-queue-sample/pom.xml
+++ b/spring-cloud-azure-testcontainers/storage-queue/pom.xml
@@ -15,7 +15,6 @@
spring-cloud-azure-testcontainers-for-storage-queue-sample
1.0.0
jar
- TestContainers for Storage Queue
17
@@ -37,18 +36,8 @@
- com.azure.spring
- spring-cloud-azure-starter-storage-queue
- test
-
-
- com.azure.spring
- spring-cloud-azure-testcontainers
- test
-
-
- org.junit.jupiter
- junit-jupiter-api
+ org.springframework.boot
+ spring-boot-starter-test
test
@@ -57,13 +46,13 @@
test
- org.springframework.boot
- spring-boot-test
+ com.azure.spring
+ spring-cloud-azure-testcontainers
test
- org.assertj
- assertj-core
+ com.azure.spring
+ spring-cloud-azure-starter-storage-queue
test
diff --git a/testcontainers/spring-cloud-azure-testcontainers-for-storage-queue-sample/src/test/java/StorageQueueTestcontainersTest.java b/spring-cloud-azure-testcontainers/storage-queue/src/test/java/StorageQueueTestcontainersTest.java
similarity index 100%
rename from testcontainers/spring-cloud-azure-testcontainers-for-storage-queue-sample/src/test/java/StorageQueueTestcontainersTest.java
rename to spring-cloud-azure-testcontainers/storage-queue/src/test/java/StorageQueueTestcontainersTest.java