Skip to content

Commit

Permalink
feat: Add EventHubs module
Browse files Browse the repository at this point in the history
  • Loading branch information
HofmeisterAn committed Feb 16, 2025
1 parent fa256c6 commit cdd69fb
Show file tree
Hide file tree
Showing 17 changed files with 559 additions and 11 deletions.
1 change: 1 addition & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<PackageVersion Include="AWSSDK.SimpleNotificationService" Version="3.7.101.7"/>
<PackageVersion Include="AWSSDK.SQS" Version="3.7.100.71"/>
<PackageVersion Include="Azure.Data.Tables" Version="12.8.0"/>
<PackageVersion Include="Azure.Messaging.EventHubs" Version="5.11.3"/>
<PackageVersion Include="Azure.Messaging.ServiceBus" Version="7.18.2"/>
<PackageVersion Include="Azure.Storage.Blobs" Version="12.17.0"/>
<PackageVersion Include="Azure.Storage.Queues" Version="12.15.0"/>
Expand Down
14 changes: 14 additions & 0 deletions Testcontainers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down Expand Up @@ -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}"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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}
Expand Down
1 change: 1 addition & 0 deletions docs/modules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
Expand Down
15 changes: 6 additions & 9 deletions src/Testcontainers.Azurite/AzuriteContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,27 @@ public string GetConnectionString()
/// <summary>
/// Gets the blob endpoint
/// </summary>
/// <returns>The azurite blob endpoint</returns>
/// <returns>The Azurite blob endpoint</returns>
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();
}

/// <summary>
/// Gets the queue endpoint
/// </summary>
/// <returns>The azurite queue endpoint</returns>
/// <returns>The Azurite queue endpoint</returns>
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();
}

/// <summary>
/// Gets the table endpoint
/// </summary>
/// <returns>The azurite table endpoint</returns>
/// <returns>The Azurite table endpoint</returns>
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();
}
}
1 change: 1 addition & 0 deletions src/Testcontainers.EventHubs/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
178 changes: 178 additions & 0 deletions src/Testcontainers.EventHubs/EventHubsBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
namespace Testcontainers.EventHubs;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
[PublicAPI]
public sealed class EventHubsBuilder : ContainerBuilder<EventHubsBuilder, EventHubsContainer, EventHubsConfiguration>
{
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;

/// <summary>
/// Initializes a new instance of the <see cref="EventHubsBuilder" /> class.
/// </summary>
public EventHubsBuilder()
: this(new EventHubsConfiguration())
{
DockerResourceConfiguration = Init().DockerResourceConfiguration;
}

/// <summary>
/// Initializes a new instance of the <see cref="EventHubsBuilder" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
private EventHubsBuilder(EventHubsConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
DockerResourceConfiguration = resourceConfiguration;
}

/// <inheritdoc />
protected override EventHubsConfiguration DockerResourceConfiguration { get; }

/// <inheritdoc />
protected override string AcceptLicenseAgreementEnvVar { get; } = "ACCEPT_EULA";

/// <inheritdoc />
protected override string AcceptLicenseAgreement { get; } = "Y";

/// <inheritdoc />
protected override string DeclineLicenseAgreement { get; } = "N";

/// <summary>
/// Accepts the license agreement.
/// </summary>
/// <remarks>
/// When <paramref name="acceptLicenseAgreement" /> is set to <c>true</c>, the Azure Event Hubs Emulator <see href="https://github.com/Azure/azure-event-hubs-emulator-installer/blob/main/EMULATOR_EULA.md">license</see> is accepted.
/// </remarks>
/// <param name="acceptLicenseAgreement">A boolean value indicating whether the Azure Event Hubs Emulator license agreement is accepted.</param>
/// <returns>A configured instance of <see cref="EventHubsBuilder" />.</returns>
public override EventHubsBuilder WithAcceptLicenseAgreement(bool acceptLicenseAgreement)
{
var licenseAgreement = acceptLicenseAgreement ? AcceptLicenseAgreement : DeclineLicenseAgreement;
return WithEnvironment(AcceptLicenseAgreementEnvVar, licenseAgreement);
}

/// <summary>
/// Sets the dependent Azurite container for the Azure Event Hubs Emulator.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="network">The network to connect the container to.</param>
/// <param name="container">The Azurite container.</param>
/// <param name="networkAlias">The Azurite container network alias.</param>
/// <returns>A configured instance of <see cref="EventHubsBuilder" />.</returns>
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);
}

