diff --git a/docs/modules/azure.md b/docs/modules/azure.md index 5d3da034f94..4ff726b620b 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 Eventhubs` 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) +AzureEventhubsEmulatorContainer | [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 @@ -49,6 +50,24 @@ Build Azure Table client: [Build Azure Table Service client](../../modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java) inside_block:createTableClient +### Azure Eventhubs Emulator + + +[Configuring the Azure Eventhubs Emulator container](../../modules/azure/src/test/resources/eventhubs_config.json) + + +Start Azure Eventhubs Emulator during a test: + + +[Starting a Azure Eventhubs Emulator container](../../modules/azure/src/test/java/org/testcontainers/azure/AzureEventhubsEmulatorContainerTest.java) inside_block:emulatorContainer + + +Configure the consumer and the producer clients: + + +[Configuring the clients](../../modules/azure/src/test/java/org/testcontainers/azure/AzureEventhubsEmulatorContainerTest.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/AzureEventhubsEmulatorContainer.java b/modules/azure/src/main/java/org/testcontainers/azure/AzureEventhubsEmulatorContainer.java new file mode 100644 index 00000000000..335d96466e0 --- /dev/null +++ b/modules/azure/src/main/java/org/testcontainers/azure/AzureEventhubsEmulatorContainer.java @@ -0,0 +1,120 @@ +package org.testcontainers.azure; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.MountableFile; + +/** + * Testcontainers implementation for Azure Eventhubs Emulator. + *

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

+ * Exposed ports: + *

+ */ +public class AzureEventhubsEmulatorContainer extends GenericContainer { + + private static final String DEFAULT_HOST = "127.0.0.1"; + + private static final int DEFAULT_AMQP_PORT = 5672; + + private static final int DEFAULT_KAFKA_PORT = 9092; + + 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 String host = DEFAULT_HOST; + + private AzuriteContainer azuriteContainer; + + private MountableFile config; + + /** + * @param dockerImageName specified docker image name to run + */ + public AzureEventhubsEmulatorContainer(final DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + + waitingFor(Wait.forLogMessage(".*Emulator Service is Successfully Up!.*", 1)); + withExposedPorts(DEFAULT_AMQP_PORT, DEFAULT_KAFKA_PORT); + } + + @Override + public void start() { + if (azuriteContainer == null) { + azuriteContainer = new AzuriteContainer(AzuriteContainer.DEFAULT_IMAGE_NAME).withNetwork(getNetwork()); + } + azuriteContainer.start(); + + super.start(); + } + + @Override + public void stop() { + super.stop(); + if (azuriteContainer != null) { + azuriteContainer.stop(); + } + } + + /** + * Provide the broker configuration to the container. + * + * @param config The file containing the broker configuration + * @return this + */ + public AzureEventhubsEmulatorContainer withConfig(final MountableFile config) { + this.config = config; + return this; + } + + /** + * Sets the hostname we want to use to connect to our emulator. (default: {@link #DEFAULT_HOST}) + * + * @param host The host name + * @return this + */ + public AzureEventhubsEmulatorContainer withHost(final String host) { + this.host = host; + return this; + } + + /** + * Accepts the EULA of the container. + * + * @return this + */ + public AzureEventhubsEmulatorContainer acceptEula() { + return withEnv("ACCEPT_EULA", "Y"); + } + + @Override + protected void configure() { + dependsOn(azuriteContainer); + final String azuriteHost = azuriteContainer.getNetworkAliases().get(0); + withEnv("BLOB_SERVER", azuriteHost); + withEnv("METADATA_SERVER", azuriteHost); + if (config != null) { + logger().info("Using path for configuration file: '{}'", config); + withCopyFileToContainer(config, "/Eventhubs_Emulator/ConfigFiles/Config.json"); + } + } + + /** + * Returns the connection string. + * + * @return connection string + */ + public String getConnectionString() { + return String.format(CONNECTION_STRING_FORMAT, host, getMappedPort(DEFAULT_AMQP_PORT)); + } +} diff --git a/modules/azure/src/main/java/org/testcontainers/azure/AzuriteContainer.java b/modules/azure/src/main/java/org/testcontainers/azure/AzuriteContainer.java index b2338a29608..aa8f327f5cd 100644 --- a/modules/azure/src/main/java/org/testcontainers/azure/AzuriteContainer.java +++ b/modules/azure/src/main/java/org/testcontainers/azure/AzuriteContainer.java @@ -42,9 +42,7 @@ public class AzuriteContainer extends GenericContainer { private static final String WELL_KNOWN_ACCOUNT_KEY = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="; - private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse( - "mcr.microsoft.com/azure-storage/azurite" - ); + static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("mcr.microsoft.com/azure-storage/azurite"); private String host = DEFAULT_HOST; diff --git a/modules/azure/src/test/java/org/testcontainers/azure/AzureEventhubsEmulatorContainerTest.java b/modules/azure/src/test/java/org/testcontainers/azure/AzureEventhubsEmulatorContainerTest.java new file mode 100644 index 00000000000..964969b8faf --- /dev/null +++ b/modules/azure/src/test/java/org/testcontainers/azure/AzureEventhubsEmulatorContainerTest.java @@ -0,0 +1,86 @@ +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.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.testcontainers.containers.Network; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.MountableFile; + +import java.time.Duration; +import java.util.Collections; +import java.util.Optional; +import java.util.Properties; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.waitAtMost; + +public class AzureEventhubsEmulatorContainerTest { + + private static Properties originalSystemProperties; + + @BeforeClass + public static void captureOriginalSystemProperties() { + originalSystemProperties = (Properties) System.getProperties().clone(); + } + + @AfterClass + public static void restoreOriginalSystemProperties() { + System.setProperties(originalSystemProperties); + } + + @Rule + // emulatorContainer { + public AzureEventhubsEmulatorContainer emulator = new AzureEventhubsEmulatorContainer( + DockerImageName.parse("mcr.microsoft.com/azure-messaging/eventhubs-emulator:2.0.1") + ) + .acceptEula() + .withNetwork(Network.newNetwork()) + .withConfig(MountableFile.forClasspathResource("/eventhubs_config.json")) + .withHost("127.0.0.1"); + + // } + + @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" + } + } +}