Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Testcontainers.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<Project Path="src/Testcontainers.FirebirdSql/Testcontainers.FirebirdSql.csproj"/>
<Project Path="src/Testcontainers.Firestore/Testcontainers.Firestore.csproj"/>
<Project Path="src/Testcontainers.Floci/Testcontainers.Floci.csproj"/>
<Project Path="src/Testcontainers.FlociAz/Testcontainers.FlociAz.csproj"/>
<Project Path="src/Testcontainers.GCloud/Testcontainers.GCloud.csproj"/>
<Project Path="src/Testcontainers.Grafana/Testcontainers.Grafana.csproj"/>
<Project Path="src/Testcontainers.InfluxDb/Testcontainers.InfluxDb.csproj"/>
Expand Down Expand Up @@ -106,6 +107,7 @@
<Project Path="tests/Testcontainers.FirebirdSql.Tests/Testcontainers.FirebirdSql.Tests.csproj"/>
<Project Path="tests/Testcontainers.Firestore.Tests/Testcontainers.Firestore.Tests.csproj"/>
<Project Path="tests/Testcontainers.Floci.Tests/Testcontainers.Floci.Tests.csproj"/>
<Project Path="tests/Testcontainers.FlociAz.Tests/Testcontainers.FlociAz.Tests.csproj"/>
<Project Path="tests/Testcontainers.Grafana.Tests/Testcontainers.Grafana.Tests.csproj"/>
<Project Path="tests/Testcontainers.InfluxDb.Tests/Testcontainers.InfluxDb.Tests.csproj"/>
<Project Path="tests/Testcontainers.JanusGraph.Tests/Testcontainers.JanusGraph.Tests.csproj"/>
Expand Down
1 change: 1 addition & 0 deletions src/Testcontainers.FlociAz/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
97 changes: 97 additions & 0 deletions src/Testcontainers.FlociAz/FlociAzBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
namespace Testcontainers.FlociAz;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
[PublicAPI]
public sealed class FlociAzBuilder : ContainerBuilder<FlociAzBuilder, FlociAzContainer, FlociAzConfiguration>
{
public const ushort FlociAzPort = 4577;

public const ushort EventHubsPort = 5672;

public const ushort ServiceBusPort = 5673;

public const string AccountName = "devstoreaccount1";

public const string AccountKey = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";

/// <summary>
/// Initializes a new instance of the <see cref="FlociAzBuilder" /> class.
/// </summary>
/// <param name="image">
/// The full Docker image name, including the image repository and tag
/// (e.g., <c>floci/floci-az:0.9.0</c>).
/// </param>
/// <remarks>
/// Docker image tags available at <see href="https://hub.docker.com/r/floci/floci-az/tags" />.
/// </remarks>
public FlociAzBuilder(string image)
: this(new DockerImage(image))
{
}

/// <summary>
/// Initializes a new instance of the <see cref="FlociAzBuilder" /> class.
/// </summary>
/// <param name="image">
/// An <see cref="IImage" /> instance that specifies the Docker image to be used
/// for the container builder configuration.
/// </param>
/// <remarks>
/// Docker image tags available at <see href="https://hub.docker.com/r/floci/floci-az/tags" />.
/// </remarks>
public FlociAzBuilder(IImage image)
: this(new FlociAzConfiguration())
{
DockerResourceConfiguration = Init().WithImage(image).DockerResourceConfiguration;
}

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

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

/// <inheritdoc />
public override FlociAzContainer Build()
{
Validate();
return new FlociAzContainer(DockerResourceConfiguration);
}

/// <inheritdoc />
protected override FlociAzBuilder Init()
{
return base.Init()
.WithPortBinding(FlociAzPort, true)
.WithPortBinding(EventHubsPort, true)
.WithPortBinding(ServiceBusPort, true)
.WithConnectionStringProvider(new FlociAzConnectionStringProvider())
.WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request =>
request.ForPath("/_floci/health").ForPort(FlociAzPort)));
}

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

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

/// <inheritdoc />
protected override FlociAzBuilder Merge(FlociAzConfiguration oldValue, FlociAzConfiguration newValue)
{
return new FlociAzBuilder(new FlociAzConfiguration(oldValue, newValue));
}
}
41 changes: 41 additions & 0 deletions src/Testcontainers.FlociAz/FlociAzConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace Testcontainers.FlociAz;

