Skip to content

Commit

Permalink
Add Azure Service Bus Emulator container to Azure module
Browse files Browse the repository at this point in the history
- Implements new Service Bus Container
- Adds new test case for Service Bus
- Updates the Azure documentation

Signed-off-by: Esta Nagy <[email protected]>
  • Loading branch information
nagyesta committed Jan 27, 2025
1 parent e1dc19f commit f9dc2f7
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 1 deletion.
36 changes: 35 additions & 1 deletion docs/modules/azure.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ 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`, `Azure Event Hubs` and `CosmosDB` emulators. In order to use them, you should use the following classes:
Currently, the module supports `Azurite`, `Azure Event Hubs`, `Azure Service Bus` and `CosmosDB` 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)
AzureEventHubsContainer | [mcr.microsoft.com/azure-messaging/eventhubs-emulator](https://github.com/microsoft/containerregistry)
AzureServiceBusEmulatorContainer | [mcr.microsoft.com/azure-messaging/servicebus-emulator](https://github.com/microsoft/containerregistry)
AzureServiceBusContainer | [mcr.microsoft.com/azure-messaging/servicebus-emulator](https://github.com/microsoft/containerregistry)
CosmosDBEmulatorContainer | [mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator](https://github.com/microsoft/containerregistry)

## Usage example
Expand Down Expand Up @@ -101,6 +103,38 @@ Configure the consumer and the producer clients:
[Configuring the clients](../../modules/azure/src/test/java/org/testcontainers/azure/AzureEventHubsContainerTest.java) inside_block:createProducerAndConsumer
<!--/codeinclude-->

### Azure Service Bus Emulator

<!--codeinclude-->
[Configuring the Azure Service Bus Emulator container](../../modules/azure/src/test/resources/service-bus-config.json)
<!--/codeinclude-->

Start Azure Service Bus Emulator during a test:

<!--codeinclude-->
[Setting up a network](../../modules/azure/src/test/java/org/testcontainers/azure/AzureServiceBusContainerTest.java) inside_block:network
<!--/codeinclude-->

<!--codeinclude-->
[Starting a SQL Server container as dependency](../../modules/azure/src/test/java/org/testcontainers/azure/AzureServiceBusContainerTest.java) inside_block:sqlContainer
<!--/codeinclude-->

<!--codeinclude-->
[Starting a Service Bus Emulator container](../../modules/azure/src/test/java/org/testcontainers/azure/AzureServiceBusContainerTest.java) inside_block:emulatorContainer
<!--/codeinclude-->

#### Using Azure Service Bus clients

Configure the sender and the processor clients:

<!--codeinclude-->
[Configuring the sender client](../../modules/azure/src/test/java/org/testcontainers/azure/AzureServiceBusContainerTest.java) inside_block:senderClient
<!--/codeinclude-->

<!--codeinclude-->
[Configuring the processor client](../../modules/azure/src/test/java/org/testcontainers/azure/AzureServiceBusContainerTest.java) inside_block:processorClient
<!--/codeinclude-->

### CosmosDB

Start Azure CosmosDB Emulator during a test:
Expand Down
3 changes: 3 additions & 0 deletions modules/azure/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ description = "Testcontainers :: Azure"

dependencies {
api project(':testcontainers')
api project(':mssqlserver')
// TODO use JDK's HTTP client and/or Apache HttpClient5
shaded 'com.squareup.okhttp3:okhttp:4.12.0'

Expand All @@ -11,4 +12,6 @@ dependencies {
testImplementation 'com.azure:azure-storage-queue:12.24.0'
testImplementation 'com.azure:azure-data-tables:12.5.0'
testImplementation 'com.azure:azure-messaging-eventhubs:5.19.2'
testImplementation 'com.azure:azure-messaging-servicebus:7.17.8'
testImplementation 'com.microsoft.sqlserver:mssql-jdbc:12.8.1.jre8'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package org.testcontainers.azure;

import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.MSSQLServerContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.images.builder.Transferable;
import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.LicenseAcceptance;

/**
* Testcontainers implementation for Azure Service Bus Emulator.
* <p>
* Supported image: {@code mcr.microsoft.com/azure-messaging/servicebus-emulator}
* <p>
* Exposed port: 5672
*/
public class AzureServiceBusContainer extends GenericContainer<AzureServiceBusContainer> {

private static final String CONNECTION_STRING_FORMAT =
"Endpoint=sb://%s:%d;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;";

private static final int DEFAULT_PORT = 5672;

private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(
"mcr.microsoft.com/azure-messaging/servicebus-emulator"
);

private MSSQLServerContainer<?> msSqlServerContainer;

/**
* @param dockerImageName The specified docker image name to run
*/
public AzureServiceBusContainer(final String dockerImageName) {
this(DockerImageName.parse(dockerImageName));
}

/**
* @param dockerImageName The specified docker image name to run
*/
public AzureServiceBusContainer(final DockerImageName dockerImageName) {
super(dockerImageName);
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
withExposedPorts(DEFAULT_PORT);
waitingFor(Wait.forLogMessage(".*Emulator Service is Successfully Up!.*", 1));
}

/**
* Sets the MS SQL Server dependency needed by the Service Bus Container,
*
* @param msSqlServerContainer The MS SQL Server container used by Service Bus as a dependency
* @return this
*/
public AzureServiceBusContainer withMsSqlServerContainer(final MSSQLServerContainer<?> msSqlServerContainer) {
dependsOn(msSqlServerContainer);
this.msSqlServerContainer = msSqlServerContainer;
return this;
}

/**
* Provide the Service Bus configuration JSON.
*
* @param config The configuration
* @return this
*/
public AzureServiceBusContainer withConfig(final Transferable config) {
withCopyToContainer(config, "/ServiceBus_Emulator/ConfigFiles/Config.json");
return this;
}

/**
* Accepts the EULA of the container.
*
* @return this
*/
public AzureServiceBusContainer acceptLicense() {
return withEnv("ACCEPT_EULA", "Y");
}

@Override
protected void configure() {
if (msSqlServerContainer == null) {
throw new IllegalStateException(
"The image " +
getDockerImageName() +
" requires a Microsoft SQL Server container. Please provide one with the withMsSqlServerContainer method!"
);
}
withEnv("SQL_SERVER", msSqlServerContainer.getNetworkAliases().get(0));
withEnv("MSSQL_SA_PASSWORD", msSqlServerContainer.getPassword());
// If license was not accepted programmatically, check if it was accepted via resource file
if (!getEnvMap().containsKey("ACCEPT_EULA")) {
LicenseAcceptance.assertLicenseAccepted(this.getDockerImageName());
acceptLicense();
}
}

/**
* Returns the connection string.
*
* @return connection string
*/
public String getConnectionString() {
return String.format(CONNECTION_STRING_FORMAT, getHost(), getMappedPort(DEFAULT_PORT));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package org.testcontainers.azure;

import com.azure.messaging.servicebus.ServiceBusClientBuilder;
import com.azure.messaging.servicebus.ServiceBusErrorContext;
import com.azure.messaging.servicebus.ServiceBusException;
import com.azure.messaging.servicebus.ServiceBusMessage;
import com.azure.messaging.servicebus.ServiceBusProcessorClient;
import com.azure.messaging.servicebus.ServiceBusReceivedMessageContext;
import com.azure.messaging.servicebus.ServiceBusSenderClient;
import com.github.dockerjava.api.model.Capability;
import org.assertj.core.api.Assertions;
import org.junit.Rule;
import org.junit.Test;
import org.testcontainers.containers.MSSQLServerContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.utility.MountableFile;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;

public class AzureServiceBusContainerTest {

@Rule
// network {
public Network network = Network.newNetwork();
// }

@Rule
// sqlContainer {
public MSSQLServerContainer<?> mssqlServerContainer = new MSSQLServerContainer<>(
"mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04"
)
.acceptLicense()
.withPassword("yourStrong(!)Password")
.withCreateContainerCmdModifier(cmd -> {
cmd.getHostConfig().withCapAdd(Capability.SYS_PTRACE);
})
.withNetwork(network);
// }

@Rule
// emulatorContainer {
public AzureServiceBusContainer emulator = new AzureServiceBusContainer(
"mcr.microsoft.com/azure-messaging/servicebus-emulator:1.0.1"
)
.acceptLicense()
.withConfig(MountableFile.forClasspathResource("/service-bus-config.json"))
.withNetwork(network)
.withMsSqlServerContainer(mssqlServerContainer);
// }

@Test
public void testWithClient() {
assertThat(emulator.getConnectionString()).startsWith("Endpoint=sb://");

// senderClient {
ServiceBusSenderClient senderClient = new ServiceBusClientBuilder()
.connectionString(emulator.getConnectionString())
.sender()
.queueName("queue.1")
.buildClient();
// }

await()
.atMost(20, TimeUnit.SECONDS)
.ignoreException(ServiceBusException.class)
.until(() -> {
senderClient.sendMessage(new ServiceBusMessage("Hello, Testcontainers!"));
return true;
});
senderClient.close();

final List<String> received = new CopyOnWriteArrayList<>();
Consumer<ServiceBusReceivedMessageContext> messageConsumer = m -> {
received.add(m.getMessage().getBody().toString());
m.complete();
};
Consumer<ServiceBusErrorContext> errorConsumer = e -> Assertions.fail("Unexpected error: " + e);
// processorClient {
ServiceBusProcessorClient processorClient = new ServiceBusClientBuilder()
.connectionString(emulator.getConnectionString())
.processor()
.queueName("queue.1")
.processMessage(messageConsumer)
.processError(errorConsumer)
.buildProcessorClient();
// }
processorClient.start();

await()
.atMost(20, TimeUnit.SECONDS)
.untilAsserted(() -> {
assertThat(received).hasSize(1).containsExactlyInAnyOrder("Hello, Testcontainers!");
});
processorClient.close();
}
}
29 changes: 29 additions & 0 deletions modules/azure/src/test/resources/service-bus-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"UserConfig": {
"Namespaces": [
{
"Name": "sbemulatorns",
"Queues": [
{
"Name": "queue.1",
"Properties": {
"DeadLetteringOnMessageExpiration": false,
"DefaultMessageTimeToLive": "PT1H",
"DuplicateDetectionHistoryTimeWindow": "PT20S",
"ForwardDeadLetteredMessagesTo": "",
"ForwardTo": "",
"LockDuration": "PT1M",
"MaxDeliveryCount": 3,
"RequiresDuplicateDetection": false,
"RequiresSession": false
}
}
],
"Topics": []
}
],
"Logging": {
"Type": "File"
}
}
}

0 comments on commit f9dc2f7

Please sign in to comment.