diff --git a/docs/modules/azure.md b/docs/modules/azure.md index 19c141c7639..e09634b09c4 100644 --- a/docs/modules/azure.md +++ b/docs/modules/azure.md @@ -5,11 +5,12 @@ 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`, `Azure Event Hubs` 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) CosmosDBEmulatorContainer | [mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator](https://github.com/microsoft/containerregistry) ## Usage example @@ -72,6 +73,34 @@ Build Azure Table client: !!! note We can use custom credentials the same way as defined in the Blob section. +### Azure Event Hubs Emulator + + +[Configuring the Azure Event Hubs Emulator container](../../modules/azure/src/test/resources/eventhubs_config.json) + + +Start Azure Event Hubs Emulator during a test: + + +[Setting up a network](../../modules/azure/src/test/java/org/testcontainers/azure/AzureEventHubsContainerTest.java) inside_block:network + + + +[Starting an Azurite container as dependency](../../modules/azure/src/test/java/org/testcontainers/azure/AzureEventHubsContainerTest.java) inside_block:azuriteContainer + + + +[Starting an Azure Event Hubs Emulator container](../../modules/azure/src/test/java/org/testcontainers/azure/AzureEventHubsContainerTest.java) inside_block:emulatorContainer + + +#### Using Azure Event Hubs clients + +Configure the consumer and the producer clients: + + +[Configuring the clients](../../modules/azure/src/test/java/org/testcontainers/azure/AzureEventHubsContainerTest.java) inside_block:createProducerAndConsumer + + ### CosmosDB Start Azure CosmosDB Emulator during a test: diff --git a/modules/azure/build.gradle b/modules/azure/build.gradle index c6cfb6738d0..3dc97d03fce 100644 --- a/modules/azure/build.gradle +++ b/modules/azure/build.gradle @@ -10,4 +10,5 @@ dependencies { 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-eventhubs:5.19.2' } diff --git a/modules/azure/src/main/java/org/testcontainers/azure/AzureEventHubsContainer.java b/modules/azure/src/main/java/org/testcontainers/azure/AzureEventHubsContainer.java new file mode 100644 index 00000000000..d611a2442cc --- /dev/null +++ b/modules/azure/src/main/java/org/testcontainers/azure/AzureEventHubsContainer.java @@ -0,0 +1,109 @@ +package org.testcontainers.azure; + +import org.testcontainers.containers.GenericContainer; +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 Eventhubs Emulator. + *

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

+ * Exposed ports: + *

+ */ +public class AzureEventHubsContainer extends GenericContainer { + + private static final int DEFAULT_AMQP_PORT = 5672; + + private static final String CONNECTION_STRING_FORMAT = + "Endpoint=sb://%s:%d;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;"; + + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse( + "mcr.microsoft.com/azure-messaging/eventhubs-emulator" + ); + + private AzuriteContainer azuriteContainer; + + /** + * @param dockerImageName specified docker image name to run + */ + public AzureEventHubsContainer(final String dockerImageName) { + this(DockerImageName.parse(dockerImageName)); + } + + /** + * @param dockerImageName specified docker image name to run + */ + public AzureEventHubsContainer(final DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + waitingFor(Wait.forLogMessage(".*Emulator Service is Successfully Up!.*", 1)); + withExposedPorts(DEFAULT_AMQP_PORT); + } + + /** + * * Sets the Azurite dependency needed by the Event Hubs Container, + * + * @param azuriteContainer The Azurite container used by Event HUbs as a dependency + * @return this + */ + public AzureEventHubsContainer withAzuriteContainer(final AzuriteContainer azuriteContainer) { + this.azuriteContainer = azuriteContainer; + dependsOn(this.azuriteContainer); + return this; + } + + /** + * Provide the broker configuration to the container. + * + * @param config The file containing the broker configuration + * @return this + */ + public AzureEventHubsContainer withConfig(final Transferable config) { + withCopyToContainer(config, "/Eventhubs_Emulator/ConfigFiles/Config.json"); + return this; + } + + /** + * Accepts the EULA of the container. + * + * @return this + */ + public AzureEventHubsContainer acceptLicense() { + withEnv("ACCEPT_EULA", "Y"); + return this; + } + + @Override + protected void configure() { + if (azuriteContainer == null) { + throw new IllegalStateException( + "The image " + + getDockerImageName() + + " requires an Azurite container. Please provide one with the withAzuriteContainer method!" + ); + } + final String azuriteHost = azuriteContainer.getNetworkAliases().get(0); + withEnv("BLOB_SERVER", azuriteHost); + withEnv("METADATA_SERVER", azuriteHost); + // 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_AMQP_PORT)); + } +} diff --git a/modules/azure/src/test/java/org/testcontainers/azure/AzureEventHubsContainerTest.java b/modules/azure/src/test/java/org/testcontainers/azure/AzureEventHubsContainerTest.java new file mode 100644 index 00000000000..c0febc2ed86 --- /dev/null +++ b/modules/azure/src/test/java/org/testcontainers/azure/AzureEventHubsContainerTest.java @@ -0,0 +1,83 @@ +package org.testcontainers.azure; + +import com.azure.core.util.IterableStream; +import com.azure.messaging.eventhubs.EventData; +import com.azure.messaging.eventhubs.EventHubClientBuilder; +import com.azure.messaging.eventhubs.EventHubConsumerClient; +import com.azure.messaging.eventhubs.EventHubProducerClient; +import com.azure.messaging.eventhubs.models.EventPosition; +import com.azure.messaging.eventhubs.models.PartitionEvent; +import org.junit.Rule; +import org.junit.Test; +import org.testcontainers.containers.Network; +import org.testcontainers.utility.MountableFile; + +import java.time.Duration; +import java.util.Collections; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.waitAtMost; + +public class AzureEventHubsContainerTest { + + @Rule + // network { + public Network network = Network.newNetwork(); + + // } + + @Rule + // azuriteContainer { + public AzuriteContainer azuriteContainer = new AzuriteContainer("mcr.microsoft.com/azure-storage/azurite:3.33.0") + .withNetwork(network); + + // } + + @Rule + // emulatorContainer { + public AzureEventHubsContainer emulator = new AzureEventHubsContainer( + "mcr.microsoft.com/azure-messaging/eventhubs-emulator:2.0.1" + ) + .acceptLicense() + .withNetwork(network) + .withConfig(MountableFile.forClasspathResource("/eventhubs_config.json")) + .withAzuriteContainer(azuriteContainer); + + // } + + @Test + public void testWithEventHubsClient() { + try ( + // createProducerAndConsumer { + EventHubProducerClient producer = new EventHubClientBuilder() + .connectionString(emulator.getConnectionString()) + .fullyQualifiedNamespace("emulatorNs1") + .eventHubName("eh1") + .buildProducerClient(); + EventHubConsumerClient consumer = new EventHubClientBuilder() + .connectionString(emulator.getConnectionString()) + .fullyQualifiedNamespace("emulatorNs1") + .eventHubName("eh1") + .consumerGroup("cg1") + .buildConsumerClient() + // } + ) { + producer.send(Collections.singletonList(new EventData("test"))); + + waitAtMost(Duration.ofSeconds(30)) + .pollDelay(Duration.ofSeconds(5)) + .untilAsserted(() -> { + IterableStream events = consumer.receiveFromPartition( + "0", + 1, + EventPosition.earliest(), + Duration.ofSeconds(2) + ); + Optional event = events.stream().findFirst(); + assertThat(event).isPresent(); + assertThat(event.get().getData().getBodyAsString()).isEqualTo("test"); + }); + } + } +} diff --git a/modules/azure/src/test/resources/eventhubs_config.json b/modules/azure/src/test/resources/eventhubs_config.json new file mode 100644 index 00000000000..554be9d7cbf --- /dev/null +++ b/modules/azure/src/test/resources/eventhubs_config.json @@ -0,0 +1,24 @@ +{ + "UserConfig": { + "NamespaceConfig": [ + { + "Type": "EventHub", + "Name": "emulatorNs1", + "Entities": [ + { + "Name": "eh1", + "PartitionCount": "1", + "ConsumerGroups": [ + { + "Name": "cg1" + } + ] + } + ] + } + ], + "LoggingConfig": { + "Type": "File" + } + } +}