diff --git a/docs/modules/azure.md b/docs/modules/azure.md index 19c141c7639..2d26be1a8cb 100644 --- a/docs/modules/azure.md +++ b/docs/modules/azure.md @@ -5,12 +5,13 @@ This module is INCUBATING. While it is ready for use and operational in the curr Testcontainers module for the Microsoft Azure's [SDK](https://github.com/Azure/azure-sdk-for-java). -Currently, the module supports `Azurite` and `CosmosDB` emulators. In order to use them, you should use the following classes: +Currently, the module supports `Azurite`, `CosmosDB`, and `Servicebus` emulators. In order to use them, you should use the following classes: Class | Container Image -|- AzuriteContainer | [mcr.microsoft.com/azure-storage/azurite](https://github.com/microsoft/containerregistry) CosmosDBEmulatorContainer | [mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator](https://github.com/microsoft/containerregistry) +AzureServicebusEmulatorContainer | [mcr.microsoft.com/azure-messaging/servicebus-emulator](https://github.com/microsoft/containerregistry) ## Usage example @@ -104,6 +105,33 @@ Test against the Emulator: [Testing against Azure CosmosDB Emulator container](../../modules/azure/src/test/java/org/testcontainers/containers/CosmosDBEmulatorContainerTest.java) inside_block:testWithClientAgainstEmulatorContainer +### Azure service bus Emulator + +Start Azure service bus Emulator during a test: + + +[Starting a Azure Service bus Emulator container with custom config](../../modules/azure/src/test/java/org/testcontainers/azure/AzureServicebusEmulatorContainerTest.java) inside_block:emulatorContainerCustomConfig + + +!!! note + This starts the service bus emulator with a custom config. + To use [default config](https://github.com/Azure/azure-service-bus-emulator-installer/blob/main/ServiceBus-Emulator/Config/Config.json) + omit `withConfigFile(...)`. + +!!! note + The service bus emulator requires a database, so a [MSSQLServerContainer](../../modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLServerContainer.java) + is started. + +Build Azure Service bus sender client: + + +[Testing against Azure Service bus Emulator container](../../modules/azure/src/test/java/org/testcontainers/azure/AzureServicebusEmulatorContainerTest.java) inside_block:buildClient + + +* See [Overview of the Azure Service Bus emulator](https://learn.microsoft.com/en-us/azure/service-bus-messaging/overview-emulator) for features and limitations. +* [Test locally by using the Azure Service Bus emulator](https://learn.microsoft.com/en-us/azure/service-bus-messaging/test-locally-with-service-bus-emulator?tabs=docker-linux-container) + + ## Adding this module to your project dependencies Add the following dependency to your `pom.xml`/`build.gradle` file: diff --git a/modules/azure/build.gradle b/modules/azure/build.gradle index c6cfb6738d0..d2093f020f0 100644 --- a/modules/azure/build.gradle +++ b/modules/azure/build.gradle @@ -5,9 +5,13 @@ dependencies { // TODO use JDK's HTTP client and/or Apache HttpClient5 shaded 'com.squareup.okhttp3:okhttp:4.12.0' + implementation project(':mssqlserver') + testImplementation 'com.microsoft.sqlserver:mssql-jdbc:12.8.1.jre8' + testImplementation 'org.assertj:assertj-core:3.26.3' testImplementation 'com.azure:azure-cosmos:4.63.3' testImplementation 'com.azure:azure-storage-blob:12.29.0' testImplementation 'com.azure:azure-storage-queue:12.24.0' testImplementation 'com.azure:azure-data-tables:12.5.0' + testImplementation 'com.azure:azure-messaging-servicebus:7.17.8' } diff --git a/modules/azure/src/main/java/org/testcontainers/azure/AzureServicebusEmulatorContainer.java b/modules/azure/src/main/java/org/testcontainers/azure/AzureServicebusEmulatorContainer.java new file mode 100644 index 00000000000..5b8f1bb8159 --- /dev/null +++ b/modules/azure/src/main/java/org/testcontainers/azure/AzureServicebusEmulatorContainer.java @@ -0,0 +1,86 @@ +package org.testcontainers.azure; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.MSSQLServerContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.MountableFile; + +/** + * Testcontainers implementation for Azure service bus emulator. + *

+ * Supported image: {@code mcr.microsoft.com/azure-messaging/servicebus-emulator} + *

+ *

Exposed ports: 5672

+ *

+ * If the official client, azure-messaging-servicebus, is used the oldest version supported is 7.17.8. + *

+ *

+ * By default, emulator uses config.json configuration file. + * To supply your own config your own config using {@code withConfigFile(MountableFile)} + *

+ *

+ * The service bus emulator requires a database, so a {@code MSSQLServerContainer} is also started. + *

+ */ +public class AzureServicebusEmulatorContainer extends GenericContainer { + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse( + "mcr.microsoft.com/azure-messaging/servicebus-emulator" + ); + + private static final int PORT = 5672; + + /** + * @param dockerImageName specified docker image name to run + */ + public AzureServicebusEmulatorContainer(final DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + withExposedPorts(PORT); + + waitingFor(Wait.forLogMessage(".*Emulator Service is Successfully Up!.*", 1)); + + withNetwork(Network.SHARED); + withNetworkAliases("sb-emulator"); + MSSQLServerContainer mssqlServerContainer = new MSSQLServerContainer(DockerImageName.parse("mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04")) + .acceptLicense(); + String mssqlNetworkAlias = "sqlserver"; + dependsOn( + mssqlServerContainer + .withNetwork(Network.SHARED) + .withNetworkAliases(mssqlNetworkAlias) + ); + addEnv("MSSQL_SA_PASSWORD", mssqlServerContainer.getPassword()); + addEnv("SQL_SERVER", mssqlNetworkAlias); + acceptLicense(); + } + + /** + * @param configFile config.json + * @return this + */ + public AzureServicebusEmulatorContainer withConfigFile(MountableFile configFile) { + return withCopyFileToContainer( + configFile, + "/ServiceBus_Emulator/ConfigFiles/Config.json" + ); + } + + /** + * Accepts the license for the Azure Service Bus Emulator container by setting the ACCEPT_EULA=Y + * variable as described at https://github.com/Azure/azure-service-bus-emulator-installer/blob/main/README.md#license + */ + public AzureServicebusEmulatorContainer acceptLicense() { + addEnv("ACCEPT_EULA", "Y"); + return self(); + } + + /** + * @return connection string for connecting to the service bus. + */ + public String getConnectionString() { + Integer mappedPort = getMappedPort(5672); + return "Endpoint=sb://localhost:" + mappedPort + ";SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;"; + } +} diff --git a/modules/azure/src/test/java/org/testcontainers/azure/AzureServicebusEmulatorContainerTest.java b/modules/azure/src/test/java/org/testcontainers/azure/AzureServicebusEmulatorContainerTest.java new file mode 100644 index 00000000000..a51fd7331a1 --- /dev/null +++ b/modules/azure/src/test/java/org/testcontainers/azure/AzureServicebusEmulatorContainerTest.java @@ -0,0 +1,62 @@ +package org.testcontainers.azure; + +import com.azure.core.util.IterableStream; +import com.azure.messaging.servicebus.ServiceBusClientBuilder; +import com.azure.messaging.servicebus.ServiceBusMessage; +import com.azure.messaging.servicebus.ServiceBusReceivedMessage; +import com.azure.messaging.servicebus.ServiceBusReceiverClient; +import com.azure.messaging.servicebus.ServiceBusSenderClient; +import com.azure.messaging.servicebus.models.ServiceBusReceiveMode; +import org.junit.Test; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.MountableFile; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AzureServicebusEmulatorContainerTest { + + @Test + public void testWithCustomConfig() { + try( + // emulatorContainerCustomConfig { + AzureServicebusEmulatorContainer azureServicebusEmulatorContainer = new AzureServicebusEmulatorContainer( + DockerImageName.parse("mcr.microsoft.com/azure-messaging/servicebus-emulator") + ).withConfigFile(MountableFile.forClasspathResource("/servicebus-config.json")) + // } + ) { + azureServicebusEmulatorContainer.start(); + sendAndReceive(azureServicebusEmulatorContainer.getConnectionString(), "our.queue"); + } + } + + private static void sendAndReceive(String connectionString, String queueName) { + List sentMessages = Arrays.asList("Hello World"); + try ( + // buildClient { + ServiceBusSenderClient sender = new ServiceBusClientBuilder() + .connectionString(connectionString) + .sender() + .queueName(queueName) + .buildClient() + // } + ) { + for (String m : sentMessages) { + sender.sendMessage(new ServiceBusMessage(m)); + } + } + try (ServiceBusReceiverClient reciever = new ServiceBusClientBuilder() + .connectionString(connectionString) + .receiver() + .queueName(queueName) + .receiveMode(ServiceBusReceiveMode.RECEIVE_AND_DELETE) + .buildClient()) { + IterableStream messagesStream = reciever.receiveMessages(sentMessages.size()); + List recievedMessages = messagesStream.stream().map(m -> m.getBody().toString()).collect(Collectors.toList()); + assertThat(recievedMessages).isEqualTo(sentMessages); + } + } +} diff --git a/modules/azure/src/test/resources/servicebus-config.json b/modules/azure/src/test/resources/servicebus-config.json new file mode 100644 index 00000000000..dbac910ad53 --- /dev/null +++ b/modules/azure/src/test/resources/servicebus-config.json @@ -0,0 +1,108 @@ +{ + "UserConfig": { + "Namespaces": [ + { + "Name": "sbemulatorns", + "Queues": [ + { + "Name": "our.queue", + "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" + } + } +}