/// <summary>
/// Sets the Azure Event Hubs Emulator configuration.
/// </summary>
/// <param name="serviceConfiguration">The service configuration.</param>
/// <returns>A configured instance of <see cref="EventHubsBuilder" />.</returns>
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");
}

/// <inheritdoc />
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);
}

/// <inheritdoc />
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."));
}

/// <inheritdoc />
protected override EventHubsBuilder Init()
{
return base.Init()
.WithImage(EventHubsImage)
.WithPortBinding(EventHubsPort, true)
.WithWaitStrategy(Wait.ForUnixContainer()
.UntilMessageIsLogged("Emulator Service is Successfully Up!")
.AddCustomWaitStrategy(new WaitTwoSeconds()));
}

/// <inheritdoc />
protected override EventHubsBuilder Clone(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new EventHubsConfiguration(resourceConfiguration));
}

/// <inheritdoc />
protected override EventHubsBuilder Clone(IContainerConfiguration resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new EventHubsConfiguration(resourceConfiguration));
}

/// <inheritdoc />
protected override EventHubsBuilder Merge(EventHubsConfiguration oldValue, EventHubsConfiguration newValue)
{
return new EventHubsBuilder(new EventHubsConfiguration(oldValue, newValue));
}

/// <inheritdoc cref="IWaitUntil" />
/// <remarks>
/// 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.
/// </remarks>
private sealed class WaitTwoSeconds : IWaitUntil
{
/// <inheritdoc />
public async Task<bool> UntilAsync(IContainer container)
{
await Task.Delay(TimeSpan.FromSeconds(2))
.ConfigureAwait(false);

return true;
}
}
}
70 changes: 70 additions & 0 deletions src/Testcontainers.EventHubs/EventHubsConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
namespace Testcontainers.EventHubs;

/// <inheritdoc cref="ContainerConfiguration" />
[PublicAPI]
public sealed class EventHubsConfiguration : ContainerConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="EventHubsConfiguration" /> class.
/// </summary>
/// <param name="azuriteContainer">The Azurite container.</param>
/// <param name="serviceConfiguration">The service configuration.</param>
public EventHubsConfiguration(AzuriteContainer azuriteContainer = null,
EventHubsServiceConfiguration serviceConfiguration = null)
{
AzuriteContainer = azuriteContainer;
ServiceConfiguration = serviceConfiguration;
}

/// <summary>
/// Initializes a new instance of the <see cref="EventHubsConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public EventHubsConfiguration(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
: base(resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="EventHubsConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public EventHubsConfiguration(IContainerConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="EventHubsConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public EventHubsConfiguration(EventHubsConfiguration resourceConfiguration)
: this(new EventHubsConfiguration(), resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="EventHubsConfiguration" /> class.
/// </summary>
/// <param name="oldValue">The old Docker resource configuration.</param>
/// <param name="newValue">The new Docker resource configuration.</param>
public EventHubsConfiguration(EventHubsConfiguration oldValue, EventHubsConfiguration newValue)
: base(oldValue, newValue)
{
AzuriteContainer = BuildConfiguration.Combine(oldValue.AzuriteContainer, newValue.AzuriteContainer);
ServiceConfiguration = BuildConfiguration.Combine(oldValue.ServiceConfiguration, newValue.ServiceConfiguration);
}

/// <summary>
/// Gets the Azurite container.
/// </summary>
public AzuriteContainer AzuriteContainer { get; }

/// <summary>
/// Gets the service configuration.
/// </summary>
public EventHubsServiceConfiguration ServiceConfiguration { get; }
}
Loading

0 comments on commit cdd69fb

Please sign in to comment.