Skip to content
Open
36 changes: 4 additions & 32 deletions src/Testcontainers.CosmosDb/CosmosDbBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Testcontainers.CosmosDb;
[PublicAPI]
public sealed class CosmosDbBuilder : ContainerBuilder<CosmosDbBuilder, CosmosDbContainer, CosmosDbConfiguration>
{
public const string CosmosDbImage = "mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:latest";
public const string CosmosDbImage = "mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-preview";

public const ushort CosmosDbPort = 8081;

Expand Down Expand Up @@ -44,8 +44,9 @@ protected override CosmosDbBuilder Init()
{
return base.Init()
.WithImage(CosmosDbImage)
.WithEnvironment("ENABLE_EXPLORER", "false")
.WithPortBinding(CosmosDbPort, true)
.WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil()));
.WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request => request.ForPort(CosmosDbPort)));
}

/// <inheritdoc />
Expand All @@ -65,33 +66,4 @@ protected override CosmosDbBuilder Merge(CosmosDbConfiguration oldValue, CosmosD
{
return new CosmosDbBuilder(new CosmosDbConfiguration(oldValue, newValue));
}

/// <inheritdoc cref="IWaitUntil" />
private sealed class WaitUntil : IWaitUntil
{
/// <inheritdoc />
public async Task<bool> UntilAsync(IContainer container)
{
// CosmosDB's preconfigured HTTP client will redirect the request to the container.
const string requestUri = "https://localhost/_explorer/emulator.pem";

var httpClient = ((CosmosDbContainer)container).HttpClient;

try
{
using var httpResponse = await httpClient.GetAsync(requestUri)
.ConfigureAwait(false);

return httpResponse.IsSuccessStatusCode;
}
catch (Exception)
{
return false;
}
finally
{
httpClient.Dispose();
}
}
}
}
}
22 changes: 18 additions & 4 deletions src/Testcontainers.CosmosDb/CosmosDbContainer.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Microsoft.Azure.Cosmos;

namespace Testcontainers.CosmosDb;

/// <inheritdoc cref="DockerContainer" />
Expand All @@ -20,11 +22,23 @@ public CosmosDbContainer(CosmosDbConfiguration configuration)
public string GetConnectionString()
{
var properties = new Dictionary<string, string>();
properties.Add("AccountEndpoint", new UriBuilder(Uri.UriSchemeHttps, Hostname, GetMappedPublicPort(CosmosDbBuilder.CosmosDbPort)).ToString());
properties.Add("AccountEndpoint", new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(CosmosDbBuilder.CosmosDbPort)).ToString());
properties.Add("AccountKey", CosmosDbBuilder.DefaultAccountKey);
return string.Join(";", properties.Select(property => string.Join("=", property.Key, property.Value)));
}

/// <summary>
/// Gets a configured cosmos client with connection mode set to Gateway.
/// </summary>
public CosmosClient CosmosClient
=> new CosmosClient(
GetConnectionString(),
new()
{
ConnectionMode = ConnectionMode.Gateway,
HttpClientFactory = () => new(new UriRewriter(Hostname, GetMappedPublicPort(CosmosDbBuilder.CosmosDbPort)))
});

/// <summary>
/// Gets a configured HTTP message handler that automatically trusts the CosmosDb Emulator's certificate.
/// </summary>
Expand All @@ -50,7 +64,7 @@ private sealed class UriRewriter : DelegatingHandler
/// <param name="hostname">The target hostname.</param>
/// <param name="port">The target port.</param>
public UriRewriter(string hostname, ushort port)
: base(new HttpClientHandler { ServerCertificateCustomValidationCallback = (_, _, _, _) => true })
: base(new HttpClientHandler())
{
_hostname = hostname;
_port = port;
Expand All @@ -59,8 +73,8 @@ public UriRewriter(string hostname, ushort port)
/// <inheritdoc />
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.RequestUri = new UriBuilder(Uri.UriSchemeHttps, _hostname, _port, request.RequestUri.PathAndQuery).Uri;
request.RequestUri = new UriBuilder(Uri.UriSchemeHttp, _hostname, _port, request.RequestUri.PathAndQuery).Uri;
return base.SendAsync(request, cancellationToken);
}
}
}
}
3 changes: 2 additions & 1 deletion src/Testcontainers.CosmosDb/Testcontainers.CosmosDb.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" VersionOverride="2023.3.0" PrivateAssets="All"/>
<PackageReference Include="Microsoft.Azure.Cosmos"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../Testcontainers/Testcontainers.csproj"/>
</ItemGroup>
</Project>
</Project>
62 changes: 50 additions & 12 deletions tests/Testcontainers.CosmosDb.Tests/CosmosDbContainerTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace Testcontainers.CosmosDb;
using System;
using System.Linq;

namespace Testcontainers.CosmosDb.Tests;

public sealed class CosmosDbContainerTest : IAsyncLifetime
{
Expand All @@ -14,24 +17,59 @@ public Task DisposeAsync()
return _cosmosDbContainer.DisposeAsync().AsTask();
}

[Fact(Skip = "The Cosmos DB Linux Emulator Docker image does not run on Microsoft's CI environment (GitHub, Azure DevOps).")] // https://github.com/Azure/azure-cosmos-db-emulator-docker/issues/45.
[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public async Task CreateDatabaseAndContainerSuccessful()
{
// Given
using var cosmosClient = _cosmosDbContainer.CosmosClient;


// When
var database = (await cosmosClient.CreateDatabaseIfNotExistsAsync("fakedb")).Database;
await database.CreateContainerIfNotExistsAsync("fakecontainer", "/id");
var databaseProperties = (await cosmosClient.GetDatabaseQueryIterator<DatabaseProperties>().ReadNextAsync()).First();


// Then
Assert.Equal("fakedb", databaseProperties.Id);
}


[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public async Task AccountPropertiesIdReturnsLocalhost()
public async Task RetrieveItemCreated()
{
// Given
using var httpClient = _cosmosDbContainer.HttpClient;
using var cosmosClient = _cosmosDbContainer.CosmosClient;

var cosmosClientOptions = new CosmosClientOptions();
cosmosClientOptions.ConnectionMode = ConnectionMode.Gateway;
cosmosClientOptions.HttpClientFactory = () => httpClient;
var database = (await cosmosClient.CreateDatabaseIfNotExistsAsync("dbfake")).Database;
await database.CreateContainerIfNotExistsAsync("containerfake", "/id");
var container = database.GetContainer("containerfake");

var id = Guid.NewGuid().ToString();
var name = Guid.NewGuid().ToString();

using var cosmosClient = new CosmosClient(_cosmosDbContainer.GetConnectionString(), cosmosClientOptions);

// When
var accountProperties = await cosmosClient.ReadAccountAsync()
.ConfigureAwait(true);
var response = await container.CreateItemAsync(
new FakeItem { id = id, Name = name },
new PartitionKey(id));

var itemResponse = await container.ReadItemAsync<FakeItem>(
id,
new PartitionKey(id));


// Then
Assert.Equal("localhost", accountProperties.Id);
Assert.Equal(id, itemResponse.Resource.id);
Assert.Equal(name, itemResponse.Resource.Name);
}


private class FakeItem
{
public string id { get; set; }
public string Name { get; set; }
}
}
}