From dad2dc922fa0ee42761dd16192ec3d2cd368af4d Mon Sep 17 00:00:00 2001 From: Jesse Wellenberg <48557715+Yeseh@users.noreply.github.com> Date: Sun, 28 Aug 2022 16:35:09 +0200 Subject: [PATCH] Restructure to model Azurite testcontainer --- Packages.props | 1 + .../TestcontainersBuilderCosmosDbExtension.cs | 21 ++++ .../CosmosDbTestcontainerConfiguration.cs | 74 ++++++++---- .../Databases/CosmosDbTestcontainer.cs | 108 +++--------------- .../Unix/Modules/Databases/CosmosDbFixture.cs | 93 +++++++-------- .../Testcontainers.Tests.csproj | 1 + .../Databases/CosmosDbTestcontainerTest.cs | 61 +++++++--- 7 files changed, 185 insertions(+), 174 deletions(-) create mode 100644 src/Testcontainers/Builders/TestcontainersBuilderCosmosDbExtension.cs diff --git a/Packages.props b/Packages.props index 3630ba010..27b841964 100644 --- a/Packages.props +++ b/Packages.props @@ -27,6 +27,7 @@ + diff --git a/src/Testcontainers/Builders/TestcontainersBuilderCosmosDbExtension.cs b/src/Testcontainers/Builders/TestcontainersBuilderCosmosDbExtension.cs new file mode 100644 index 000000000..c43cf7f2e --- /dev/null +++ b/src/Testcontainers/Builders/TestcontainersBuilderCosmosDbExtension.cs @@ -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 WithCosmosDb( + this ITestcontainersBuilder builder, CosmosDbTestcontainerConfiguration configuration) + { + builder.WithImage(configuration.Image) + .WithWaitStrategy(configuration.WaitStrategy) + .WithExposedPort(configuration.SqlApiContainerPort) + .WithPortBinding(configuration.SqlApiPort); + + return builder; + } + } +} diff --git a/src/Testcontainers/Configurations/Modules/Databases/CosmosDbTestcontainerConfiguration.cs b/src/Testcontainers/Configurations/Modules/Databases/CosmosDbTestcontainerConfiguration.cs index 602dbaa85..262b2aca6 100644 --- a/src/Testcontainers/Configurations/Modules/Databases/CosmosDbTestcontainerConfiguration.cs +++ b/src/Testcontainers/Configurations/Modules/Databases/CosmosDbTestcontainerConfiguration.cs @@ -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 @@ -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; + } } } diff --git a/src/Testcontainers/Containers/Modules/Databases/CosmosDbTestcontainer.cs b/src/Testcontainers/Containers/Modules/Databases/CosmosDbTestcontainer.cs index 8d928148e..b1c3c5fb7 100644 --- a/src/Testcontainers/Containers/Modules/Databases/CosmosDbTestcontainer.cs +++ b/src/Testcontainers/Containers/Modules/Databases/CosmosDbTestcontainer.cs @@ -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 QueryAsync( - string queryString, IEnumerable> 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 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 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}"; } } diff --git a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/Modules/Databases/CosmosDbFixture.cs b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/Modules/Databases/CosmosDbFixture.cs index bb633c395..e1fa2b7a3 100644 --- a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/Modules/Databases/CosmosDbFixture.cs +++ b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/Modules/Databases/CosmosDbFixture.cs @@ -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 + public static class CosmosDbFixture { - private readonly TestcontainerDatabaseConfiguration configuration = new CosmosDbTestcontainerConfiguration + [UsedImplicitly] + public class CosmosDbDefaultFixture : DatabaseFixture { - Database = "testdb" - }; - - public CosmosDbFixture() - { - this.Container = new TestcontainersBuilder() - .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() + .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(); + } } } } diff --git a/tests/Testcontainers.Tests/Testcontainers.Tests.csproj b/tests/Testcontainers.Tests/Testcontainers.Tests.csproj index 7b404d797..99b766802 100644 --- a/tests/Testcontainers.Tests/Testcontainers.Tests.csproj +++ b/tests/Testcontainers.Tests/Testcontainers.Tests.csproj @@ -24,6 +24,7 @@ + diff --git a/tests/Testcontainers.Tests/Unit/Containers/Unix/Modules/Databases/CosmosDbTestcontainerTest.cs b/tests/Testcontainers.Tests/Unit/Containers/Unix/Modules/Databases/CosmosDbTestcontainerTest.cs index 7bec469e7..168760c53 100644 --- a/tests/Testcontainers.Tests/Unit/Containers/Unix/Modules/Databases/CosmosDbTestcontainerTest.cs +++ b/tests/Testcontainers.Tests/Unit/Containers/Unix/Modules/Databases/CosmosDbTestcontainerTest.cs @@ -1,25 +1,58 @@ namespace DotNet.Testcontainers.Tests.Unit { + using Microsoft.Azure.Cosmos; using DotNet.Testcontainers.Tests.Fixtures; using System.Threading.Tasks; using Xunit; + using System.Net.Http; + using System.Data.Common; + using DotNet.Testcontainers.Containers; - public class CosmosDbTestcontainerTest : IClassFixture - { - private readonly CosmosDbFixture cosmosDbFixture; + public static class CosmosDbTestcontainerTest + { + [Collection(nameof(Testcontainers))] + public sealed class SqlApi : IClassFixture + { + private static readonly CosmosClientOptions skipSslValidationOptions = new CosmosClientOptions() + { + HttpClientFactory = () => + { + HttpMessageHandler httpMessageHandler = new HttpClientHandler() + { + ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator + }; - public CosmosDbTestcontainerTest(CosmosDbFixture cosmosDbFixture) - { - this.cosmosDbFixture = cosmosDbFixture; - } + return new HttpClient(httpMessageHandler); + }, - [Fact] - public async Task DatabaseCreated() - { - var dbResponse = await this.cosmosDbFixture.Container.CreateDatabaseAsync() - .ConfigureAwait(false); + ConnectionMode = ConnectionMode.Gateway + }; - Assert.Equal(System.Net.HttpStatusCode.Created, dbResponse.StatusCode); - } + private readonly CosmosDbFixture.CosmosDbDefaultFixture commonContainerPorts; + + private CosmosDbTestcontainer Container; + + public SqlApi(CosmosDbFixture.CosmosDbDefaultFixture commonContainerPorts) + { + this.commonContainerPorts = commonContainerPorts; + } + + [Fact] + public async Task ShouldEstablishConnection() + { + var exception = await Record.ExceptionAsync(() => Task.WhenAll(EstablishConnection(commonContainerPorts))); + Assert.Null(exception); + } + + private static async Task EstablishConnection(CosmosDbFixture.CosmosDbDefaultFixture cosmosDb) + { + var client = new CosmosClient(cosmosDb.Container.ConnectionString, skipSslValidationOptions); + + var properties = await client.ReadAccountAsync() + .ConfigureAwait(false); + + Assert.Equal(cosmosDb.Configuration.SqlApiPort, cosmosDb.Container.SqlApiPort); + } } + } }