/// <inheritdoc cref="ContainerConfiguration" />
[PublicAPI]
public sealed class FlociAzConfiguration : ContainerConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="FlociAzConfiguration" /> class.
/// </summary>
public FlociAzConfiguration()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="FlociAzConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public FlociAzConfiguration(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
: base(resourceConfiguration)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="FlociAzConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The container configuration.</param>
public FlociAzConfiguration(IContainerConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="FlociAzConfiguration" /> class.
/// </summary>
/// <param name="oldValue">The old FlociAz configuration.</param>
/// <param name="newValue">The new FlociAz configuration.</param>
public FlociAzConfiguration(FlociAzConfiguration oldValue, FlociAzConfiguration newValue)
: base(oldValue, newValue)
{
}
}
13 changes: 13 additions & 0 deletions src/Testcontainers.FlociAz/FlociAzConnectionStringProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Testcontainers.FlociAz;

/// <summary>
/// Provides the FlociAz connection string.
/// </summary>
internal sealed class FlociAzConnectionStringProvider : ContainerConnectionStringProvider<FlociAzContainer, FlociAzConfiguration>
{
/// <inheritdoc />
protected override string GetHostConnectionString()
{
return Container.GetConnectionString();
}
}
78 changes: 78 additions & 0 deletions src/Testcontainers.FlociAz/FlociAzContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
namespace Testcontainers.FlociAz;

/// <inheritdoc cref="DockerContainer" />
[PublicAPI]
public sealed class FlociAzContainer : DockerContainer
{
/// <summary>
/// Initializes a new instance of the <see cref="FlociAzContainer" /> class.
/// </summary>
/// <param name="configuration">The container configuration.</param>
public FlociAzContainer(FlociAzConfiguration configuration)
: base(configuration)
{
}

/// <summary>
/// Gets the FlociAz storage connection string.
/// </summary>
/// <returns>The FlociAz storage connection string.</returns>
public string GetConnectionString()
{
var properties = new Dictionary<string, string>();
properties.Add("DefaultEndpointsProtocol", Uri.UriSchemeHttp);
properties.Add("AccountName", FlociAzBuilder.AccountName);
properties.Add("AccountKey", FlociAzBuilder.AccountKey);
properties.Add("BlobEndpoint", GetServiceEndpoint());
properties.Add("QueueEndpoint", GetServiceEndpoint("queue"));
properties.Add("TableEndpoint", GetServiceEndpoint("table"));
return string.Join(";", properties.Select(property => string.Join("=", property.Key, property.Value)));
}

/// <summary>
/// Gets the FlociAz service endpoint.
/// </summary>
/// <remarks>
/// FlociAz routes its REST services by path: the blob storage service uses the
/// bare <c>/{accountName}</c> path (omit the <paramref name="service" /> argument),
/// other services use <c>/{accountName}-{service}</c> (e.g., <c>queue</c>,
/// <c>table</c>, <c>cosmos</c>, <c>keyvault</c>, <c>appconfig</c>, <c>functions</c>).
/// </remarks>
/// <param name="service">The service name, or <c>null</c> for the blob storage endpoint.</param>
/// <returns>The FlociAz service endpoint.</returns>
public string GetServiceEndpoint(string service = null)
{
var path = string.IsNullOrEmpty(service) ? FlociAzBuilder.AccountName : string.Join("-", FlociAzBuilder.AccountName, service);
return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(FlociAzBuilder.FlociAzPort), path).ToString();
}

/// <summary>
/// Gets the FlociAz Cosmos DB connection string.
/// </summary>
/// <returns>The FlociAz Cosmos DB connection string.</returns>
public string GetCosmosConnectionString()
{
var properties = new Dictionary<string, string>();
properties.Add("AccountEndpoint", GetServiceEndpoint("cosmos"));
properties.Add("AccountKey", FlociAzBuilder.AccountKey);
return string.Join(";", properties.Select(property => string.Join("=", property.Key, property.Value)));
}

/// <summary>
/// Gets the FlociAz Event Hubs endpoint.
/// </summary>
/// <returns>The FlociAz Event Hubs endpoint.</returns>
public string GetEventHubsEndpoint()
{
return new UriBuilder("amqp", Hostname, GetMappedPublicPort(FlociAzBuilder.EventHubsPort)).ToString();
}

