diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml
index 496cd9f21..e1f122658 100644
--- a/.github/workflows/cicd.yml
+++ b/.github/workflows/cicd.yml
@@ -52,6 +52,7 @@ jobs:
{ name: "Testcontainers.DynamoDb", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.Elasticsearch", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.EventStoreDb", runs-on: "ubuntu-22.04" },
+ { name: "Testcontainers.EventHubs", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.FakeGcsServer", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.FirebirdSql", runs-on: "ubuntu-22.04" },
{ name: "Testcontainers.Firestore", runs-on: "ubuntu-22.04" },
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 82dda0fd4..0a4db1cff 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -32,6 +32,7 @@
+
diff --git a/Testcontainers.sln b/Testcontainers.sln
index 06e1381fc..96eb3545f 100644
--- a/Testcontainers.sln
+++ b/Testcontainers.sln
@@ -41,6 +41,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.DynamoDb", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Elasticsearch", "src\Testcontainers.Elasticsearch\Testcontainers.Elasticsearch.csproj", "{641DDEA5-B6E0-41E6-BA11-7A28C0913127}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.EventHubs", "src\Testcontainers.EventHubs\Testcontainers.EventHubs.csproj", "{0EF885E9-E973-47DC-AA9C-3A5E9175B0F3}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.EventStoreDb", "src\Testcontainers.EventStoreDb\Testcontainers.EventStoreDb.csproj", "{84D707E0-C9FA-4327-85DC-0AFEBEA73572}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.FakeGcsServer", "src\Testcontainers.FakeGcsServer\Testcontainers.FakeGcsServer.csproj", "{FF86B509-2F9E-4269-ABC2-912B3339DE29}"
@@ -143,6 +145,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.DynamoDb.Tes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Elasticsearch.Tests", "tests\Testcontainers.Elasticsearch.Tests\Testcontainers.Elasticsearch.Tests.csproj", "{DD5B3678-468F-4D73-AECE-705E3D66CD43}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.EventHubs.Tests", "tests\Testcontainers.EventHubs.Tests\Testcontainers.EventHubs.Tests.csproj", "{4A0C5523-CEB2-49C9-AE62-9187A01B016B}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.EventStoreDb.Tests", "tests\Testcontainers.EventStoreDb.Tests\Testcontainers.EventStoreDb.Tests.csproj", "{64F8E9B9-78FD-4E13-BDDF-0340E2D4E1D0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.FakeGcsServer.Tests", "tests\Testcontainers.FakeGcsServer.Tests\Testcontainers.FakeGcsServer.Tests.csproj", "{9F27AA1B-C25D-400C-BCB0-6B0BF1A1DCEA}"
@@ -282,6 +286,10 @@ Global
{641DDEA5-B6E0-41E6-BA11-7A28C0913127}.Debug|Any CPU.Build.0 = Debug|Any CPU
{641DDEA5-B6E0-41E6-BA11-7A28C0913127}.Release|Any CPU.ActiveCfg = Release|Any CPU
{641DDEA5-B6E0-41E6-BA11-7A28C0913127}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0EF885E9-E973-47DC-AA9C-3A5E9175B0F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0EF885E9-E973-47DC-AA9C-3A5E9175B0F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0EF885E9-E973-47DC-AA9C-3A5E9175B0F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0EF885E9-E973-47DC-AA9C-3A5E9175B0F3}.Release|Any CPU.Build.0 = Release|Any CPU
{84D707E0-C9FA-4327-85DC-0AFEBEA73572}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{84D707E0-C9FA-4327-85DC-0AFEBEA73572}.Debug|Any CPU.Build.0 = Debug|Any CPU
{84D707E0-C9FA-4327-85DC-0AFEBEA73572}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -486,6 +494,10 @@ Global
{DD5B3678-468F-4D73-AECE-705E3D66CD43}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD5B3678-468F-4D73-AECE-705E3D66CD43}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD5B3678-468F-4D73-AECE-705E3D66CD43}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4A0C5523-CEB2-49C9-AE62-9187A01B016B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4A0C5523-CEB2-49C9-AE62-9187A01B016B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4A0C5523-CEB2-49C9-AE62-9187A01B016B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4A0C5523-CEB2-49C9-AE62-9187A01B016B}.Release|Any CPU.Build.0 = Release|Any CPU
{64F8E9B9-78FD-4E13-BDDF-0340E2D4E1D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{64F8E9B9-78FD-4E13-BDDF-0340E2D4E1D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{64F8E9B9-78FD-4E13-BDDF-0340E2D4E1D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -650,6 +662,7 @@ Global
{ED3C611F-DFE2-4AB7-A323-B500E95B4FF9} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{2EAFA567-9F68-4C52-9DBC-8F3EC11BB2CE} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{641DDEA5-B6E0-41E6-BA11-7A28C0913127} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
+ {0EF885E9-E973-47DC-AA9C-3A5E9175B0F3} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{84D707E0-C9FA-4327-85DC-0AFEBEA73572} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{FF86B509-2F9E-4269-ABC2-912B3339DE29} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{31BAF2C4-0608-4C0F-845A-14FE7C0A1670} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
@@ -701,6 +714,7 @@ Global
{AF9853AB-86E7-49DE-8DF8-454838E90D6F} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{101515E6-74C1-40F9-85C8-871F742A378D} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{DD5B3678-468F-4D73-AECE-705E3D66CD43} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
+ {4A0C5523-CEB2-49C9-AE62-9187A01B016B} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{64F8E9B9-78FD-4E13-BDDF-0340E2D4E1D0} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{9F27AA1B-C25D-400C-BCB0-6B0BF1A1DCEA} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{E39095AC-9B34-4178-A486-04C902B6FD33} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
diff --git a/docs/modules/index.md b/docs/modules/index.md
index 48a75587b..6e6681edb 100644
--- a/docs/modules/index.md
+++ b/docs/modules/index.md
@@ -24,6 +24,7 @@ await moduleNameContainer.StartAsync();
| ActiveMQ Artemis | `apache/activemq-artemis:2.31.2` | [NuGet](https://www.nuget.org/packages/Testcontainers.ActiveMq) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.ActiveMq) |
| ArangoDB | `arangodb:3.11.5` | [NuGet](https://www.nuget.org/packages/Testcontainers.ArangoDb) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.ArangoDb) |
| Azure Cosmos DB | `mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:latest` | [NuGet](https://www.nuget.org/packages/Testcontainers.CosmosDb) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.CosmosDb) |
+| Azure Event Hubs | `mcr.microsoft.com/azure-messaging/eventhubs-emulator:latest` | [NuGet](https://www.nuget.org/packages/Testcontainers.EventHubs) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.EventHubs) |
| Azure Service Bus | `mcr.microsoft.com/azure-messaging/servicebus-emulator:latest` | [NuGet](https://www.nuget.org/packages/Testcontainers.ServiceBus) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.ServiceBus) |
| Azurite | `mcr.microsoft.com/azure-storage/azurite:3.24.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.Azurite) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Azurite) |
| BigQuery | `ghcr.io/goccy/bigquery-emulator:0.4` | [NuGet](https://www.nuget.org/packages/Testcontainers.BigQuery) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.BigQuery) |
diff --git a/src/Testcontainers.Azurite/AzuriteContainer.cs b/src/Testcontainers.Azurite/AzuriteContainer.cs
index e0cbd0d1b..b7b1023b7 100644
--- a/src/Testcontainers.Azurite/AzuriteContainer.cs
+++ b/src/Testcontainers.Azurite/AzuriteContainer.cs
@@ -32,30 +32,27 @@ public string GetConnectionString()
///
/// Gets the blob endpoint
///
- /// The azurite blob endpoint
+ /// The Azurite blob endpoint
public string GetBlobEndpoint()
{
- return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(AzuriteBuilder.BlobPort),
- AzuriteBuilder.AccountName).ToString();
+ return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(AzuriteBuilder.BlobPort), AzuriteBuilder.AccountName).ToString();
}
///
/// Gets the queue endpoint
///
- /// The azurite queue endpoint
+ /// The Azurite queue endpoint
public string GetQueueEndpoint()
{
- return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(AzuriteBuilder.QueuePort),
- AzuriteBuilder.AccountName).ToString();
+ return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(AzuriteBuilder.QueuePort), AzuriteBuilder.AccountName).ToString();
}
///
/// Gets the table endpoint
///
- /// The azurite table endpoint
+ /// The Azurite table endpoint
public string GetTableEndpoint()
{
- return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(AzuriteBuilder.TablePort),
- AzuriteBuilder.AccountName).ToString();
+ return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(AzuriteBuilder.TablePort), AzuriteBuilder.AccountName).ToString();
}
}
\ No newline at end of file
diff --git a/src/Testcontainers.EventHubs/.editorconfig b/src/Testcontainers.EventHubs/.editorconfig
new file mode 100644
index 000000000..6f066619d
--- /dev/null
+++ b/src/Testcontainers.EventHubs/.editorconfig
@@ -0,0 +1 @@
+root = true
\ No newline at end of file
diff --git a/src/Testcontainers.EventHubs/EventHubsBuilder.cs b/src/Testcontainers.EventHubs/EventHubsBuilder.cs
new file mode 100644
index 000000000..a24bd217a
--- /dev/null
+++ b/src/Testcontainers.EventHubs/EventHubsBuilder.cs
@@ -0,0 +1,178 @@
+namespace Testcontainers.EventHubs;
+
+///
+[PublicAPI]
+public sealed class EventHubsBuilder : ContainerBuilder
+{
+ public const string EventHubsNetworkAlias = "eventhubs-container";
+
+ public const string AzuriteNetworkAlias = "azurite-container";
+
+ public const string EventHubsImage = "mcr.microsoft.com/azure-messaging/eventhubs-emulator:latest";
+
+ public const ushort EventHubsPort = 5672;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public EventHubsBuilder()
+ : this(new EventHubsConfiguration())
+ {
+ DockerResourceConfiguration = Init().DockerResourceConfiguration;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ private EventHubsBuilder(EventHubsConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ DockerResourceConfiguration = resourceConfiguration;
+ }
+
+ ///
+ protected override EventHubsConfiguration DockerResourceConfiguration { get; }
+
+ ///
+ protected override string AcceptLicenseAgreementEnvVar { get; } = "ACCEPT_EULA";
+
+ ///
+ protected override string AcceptLicenseAgreement { get; } = "Y";
+
+ ///
+ protected override string DeclineLicenseAgreement { get; } = "N";
+
+ ///
+ /// Accepts the license agreement.
+ ///
+ ///
+ /// When is set to true, the Azure Event Hubs Emulator license is accepted.
+ ///
+ /// A boolean value indicating whether the Azure Event Hubs Emulator license agreement is accepted.
+ /// A configured instance of .
+ public override EventHubsBuilder WithAcceptLicenseAgreement(bool acceptLicenseAgreement)
+ {
+ var licenseAgreement = acceptLicenseAgreement ? AcceptLicenseAgreement : DeclineLicenseAgreement;
+ return WithEnvironment(AcceptLicenseAgreementEnvVar, licenseAgreement);
+ }
+
+ ///
+ /// Sets the dependent Azurite container for the Azure Event Hubs Emulator.
+ ///
+ ///
+ /// This method allows an existing Azurite container to be attached to the Azure Event
+ /// Hubs Emulator. The containers must be on the same network to enable communication
+ /// between them.
+ ///
+ /// The network to connect the container to.
+ /// The Azurite container.
+ /// The Azurite container network alias.
+ /// A configured instance of .
+ public EventHubsBuilder WithAzuriteContainer(
+ INetwork network,
+ AzuriteContainer container,
+ string networkAlias)
+ {
+ return Merge(DockerResourceConfiguration, new EventHubsConfiguration(azuriteContainer: container))
+ .DependsOn(container)
+ .WithNetwork(network)
+ .WithNetworkAliases(EventHubsNetworkAlias)
+ .WithEnvironment("BLOB_SERVER", networkAlias)
+ .WithEnvironment("METADATA_SERVER", networkAlias);
+ }
+
+ ///
+ /// Sets the Azure Event Hubs Emulator configuration.
+ ///
+ /// The service configuration.
+ /// A configured instance of .
+ public EventHubsBuilder WithConfigurationBuilder(EventHubsServiceConfiguration serviceConfiguration)
+ {
+ var resourceContent = Encoding.Default.GetBytes(serviceConfiguration.Build());
+ return Merge(DockerResourceConfiguration, new EventHubsConfiguration(serviceConfiguration: serviceConfiguration))
+ .WithResourceMapping(resourceContent, "Eventhubs_Emulator/ConfigFiles/Config.json");
+ }
+
+ ///
+ public override EventHubsContainer Build()
+ {
+ Validate();
+ ValidateLicenseAgreement();
+
+ if (DockerResourceConfiguration.AzuriteContainer != null)
+ {
+ return new EventHubsContainer(DockerResourceConfiguration);
+ }
+
+ // If the user has not provided an existing Azurite container instance,
+ // we configure one.
+ var network = new NetworkBuilder()
+ .Build();
+
+ var container = new AzuriteBuilder()
+ .WithNetwork(network)
+ .WithNetworkAliases(AzuriteNetworkAlias)
+ .Build();
+
+ var eventHubsContainer = WithAzuriteContainer(network, container, AzuriteNetworkAlias);
+ return new EventHubsContainer(eventHubsContainer.DockerResourceConfiguration);
+ }
+
+ ///
+ protected override void Validate()
+ {
+ base.Validate();
+
+ _ = Guard.Argument(DockerResourceConfiguration.ServiceConfiguration, nameof(DockerResourceConfiguration.ServiceConfiguration))
+ .NotNull()
+ .ThrowIf(argument => !argument.Value.Validate(), _ => throw new ArgumentException("The service configuration of the Azure Event Hubs Emulator is invalid."));
+ }
+
+ ///
+ protected override EventHubsBuilder Init()
+ {
+ return base.Init()
+ .WithImage(EventHubsImage)
+ .WithPortBinding(EventHubsPort, true)
+ .WithWaitStrategy(Wait.ForUnixContainer()
+ .UntilMessageIsLogged("Emulator Service is Successfully Up!")
+ .AddCustomWaitStrategy(new WaitTwoSeconds()));
+ }
+
+ ///
+ protected override EventHubsBuilder Clone(IResourceConfiguration resourceConfiguration)
+ {
+ return Merge(DockerResourceConfiguration, new EventHubsConfiguration(resourceConfiguration));
+ }
+
+ ///
+ protected override EventHubsBuilder Clone(IContainerConfiguration resourceConfiguration)
+ {
+ return Merge(DockerResourceConfiguration, new EventHubsConfiguration(resourceConfiguration));
+ }
+
+ ///
+ protected override EventHubsBuilder Merge(EventHubsConfiguration oldValue, EventHubsConfiguration newValue)
+ {
+ return new EventHubsBuilder(new EventHubsConfiguration(oldValue, newValue));
+ }
+
+ ///
+ ///
+ /// This is a workaround to ensure that the wait strategy does not indicate
+ /// readiness too early:
+ /// https://github.com/Azure/azure-service-bus-emulator-installer/issues/35#issuecomment-2497164533.
+ ///
+ private sealed class WaitTwoSeconds : IWaitUntil
+ {
+ ///
+ public async Task UntilAsync(IContainer container)
+ {
+ await Task.Delay(TimeSpan.FromSeconds(2))
+ .ConfigureAwait(false);
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Testcontainers.EventHubs/EventHubsConfiguration.cs b/src/Testcontainers.EventHubs/EventHubsConfiguration.cs
new file mode 100644
index 000000000..76c57ecbb
--- /dev/null
+++ b/src/Testcontainers.EventHubs/EventHubsConfiguration.cs
@@ -0,0 +1,70 @@
+namespace Testcontainers.EventHubs;
+
+///
+[PublicAPI]
+public sealed class EventHubsConfiguration : ContainerConfiguration
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Azurite container.
+ /// The service configuration.
+ public EventHubsConfiguration(AzuriteContainer azuriteContainer = null,
+ EventHubsServiceConfiguration serviceConfiguration = null)
+ {
+ AzuriteContainer = azuriteContainer;
+ ServiceConfiguration = serviceConfiguration;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public EventHubsConfiguration(IResourceConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ // Passes the configuration upwards to the base implementations to create an updated immutable copy.
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public EventHubsConfiguration(IContainerConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ // Passes the configuration upwards to the base implementations to create an updated immutable copy.
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public EventHubsConfiguration(EventHubsConfiguration resourceConfiguration)
+ : this(new EventHubsConfiguration(), resourceConfiguration)
+ {
+ // Passes the configuration upwards to the base implementations to create an updated immutable copy.
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The old Docker resource configuration.
+ /// The new Docker resource configuration.
+ public EventHubsConfiguration(EventHubsConfiguration oldValue, EventHubsConfiguration newValue)
+ : base(oldValue, newValue)
+ {
+ AzuriteContainer = BuildConfiguration.Combine(oldValue.AzuriteContainer, newValue.AzuriteContainer);
+ ServiceConfiguration = BuildConfiguration.Combine(oldValue.ServiceConfiguration, newValue.ServiceConfiguration);
+ }
+
+ ///
+ /// Gets the Azurite container.
+ ///
+ public AzuriteContainer AzuriteContainer { get; }
+
+ ///
+ /// Gets the service configuration.
+ ///
+ public EventHubsServiceConfiguration ServiceConfiguration { get; }
+}
\ No newline at end of file
diff --git a/src/Testcontainers.EventHubs/EventHubsContainer.cs b/src/Testcontainers.EventHubs/EventHubsContainer.cs
new file mode 100644
index 000000000..c575aec21
--- /dev/null
+++ b/src/Testcontainers.EventHubs/EventHubsContainer.cs
@@ -0,0 +1,33 @@
+namespace Testcontainers.EventHubs;
+
+///
+[PublicAPI]
+public sealed class EventHubsContainer : DockerContainer
+{
+ private readonly EventHubsConfiguration _configuration;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The container configuration.
+ public EventHubsContainer(EventHubsConfiguration configuration)
+ : base(configuration)
+ {
+ _configuration = configuration;
+ }
+
+ ///
+ /// Gets the Event Hubs connection string.
+ ///
+ /// The Event Hubs connection string.
+ public string GetConnectionString()
+ {
+ var properties = new Dictionary();
+ properties.Add("Endpoint", new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(EventHubsBuilder.EventHubsPort)).ToString());
+ properties.Add("DefaultEndpointsProtocol", Uri.UriSchemeHttp);
+ properties.Add("SharedAccessKeyName", "RootManageSharedAccessKey");
+ properties.Add("SharedAccessKey", "SAS_KEY_VALUE");
+ properties.Add("UseDevelopmentEmulator", "true");
+ return string.Join(";", properties.Select(property => string.Join("=", property.Key, property.Value)));
+ }
+}
\ No newline at end of file
diff --git a/src/Testcontainers.EventHubs/EventHubsServiceConfiguration.cs b/src/Testcontainers.EventHubs/EventHubsServiceConfiguration.cs
new file mode 100644
index 000000000..120ff65d6
--- /dev/null
+++ b/src/Testcontainers.EventHubs/EventHubsServiceConfiguration.cs
@@ -0,0 +1,89 @@
+namespace Testcontainers.EventHubs;
+
+[PublicAPI]
+public record RootConfiguration(UserConfig UserConfig)
+{
+ public UserConfig UserConfig { get; } = UserConfig;
+}
+
+[PublicAPI]
+public record UserConfig(IReadOnlyCollection NamespaceConfig, LoggingConfig LoggingConfig)
+{
+ public IReadOnlyCollection NamespaceConfig { get; } = NamespaceConfig;
+
+ public LoggingConfig LoggingConfig { get; } = LoggingConfig;
+}
+
+[PublicAPI]
+public record NamespaceConfig(string Type, string Name, IReadOnlyCollection Entities)
+{
+ public string Type { get; } = Type;
+
+ public string Name { get; } = Name;
+
+ public IReadOnlyCollection Entities { get; } = Entities;
+}
+
+[PublicAPI]
+public record Entity(string Name, int PartitionCount, IReadOnlyCollection ConsumerGroups)
+{
+ public string Name { get; } = Name;
+
+ public int PartitionCount { get; } = PartitionCount;
+
+ public IReadOnlyCollection ConsumerGroups { get; } = ConsumerGroups;
+}
+
+[PublicAPI]
+public record ConsumerGroup(string Name)
+{
+ public string Name { get; } = Name;
+}
+
+[PublicAPI]
+public record LoggingConfig(string Type)
+{
+ public string Type { get; } = Type;
+}
+
+[PublicAPI]
+public sealed class EventHubsServiceConfiguration
+{
+ private readonly NamespaceConfig _namespaceConfig;
+
+ private EventHubsServiceConfiguration(NamespaceConfig namespaceConfig)
+ {
+ _namespaceConfig = namespaceConfig;
+ }
+
+ public static EventHubsServiceConfiguration Create()
+ {
+ var namespaceConfig = new NamespaceConfig("EventHub", "ns-1", Array.Empty());
+ return new EventHubsServiceConfiguration(namespaceConfig);
+ }
+
+ public EventHubsServiceConfiguration WithEntity(string name, int partitionCount, params string[] consumerGroupNames)
+ {
+ return WithEntity(name, partitionCount, new ReadOnlyCollection(consumerGroupNames));
+ }
+
+ public EventHubsServiceConfiguration WithEntity(string name, int partitionCount, IEnumerable consumerGroupNames)
+ {
+ var consumerGroups = new ReadOnlyCollection(consumerGroupNames.Select(consumerGroupName => new ConsumerGroup(consumerGroupName)).ToList());
+ var entity = new Entity(name, partitionCount, consumerGroups);
+ var entities = new ReadOnlyCollection(_namespaceConfig.Entities.Append(entity).ToList());
+ return new EventHubsServiceConfiguration(new NamespaceConfig(_namespaceConfig.Type, _namespaceConfig.Name, entities));
+ }
+
+ public bool Validate()
+ {
+ Predicate isValidEntity = entity => entity.PartitionCount > 0 && entity.PartitionCount <= 32 && entity.ConsumerGroups.Count > 0 && entity.ConsumerGroups.Count <= 20;
+ return _namespaceConfig.Entities.All(entity => isValidEntity(entity));
+ }
+
+ public string Build()
+ {
+ var rootConfiguration = new RootConfiguration(new UserConfig([_namespaceConfig], new LoggingConfig("file")));
+ return JsonSerializer.Serialize(rootConfiguration);
+ }
+}
\ No newline at end of file
diff --git a/src/Testcontainers.EventHubs/Testcontainers.EventHubs.csproj b/src/Testcontainers.EventHubs/Testcontainers.EventHubs.csproj
new file mode 100644
index 000000000..643fdade3
--- /dev/null
+++ b/src/Testcontainers.EventHubs/Testcontainers.EventHubs.csproj
@@ -0,0 +1,13 @@
+
+
+ net8.0;net9.0;netstandard2.0;netstandard2.1
+ latest
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Testcontainers.EventHubs/Usings.cs b/src/Testcontainers.EventHubs/Usings.cs
new file mode 100644
index 000000000..b68f67969
--- /dev/null
+++ b/src/Testcontainers.EventHubs/Usings.cs
@@ -0,0 +1,15 @@
+global using System;
+global using System.Collections.Generic;
+global using System.Collections.ObjectModel;
+global using System.Linq;
+global using System.Text;
+global using System.Text.Json;
+global using System.Threading.Tasks;
+global using Docker.DotNet.Models;
+global using DotNet.Testcontainers;
+global using DotNet.Testcontainers.Builders;
+global using DotNet.Testcontainers.Configurations;
+global using DotNet.Testcontainers.Containers;
+global using DotNet.Testcontainers.Networks;
+global using JetBrains.Annotations;
+global using Testcontainers.Azurite;
\ No newline at end of file
diff --git a/tests/Testcontainers.EventHubs.Tests/.editorconfig b/tests/Testcontainers.EventHubs.Tests/.editorconfig
new file mode 100644
index 000000000..6f066619d
--- /dev/null
+++ b/tests/Testcontainers.EventHubs.Tests/.editorconfig
@@ -0,0 +1 @@
+root = true
\ No newline at end of file
diff --git a/tests/Testcontainers.EventHubs.Tests/EventHubsContainerTest.cs b/tests/Testcontainers.EventHubs.Tests/EventHubsContainerTest.cs
new file mode 100644
index 000000000..619c5f642
--- /dev/null
+++ b/tests/Testcontainers.EventHubs.Tests/EventHubsContainerTest.cs
@@ -0,0 +1,101 @@
+namespace Testcontainers.EventHubs;
+
+public abstract class EventHubsContainerTest : IAsyncLifetime
+{
+ private const string EventHubsName = "eh-1";
+
+ private const string EventHubsConsumerGroupName = "cg-1";
+
+ private readonly EventHubsContainer _eventHubsContainer;
+
+ private EventHubsContainerTest(EventHubsContainer eventHubsContainer)
+ {
+ _eventHubsContainer = eventHubsContainer;
+ }
+
+ public Task InitializeAsync()
+ {
+ return _eventHubsContainer.StartAsync();
+ }
+
+ public Task DisposeAsync()
+ {
+ return _eventHubsContainer.DisposeAsync().AsTask();
+ }
+
+ private static EventHubsServiceConfiguration GetServiceConfiguration()
+ {
+ return EventHubsServiceConfiguration.Create().WithEntity(EventHubsName, 2, EventHubsConsumerGroupName);
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task SendEventDataBatchShouldNotThrowException()
+ {
+ // Given
+ var message = Guid.NewGuid().ToString();
+
+ await using var client = new EventHubProducerClient(_eventHubsContainer.GetConnectionString(), EventHubsName);
+
+ // When
+ var properties = await client.GetEventHubPropertiesAsync()
+ .ConfigureAwait(true);
+
+ using var eventDataBatch = await client.CreateBatchAsync()
+ .ConfigureAwait(true);
+
+ eventDataBatch.TryAdd(new EventData(message));
+
+ await client.SendAsync(eventDataBatch)
+ .ConfigureAwait(true);
+
+ // Then
+ Assert.NotNull(properties);
+ }
+
+ [UsedImplicitly]
+ public sealed class EventHubsDefaultAzuriteConfiguration : EventHubsContainerTest
+ {
+ public EventHubsDefaultAzuriteConfiguration()
+ : base(new EventHubsBuilder()
+ .WithAcceptLicenseAgreement(true)
+ .WithConfigurationBuilder(GetServiceConfiguration())
+ .Build())
+ {
+ }
+ }
+
+ [UsedImplicitly]
+ public sealed class EventHubsCustomAzuriteConfiguration : EventHubsContainerTest, IClassFixture
+ {
+ public EventHubsCustomAzuriteConfiguration(DatabaseFixture fixture)
+ : base(new EventHubsBuilder()
+ .WithAcceptLicenseAgreement(true)
+ .WithConfigurationBuilder(GetServiceConfiguration())
+ .WithAzuriteContainer(fixture.Network, fixture.Container, DatabaseFixture.AzuriteNetworkAlias)
+ .Build())
+ {
+ }
+ }
+
+ [UsedImplicitly]
+ public sealed class DatabaseFixture
+ {
+ public DatabaseFixture()
+ {
+ Network = new NetworkBuilder()
+ .Build();
+
+ Container = new AzuriteBuilder()
+ .WithNetwork(Network)
+ .WithNetworkAliases(AzuriteNetworkAlias)
+ .Build();
+ }
+
+ public static string AzuriteNetworkAlias => EventHubsBuilder.AzuriteNetworkAlias;
+
+ public INetwork Network { get; }
+
+ public AzuriteContainer Container { get; }
+ }
+}
\ No newline at end of file
diff --git a/tests/Testcontainers.EventHubs.Tests/Testcontainers.EventHubs.Tests.csproj b/tests/Testcontainers.EventHubs.Tests/Testcontainers.EventHubs.Tests.csproj
new file mode 100644
index 000000000..f797bf3b5
--- /dev/null
+++ b/tests/Testcontainers.EventHubs.Tests/Testcontainers.EventHubs.Tests.csproj
@@ -0,0 +1,18 @@
+
+
+ net9.0
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Testcontainers.EventHubs.Tests/Usings.cs b/tests/Testcontainers.EventHubs.Tests/Usings.cs
new file mode 100644
index 000000000..371cf2494
--- /dev/null
+++ b/tests/Testcontainers.EventHubs.Tests/Usings.cs
@@ -0,0 +1,10 @@
+global using System;
+global using System.Threading.Tasks;
+global using Azure.Messaging.EventHubs;
+global using Azure.Messaging.EventHubs.Producer;
+global using DotNet.Testcontainers.Builders;
+global using DotNet.Testcontainers.Commons;
+global using DotNet.Testcontainers.Networks;
+global using JetBrains.Annotations;
+global using Testcontainers.Azurite;
+global using Xunit;
\ No newline at end of file
diff --git a/tests/Testcontainers.ServiceBus.Tests/ServiceBusContainerTest.cs b/tests/Testcontainers.ServiceBus.Tests/ServiceBusContainerTest.cs
index 685d8dd3e..5944fae3f 100644
--- a/tests/Testcontainers.ServiceBus.Tests/ServiceBusContainerTest.cs
+++ b/tests/Testcontainers.ServiceBus.Tests/ServiceBusContainerTest.cs
@@ -57,7 +57,9 @@ await sender.SendMessageAsync(message)
public sealed class ServiceBusDefaultMsSqlConfiguration : ServiceBusContainerTest
{
public ServiceBusDefaultMsSqlConfiguration()
- : base(new ServiceBusBuilder().WithAcceptLicenseAgreement(true).Build())
+ : base(new ServiceBusBuilder()
+ .WithAcceptLicenseAgreement(true)
+ .Build())
{
}
}
@@ -66,7 +68,10 @@ public ServiceBusDefaultMsSqlConfiguration()
public sealed class ServiceBusCustomMsSqlConfiguration : ServiceBusContainerTest, IClassFixture
{
public ServiceBusCustomMsSqlConfiguration(DatabaseFixture fixture)
- : base(new ServiceBusBuilder().WithAcceptLicenseAgreement(true).WithMsSqlContainer(fixture.Network, fixture.Container, DatabaseFixture.DatabaseNetworkAlias).Build())
+ : base(new ServiceBusBuilder()
+ .WithAcceptLicenseAgreement(true)
+ .WithMsSqlContainer(fixture.Network, fixture.Container, DatabaseFixture.DatabaseNetworkAlias)
+ .Build())
{
}
}