Skip to content

Commit

Permalink
Restructure to model Azurite testcontainer
Browse files Browse the repository at this point in the history
  • Loading branch information
Yeseh committed Aug 28, 2022
1 parent c3809a4 commit dad2dc9
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 174 deletions.
1 change: 1 addition & 0 deletions Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<PackageReference Update="Azure.Data.Tables" Version="12.6.1" />
<PackageReference Update="Azure.Storage.Blobs" Version="12.13.0" />
<PackageReference Update="Azure.Storage.Queues" Version="12.11.0" />
<PackageReference Update="Microsoft.Azure.Cosmos" Version="3.30.0" />
<PackageReference Update="Confluent.Kafka" Version="1.8.2" />
<PackageReference Update="CouchbaseNetClient" Version="3.2.9" />
<PackageReference Update="Elastic.Clients.Elasticsearch" Version="8.0.0-beta.3" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace DotNet.Testcontainers.Builders
{
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;
using JetBrains.Annotations;

[PublicAPI]
public static class TestcontainersBuilderCosmosDbExtension
{
public static ITestcontainersBuilder<CosmosDbTestcontainer> WithCosmosDb(
this ITestcontainersBuilder<CosmosDbTestcontainer> builder, CosmosDbTestcontainerConfiguration configuration)
{
builder.WithImage(configuration.Image)
.WithWaitStrategy(configuration.WaitStrategy)
.WithExposedPort(configuration.SqlApiContainerPort)
.WithPortBinding(configuration.SqlApiPort);

return builder;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,39 +1,63 @@
namespace DotNet.Testcontainers.Configurations
{
using System;
using DotNet.Testcontainers.Builders;
using JetBrains.Annotations;

[PublicAPI]
public class CosmosDbTestcontainerConfiguration : TestcontainerDatabaseConfiguration
{
private const string CosmosDbImage =
// TODO: WithMongoAPI extension?
public const string DefaultCosmosDbSqlApiImage =
"mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator";

private const int CosmosDbPort = 8081;
public const string DefaultCosmosDbMongoDbApiImage =
"mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:mongo";

private const string CosmosDbDefaultKey =
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";

private const string CosmosDbDefaultAccountName =
"localhost:8081";
private const int DefaultSqlApiPort = 8081;

private const int DefaultMongoDbApiPort = 10250;

public CosmosDbTestcontainerConfiguration()
: this(CosmosDbImage) { }
: this(DefaultCosmosDbSqlApiImage)
{
}

public CosmosDbTestcontainerConfiguration(string image)
: base(image, CosmosDbPort, CosmosDbPort)
public CosmosDbTestcontainerConfiguration(string image)
: base(image, DefaultSqlApiPort)
{
this.Environments.Add("AZURE_COSMOS_EMULATOR_PARTITION_COUNT", "1");
this.Environments.Add("AZURE_COSMOS_EMULATOR_MONGO_DB_ENDPOINT", "");
this.Environments.Add("AZURE_COSMOS_EMULATOR_PARTITION_COUNT", "1");
}

public override string Username
public int MongoDbApiPort { get; set; }

public int MongoDbApiContainerPort { get; set; }

public int SqlApiPort { get; set; }

public int SqlApiContainerPort { get; set; }

public override IWaitForContainerOS WaitStrategy
{
get => CosmosDbDefaultAccountName;
get
{
var waitStrategy = Wait.ForUnixContainer();
// waitStrategy = string.IsNullOrWhiteSpace(this.MongoDbEndpoint)
// ? waitStrategy.UntilPortIsAvailable(SqlApiContainerPort)
// : waitStrategy.UntilPortIsAvailable(MongoDbApiContainerPort);
waitStrategy = waitStrategy.UntilMessageIsLogged(this.OutputConsumer.Stdout, "Started");

return waitStrategy;
}
}

public override string Password
{
get => CosmosDbDefaultKey;
// Default key for the emulator
// See: https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator?tabs=ssl-netstd21#authenticate-requests
get => "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
set => throw new NotImplementedException();
}

public override string Database
Expand All @@ -42,26 +66,28 @@ public override string Database
set => this.Environments["AZURE_COSMOS_EMULATOR_DATABASE"] = value;
}

public string PartitionCount
public int PartitionCount
{
get => this.Environments["AZURE_COSMOS_EMULATOR_PARTITION_COUNT"];
set => this.Environments["AZURE_COSMOS_EMULATOR_PARTITION_COUNT"] = value;
get => int.Parse(this.Environments["AZURE_COSMOS_EMULATOR_PARTITION_COUNT"]);
set => this.Environments["AZURE_COSMOS_EMULATOR_PARTITION_COUNT"] = value.ToString();
}

public string EnableDataPersistence
public bool EnableDataPersistence
{
get => this.Environments["AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE"];
set => this.Environments["AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE"] = value;
get => this.Environments["AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE"] == "true";
set => this.Environments["AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE"] = value.ToString().ToLower();
}

public string IPAddressOverride
{
get => this.Environments["AZURE_COSMOS_EMULATOR_IP_ADDRESS_OVERRIDE"];
set => this.Environments["AZURE_COSMOS_EMULATOR_IP_ADDRESS_OVERRIDE"] = value;
}
}

public override IWaitForContainerOS WaitStrategy => Wait.ForUnixContainer()
.UntilCommandIsCompleted("ls");
//.UntilMessageIsLogged(this.OutputConsumer.Stdout, "Started");
public string MongoDbEndpoint
{
get => this.Environments["AZURE_COSMOS_EMULATOR_MONGO_DB_ENDPOINT"];
set => this.Environments["AZURE_COSMOS_EMULATOR_MONGO_DB_ENDPOINT"] = value;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,110 +1,36 @@
namespace DotNet.Testcontainers.Containers
{
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using DotNet.Testcontainers.Configurations;
using Microsoft.Extensions.Logging;
using System.Text.Json;
using System;
using System.Threading;
using System.Text;
using JetBrains.Annotations;

public sealed class CosmosDbTestcontainer : TestcontainerDatabase
{
private string CosmosUrl;
[PublicAPI]
public int SqlApiPort
=> this.GetMappedPublicPort(this.ContainerSqlApiPort);

private HttpClient HttpClient;

internal CosmosDbTestcontainer(ITestcontainersConfiguration configuration, ILogger logger)
: base(configuration, logger)
{
CosmosUrl = $"https://{this.Username}.documents.azure.com/dbs";
}
[PublicAPI]
public int MongoApiPort
=> this.GetMappedPublicPort(this.ContainerMongoApiPort);

public override string ConnectionString
=> $"AccountEndpoint=https://{this.Username}:{this.Port}.documents.azure.com:443/;AccountKey={this.Password}";
[PublicAPI]
public int ContainerSqlApiPort { get; set; }

public async Task<HttpResponseMessage> QueryAsync(
string queryString, IEnumerable<KeyValuePair<string, string>> parameters = default)
{
Console.WriteLine("Executing query...");
var client = GetHttpClient();
var parJsonStr =JsonSerializer.Serialize(parameters);
var body = new { Query = queryString, Parameters = parJsonStr };
var reqBodyStr = JsonSerializer.Serialize(body);
var content = new StringContent(reqBodyStr);

var response = await client.PostAsync(CosmosUrl, content);
[PublicAPI]
public int ContainerMongoApiPort { get; set; }

return response;
}
// TODO: Call this implicitly on initialize
public async Task<HttpResponseMessage> CreateDatabaseAsync()
{
Console.WriteLine("Attempting to create database...");
var url = $"https://localhost:8081/dbs";
var client = GetHttpClient();
var jsonData = JsonSerializer.Serialize(new { id = string.IsNullOrEmpty(this.Database) ? "testdb" : this.Database });
var contentData = new StringContent(jsonData, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, contentData).ConfigureAwait(false);
public string AccountEndpoint { get; }

return response;
}
private HttpClient HttpClient;

private HttpClient GetHttpClient()
internal CosmosDbTestcontainer(ITestcontainersConfiguration configuration, ILogger logger)
: base(configuration, logger)
{
if (HttpClient != null) return HttpClient;

var handler = new CosmosDbHttpHandler(this.Password);
var client = new HttpClient(handler);

HttpClient = client;
return HttpClient;
}

private sealed class CosmosDbHttpHandler : HttpClientHandler
{
private readonly string _password;

public CosmosDbHttpHandler(string password)
{
this._password = password;
// Skip SSL certificate validation
// https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator?tabs=ssl-netstd21#disable-ssl-validation
this.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true;
}

// https://stackoverflow.com/questions/52262767/cosmos-db-rest-api-create-user-permissio
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken ct)
{
var hmac = new System.Security.Cryptography.HMACSHA256()
{
Key = Convert.FromBase64String(this._password)
};

var date = DateTime.UtcNow.ToString("r");
var payload = "POST".ToLowerInvariant() + "\n" +
"dbs".ToLowerInvariant() + "\n" +
"" + "\n" +
date.ToLowerInvariant() + "\n" +
"" + "\n";

var hashPayload = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
var signature = Convert.ToBase64String(hashPayload);
var authHeaderValue = System.Web.HttpUtility.UrlEncode($"type=master&ver=1.0&sig={signature}");

request.Headers.Add("x-ms-documentdb-isquery", "true");
request.Headers.Add("x-ms-documentdb-query-enablecrosspartition", "true");
request.Headers.Add("x-ms-version", "2018-12-31");
request.Headers.Add("x-ms-date", date);
request.Headers.Add("authorization", authHeaderValue);
//request.Headers.Add("Content-Type", "application/query+json");
request.Headers.Add("Accept", "application/json");

return base.SendAsync(request, ct);
}
}
public override string ConnectionString =>
$"AccountEndpoint=https://{this.Hostname}:{this.SqlApiPort};AccountKey={this.Password}";
}
}
Original file line number Diff line number Diff line change
@@ -1,57 +1,60 @@
namespace DotNet.Testcontainers.Tests.Fixtures
{
using System;
using System.Data.Common;
using System.Threading.Tasks;
using System;
using System.Data.Common;
using System.IO;
using System.Threading.Tasks;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;
using JetBrains.Annotations;
using Npgsql;

public sealed class CosmosDbFixture : DatabaseFixture<CosmosDbTestcontainer, DbConnection>
public static class CosmosDbFixture
{
private readonly TestcontainerDatabaseConfiguration configuration = new CosmosDbTestcontainerConfiguration
[UsedImplicitly]
public class CosmosDbDefaultFixture : DatabaseFixture<CosmosDbTestcontainer, DbConnection>
{
Database = "testdb"
};

public CosmosDbFixture()
{
this.Container = new TestcontainersBuilder<CosmosDbTestcontainer>()
.WithDatabase(this.configuration)
.Build();
}

public override async Task InitializeAsync()
{
Console.WriteLine("Initializing CosmosDB container");
await this.Container.StartAsync()
.ConfigureAwait(false);

// var dbResponse = await this.Container.CreateDatabaseAsync()
// .ConfigureAwait(false);

// if (dbResponse.StatusCode != System.Net.HttpStatusCode.Created)
// {
// throw new System.Exception("Failed to create database");
// }
}

public override async Task DisposeAsync()
{
if (Connection != null && Connection.State != System.Data.ConnectionState.Closed)
{
this.Connection.Dispose();

}

await this.Container.DisposeAsync()
.ConfigureAwait(false);
}

public override void Dispose()
{
this.configuration.Dispose();
public CosmosDbTestcontainerConfiguration Configuration { get; set; }

public CosmosDbDefaultFixture()
: this(new CosmosDbTestcontainerConfiguration())
{
}

protected CosmosDbDefaultFixture(CosmosDbTestcontainerConfiguration configuration)
{
this.Configuration = configuration;
this.Container = new TestcontainersBuilder<CosmosDbTestcontainer>()
.WithHostname("localhost")
.WithImage(configuration.Image)
.WithPortBinding(configuration.DefaultPort)
.WithExposedPort(configuration.DefaultPort)
.WithWaitStrategy(configuration.WaitStrategy)
.WithEnvironment("AZURE_COSMOS_EMULATOR_PARTITION_COUNT", "1")
.Build();
}

public override Task InitializeAsync()
{
return this.Container.StartAsync();
}

public override async Task DisposeAsync()
{
if (Connection != null && Connection.State != System.Data.ConnectionState.Closed)
{
this.Connection.Dispose();
}

await this.Container.DisposeAsync()
.ConfigureAwait(false);
}

public override void Dispose()
{
this.Configuration.Dispose();
}
}
}
}
1 change: 1 addition & 0 deletions tests/Testcontainers.Tests/Testcontainers.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<PackageReference Include="Azure.Data.Tables" />
<PackageReference Include="Azure.Storage.Blobs" />
<PackageReference Include="Azure.Storage.Queues" />
<PackageReference Include="Microsoft.Azure.Cosmos" />
<PackageReference Include="Confluent.Kafka" />
<PackageReference Include="CouchbaseNetClient" />
<PackageReference Include="Elastic.Clients.Elasticsearch" />
Expand Down
Loading

0 comments on commit dad2dc9

Please sign in to comment.