Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Update CosmosDb image to vnext-preview version #1324

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
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";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to use the preview version as a default? I know that this image is quite new but users can always:

new CosmosDbBuilder()
.WithImage("mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-preview")
.Build();

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The advantage of this PR is the use of this image as default, as it delivers significantly better results compared to the current :latest. Additionally, some configurations made in this PR are not compatible between the :latest and :vnext-preview versions. Currently, the :vnext-preview version shows considerably better results than :latest.


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)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use this.HttpClient here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the discussion here, it was agreed that it would be better to remove the dependency on the Cosmos Client and delegate the responsibility of creating the client to the consumer.

});

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