/// <summary>
/// Gets the FlociAz Service Bus endpoint.
/// </summary>
/// <returns>The FlociAz Service Bus endpoint.</returns>
public string GetServiceBusEndpoint()
{
return new UriBuilder("amqp", Hostname, GetMappedPublicPort(FlociAzBuilder.ServiceBusPort)).ToString();
}
}
12 changes: 12 additions & 0 deletions src/Testcontainers.FlociAz/Testcontainers.FlociAz.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" VersionOverride="2023.3.0" PrivateAssets="All"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../Testcontainers/Testcontainers.csproj"/>
</ItemGroup>
</Project>
9 changes: 9 additions & 0 deletions src/Testcontainers.FlociAz/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using Docker.DotNet.Models;
global using DotNet.Testcontainers.Builders;
global using DotNet.Testcontainers.Configurations;
global using DotNet.Testcontainers.Containers;
global using DotNet.Testcontainers.Images;
global using JetBrains.Annotations;
1 change: 1 addition & 0 deletions tests/Testcontainers.FlociAz.Tests/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
1 change: 1 addition & 0 deletions tests/Testcontainers.FlociAz.Tests/.runs-on
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ubuntu-24.04
1 change: 1 addition & 0 deletions tests/Testcontainers.FlociAz.Tests/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FROM floci/floci-az:0.9.0@sha256:5e403a7d788c24ab2d1dc03d4803ef2d3962ca01522abba65ed86f221ae87ac0
102 changes: 102 additions & 0 deletions tests/Testcontainers.FlociAz.Tests/FlociAzContainerTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
namespace Testcontainers.FlociAz;

public sealed class FlociAzContainerTest : IAsyncLifetime
{
private const string AzureService = "Service";

private readonly FlociAzContainer _flociAzContainer = new FlociAzBuilder(TestSession.GetImageFromDockerfile()).Build();

public async ValueTask InitializeAsync()
{
await _flociAzContainer.StartAsync()
.ConfigureAwait(false);
}

public ValueTask DisposeAsync()
{
return _flociAzContainer.DisposeAsync();
}

[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
[Trait(AzureService, "blob")]
public async Task DownloadBlobReturnsUploadedBlob()
{
// Given
var content = Guid.NewGuid().ToString("D");

var client = new BlobServiceClient(_flociAzContainer.GetConnectionString());

var containerClient = client.GetBlobContainerClient(Guid.NewGuid().ToString("D"));

var blobClient = containerClient.GetBlobClient(Guid.NewGuid().ToString("D"));

// When
_ = await containerClient.CreateAsync(cancellationToken: TestContext.Current.CancellationToken)
.ConfigureAwait(true);

_ = await blobClient.UploadAsync(BinaryData.FromString(content), cancellationToken: TestContext.Current.CancellationToken)
.ConfigureAwait(true);

var downloadResult = await blobClient.DownloadContentAsync(TestContext.Current.CancellationToken)
.ConfigureAwait(true);

// Then
Assert.Equal(content, downloadResult.Value.Content.ToString());
Assert.Equal(_flociAzContainer.GetConnectionString(), _flociAzContainer.GetConnectionString(ConnectionMode.Host));
}

[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
[Trait(AzureService, "queue")]
public async Task ReceiveMessageReturnsSentMessage()
{
// Given
var message = Guid.NewGuid().ToString("D");

var client = new QueueServiceClient(_flociAzContainer.GetConnectionString());

var queueClient = client.GetQueueClient(Guid.NewGuid().ToString("D"));

// When
_ = await queueClient.CreateAsync(cancellationToken: TestContext.Current.CancellationToken)
.ConfigureAwait(true);

_ = await queueClient.SendMessageAsync(message, TestContext.Current.CancellationToken)
.ConfigureAwait(true);

var receivedMessage = await queueClient.ReceiveMessageAsync(cancellationToken: TestContext.Current.CancellationToken)
.ConfigureAwait(true);

// Then
Assert.Equal(message, receivedMessage.Value.MessageText);
}

[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
[Trait(AzureService, "table")]
public async Task GetEntityReturnsAddedEntity()
{
// Given
var partitionKey = Guid.NewGuid().ToString("D");

var rowKey = Guid.NewGuid().ToString("D");

var client = new TableServiceClient(_flociAzContainer.GetConnectionString());

var tableClient = client.GetTableClient("Table" + Guid.NewGuid().ToString("N"));

// When
_ = await tableClient.CreateAsync(TestContext.Current.CancellationToken)
.ConfigureAwait(true);

_ = await tableClient.AddEntityAsync(new TableEntity(partitionKey, rowKey), TestContext.Current.CancellationToken)
.ConfigureAwait(true);

var entityResponse = await tableClient.GetEntityAsync<TableEntity>(partitionKey, rowKey, cancellationToken: TestContext.Current.CancellationToken)
.ConfigureAwait(true);

// Then
Assert.Equal(rowKey, entityResponse.Value.RowKey);
}
}
Loading
Loading