From a2a15798108ee25d5073cb0f496f9b383dbf46b5 Mon Sep 17 00:00:00 2001 From: Thomas Clark Date: Thu, 13 Jun 2024 19:10:08 +0100 Subject: [PATCH 1/2] feat: add azure app configuration module --- Testcontainers.sln | 14 +++ .../.editorconfig | 1 + .../AzureAppConfigurationBuilder.cs | 108 ++++++++++++++++++ .../AzureAppConfigurationConfiguration.cs | 69 +++++++++++ .../AzureAppConfigurationContainer.cs | 31 +++++ ...estcontainers.AzureAppConfiguration.csproj | 12 ++ .../Usings.cs | 9 ++ .../.editorconfig | 1 + .../AzureAppConfigurationContainerTest.cs | 34 ++++++ ...tainers.AzureAppConfiguration.Tests.csproj | 18 +++ .../Usings.cs | 4 + 11 files changed, 301 insertions(+) create mode 100644 src/Testcontainers.AzureAppConfiguration/.editorconfig create mode 100644 src/Testcontainers.AzureAppConfiguration/AzureAppConfigurationBuilder.cs create mode 100644 src/Testcontainers.AzureAppConfiguration/AzureAppConfigurationConfiguration.cs create mode 100644 src/Testcontainers.AzureAppConfiguration/AzureAppConfigurationContainer.cs create mode 100644 src/Testcontainers.AzureAppConfiguration/Testcontainers.AzureAppConfiguration.csproj create mode 100644 src/Testcontainers.AzureAppConfiguration/Usings.cs create mode 100644 tests/Testcontainers.AzureAppConfiguration.Tests/.editorconfig create mode 100644 tests/Testcontainers.AzureAppConfiguration.Tests/AzureAppConfigurationContainerTest.cs create mode 100644 tests/Testcontainers.AzureAppConfiguration.Tests/Testcontainers.AzureAppConfiguration.Tests.csproj create mode 100644 tests/Testcontainers.AzureAppConfiguration.Tests/Usings.cs diff --git a/Testcontainers.sln b/Testcontainers.sln index e8c10a811..51790f98f 100644 --- a/Testcontainers.sln +++ b/Testcontainers.sln @@ -17,6 +17,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ActiveMq", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ArangoDb", "src\Testcontainers.ArangoDb\Testcontainers.ArangoDb.csproj", "{AB9C1563-07C7-4685-BACD-BB1FF64B3611}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.AzureAppConfiguration", "src\Testcontainers.AzureAppConfiguration\Testcontainers.AzureAppConfiguration.csproj", "{5C03BAF1-8CC7-421D-A457-9FBEF923E06E}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Azurite", "src\Testcontainers.Azurite\Testcontainers.Azurite.csproj", "{3F2E254F-C203-43FD-A078-DC3E2CBC0F9F}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.BigQuery", "src\Testcontainers.BigQuery\Testcontainers.BigQuery.csproj", "{A9FF9C7F-BBA0-4B44-90B7-48A60F9E00F3}" @@ -105,6 +107,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ActiveMq.Tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ArangoDb.Tests", "tests\Testcontainers.ArangoDb.Tests\Testcontainers.ArangoDb.Tests.csproj", "{8E1E0A6D-EEBB-4455-B8E8-A55AF9B2062C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.AzureAppConfiguration.Tests", "tests\Testcontainers.AzureAppConfiguration.Tests\Testcontainers.AzureAppConfiguration.Tests.csproj", "{A7C87D4E-540F-4AD2-8A42-C7F86700688D}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Azurite.Tests", "tests\Testcontainers.Azurite.Tests\Testcontainers.Azurite.Tests.csproj", "{B272FDDE-5E01-425D-B9E1-10FF883DDAAA}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.BigQuery.Tests", "tests\Testcontainers.BigQuery.Tests\Testcontainers.BigQuery.Tests.csproj", "{03E60673-078A-4508-99AD-8537CE6F78F1}" @@ -580,6 +584,14 @@ Global {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.Build.0 = Release|Any CPU + {5C03BAF1-8CC7-421D-A457-9FBEF923E06E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C03BAF1-8CC7-421D-A457-9FBEF923E06E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C03BAF1-8CC7-421D-A457-9FBEF923E06E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C03BAF1-8CC7-421D-A457-9FBEF923E06E}.Release|Any CPU.Build.0 = Release|Any CPU + {A7C87D4E-540F-4AD2-8A42-C7F86700688D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A7C87D4E-540F-4AD2-8A42-C7F86700688D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A7C87D4E-540F-4AD2-8A42-C7F86700688D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A7C87D4E-540F-4AD2-8A42-C7F86700688D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {5365F780-0E6C-41F0-B1B9-7DC34368F80C} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} @@ -675,5 +687,7 @@ Global {1A1983E6-5297-435F-B467-E8E1F11277D6} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {27CDB869-A150-4593-958F-6F26E5391E7C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} + {5C03BAF1-8CC7-421D-A457-9FBEF923E06E} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} + {A7C87D4E-540F-4AD2-8A42-C7F86700688D} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} EndGlobalSection EndGlobal diff --git a/src/Testcontainers.AzureAppConfiguration/.editorconfig b/src/Testcontainers.AzureAppConfiguration/.editorconfig new file mode 100644 index 000000000..6f066619d --- /dev/null +++ b/src/Testcontainers.AzureAppConfiguration/.editorconfig @@ -0,0 +1 @@ +root = true \ No newline at end of file diff --git a/src/Testcontainers.AzureAppConfiguration/AzureAppConfigurationBuilder.cs b/src/Testcontainers.AzureAppConfiguration/AzureAppConfigurationBuilder.cs new file mode 100644 index 000000000..c2a00552b --- /dev/null +++ b/src/Testcontainers.AzureAppConfiguration/AzureAppConfigurationBuilder.cs @@ -0,0 +1,108 @@ +namespace Testcontainers.AzureAppConfiguration; + +/// +[PublicAPI] +public sealed class AzureAppConfigurationBuilder : ContainerBuilder +{ + public const string AzureAppConfigurationImage = "tnc1997/azure-app-configuration-emulator:1.0"; + + public const ushort AzureAppConfigurationPort = 8080; + + public const string DefaultCredential = "abcd"; + + public const string DefaultSecret = "c2VjcmV0"; + + /// + /// Initializes a new instance of the class. + /// + public AzureAppConfigurationBuilder() + : this(new AzureAppConfigurationConfiguration()) + { + DockerResourceConfiguration = Init().DockerResourceConfiguration; + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + private AzureAppConfigurationBuilder(AzureAppConfigurationConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + DockerResourceConfiguration = resourceConfiguration; + } + + /// + protected override AzureAppConfigurationConfiguration DockerResourceConfiguration { get; } + + /// + /// Sets the Azure App Configuration credential. + /// + /// The Azure App Configuration credential. + /// A configured instance of . + public AzureAppConfigurationBuilder WithCredential(string credential) + { + return Merge(DockerResourceConfiguration, new AzureAppConfigurationConfiguration(credential: credential)) + .WithEnvironment("Authentication__Schemes__Hmac__Credential", credential); + } + + /// + /// Sets the Azure App Configuration secret. + /// + /// The Azure App Configuration secret. + /// A configured instance of . + public AzureAppConfigurationBuilder WithSecret(string secret) + { + return Merge(DockerResourceConfiguration, new AzureAppConfigurationConfiguration(secret: secret)) + .WithEnvironment("Authentication__Schemes__Hmac__Secret", secret); + } + + /// + public override AzureAppConfigurationContainer Build() + { + Validate(); + return new AzureAppConfigurationContainer(DockerResourceConfiguration); + } + + /// + protected override AzureAppConfigurationBuilder Init() + { + return base.Init() + .WithImage(AzureAppConfigurationImage) + .WithPortBinding(AzureAppConfigurationPort, true) + .WithCredential(DefaultCredential) + .WithSecret(DefaultSecret) + .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("Now listening on")); + } + + /// + protected override void Validate() + { + base.Validate(); + + _ = Guard.Argument(DockerResourceConfiguration.Credential, nameof(DockerResourceConfiguration.Credential)) + .NotNull() + .NotEmpty(); + + _ = Guard.Argument(DockerResourceConfiguration.Secret, nameof(DockerResourceConfiguration.Secret)) + .NotNull() + .NotEmpty(); + } + + /// + protected override AzureAppConfigurationBuilder Clone(IResourceConfiguration resourceConfiguration) + { + return Merge(DockerResourceConfiguration, new AzureAppConfigurationConfiguration(resourceConfiguration)); + } + + /// + protected override AzureAppConfigurationBuilder Clone(IContainerConfiguration resourceConfiguration) + { + return Merge(DockerResourceConfiguration, new AzureAppConfigurationConfiguration(resourceConfiguration)); + } + + /// + protected override AzureAppConfigurationBuilder Merge(AzureAppConfigurationConfiguration oldValue, AzureAppConfigurationConfiguration newValue) + { + return new AzureAppConfigurationBuilder(new AzureAppConfigurationConfiguration(oldValue, newValue)); + } +} \ No newline at end of file diff --git a/src/Testcontainers.AzureAppConfiguration/AzureAppConfigurationConfiguration.cs b/src/Testcontainers.AzureAppConfiguration/AzureAppConfigurationConfiguration.cs new file mode 100644 index 000000000..514c9a8e7 --- /dev/null +++ b/src/Testcontainers.AzureAppConfiguration/AzureAppConfigurationConfiguration.cs @@ -0,0 +1,69 @@ +namespace Testcontainers.AzureAppConfiguration; + +/// +[PublicAPI] +public sealed class AzureAppConfigurationConfiguration : ContainerConfiguration +{ + /// + /// Initializes a new instance of the class. + /// + /// The Azure App Configuration credential. + /// The Azure App Configuration secret. + public AzureAppConfigurationConfiguration(string credential = null, string secret = null) + { + Credential = credential; + Secret = secret; + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public AzureAppConfigurationConfiguration(IResourceConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public AzureAppConfigurationConfiguration(IContainerConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public AzureAppConfigurationConfiguration(AzureAppConfigurationConfiguration resourceConfiguration) + : this(new AzureAppConfigurationConfiguration(), resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The old Docker resource configuration. + /// The new Docker resource configuration. + public AzureAppConfigurationConfiguration(AzureAppConfigurationConfiguration oldValue, AzureAppConfigurationConfiguration newValue) + : base(oldValue, newValue) + { + Credential = BuildConfiguration.Combine(oldValue.Credential, newValue.Credential); + Secret = BuildConfiguration.Combine(oldValue.Secret, newValue.Secret); + } + + /// + /// Gets the Azure App Configuration credential. + /// + public string Credential { get; } + + /// + /// Gets the Azure App Configuration secret. + /// + public string Secret { get; } +} \ No newline at end of file diff --git a/src/Testcontainers.AzureAppConfiguration/AzureAppConfigurationContainer.cs b/src/Testcontainers.AzureAppConfiguration/AzureAppConfigurationContainer.cs new file mode 100644 index 000000000..3c1a95765 --- /dev/null +++ b/src/Testcontainers.AzureAppConfiguration/AzureAppConfigurationContainer.cs @@ -0,0 +1,31 @@ +namespace Testcontainers.AzureAppConfiguration; + +/// +[PublicAPI] +public sealed class AzureAppConfigurationContainer : DockerContainer +{ + private readonly AzureAppConfigurationConfiguration _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The container configuration. + public AzureAppConfigurationContainer(AzureAppConfigurationConfiguration configuration) + : base(configuration) + { + _configuration = configuration; + } + + /// + /// Gets the Azure App Configuration connection string. + /// + /// The Azure App Configuration connection string. + public string GetConnectionString() + { + var properties = new Dictionary(); + properties.Add("Endpoint", new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(AzureAppConfigurationBuilder.AzureAppConfigurationPort)).ToString()); + properties.Add("Id", _configuration.Credential); + properties.Add("Secret", _configuration.Secret); + return string.Join(";", properties.Select(property => string.Join("=", property.Key, property.Value))); + } +} \ No newline at end of file diff --git a/src/Testcontainers.AzureAppConfiguration/Testcontainers.AzureAppConfiguration.csproj b/src/Testcontainers.AzureAppConfiguration/Testcontainers.AzureAppConfiguration.csproj new file mode 100644 index 000000000..f23847217 --- /dev/null +++ b/src/Testcontainers.AzureAppConfiguration/Testcontainers.AzureAppConfiguration.csproj @@ -0,0 +1,12 @@ + + + net6.0;net8.0;netstandard2.0;netstandard2.1 + latest + + + + + + + + diff --git a/src/Testcontainers.AzureAppConfiguration/Usings.cs b/src/Testcontainers.AzureAppConfiguration/Usings.cs new file mode 100644 index 000000000..7a7b31226 --- /dev/null +++ b/src/Testcontainers.AzureAppConfiguration/Usings.cs @@ -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; +global using DotNet.Testcontainers.Builders; +global using DotNet.Testcontainers.Configurations; +global using DotNet.Testcontainers.Containers; +global using JetBrains.Annotations; \ No newline at end of file diff --git a/tests/Testcontainers.AzureAppConfiguration.Tests/.editorconfig b/tests/Testcontainers.AzureAppConfiguration.Tests/.editorconfig new file mode 100644 index 000000000..6f066619d --- /dev/null +++ b/tests/Testcontainers.AzureAppConfiguration.Tests/.editorconfig @@ -0,0 +1 @@ +root = true \ No newline at end of file diff --git a/tests/Testcontainers.AzureAppConfiguration.Tests/AzureAppConfigurationContainerTest.cs b/tests/Testcontainers.AzureAppConfiguration.Tests/AzureAppConfigurationContainerTest.cs new file mode 100644 index 000000000..0f00f5dd0 --- /dev/null +++ b/tests/Testcontainers.AzureAppConfiguration.Tests/AzureAppConfigurationContainerTest.cs @@ -0,0 +1,34 @@ +namespace Testcontainers.AzureAppConfiguration; + +public sealed class AzureAppConfigurationContainerTest : IAsyncLifetime +{ + private readonly AzureAppConfigurationContainer _azureAppConfigurationContainer = new AzureAppConfigurationBuilder().Build(); + + public Task InitializeAsync() + { + return _azureAppConfigurationContainer.StartAsync(); + } + + public Task DisposeAsync() + { + return _azureAppConfigurationContainer.DisposeAsync().AsTask(); + } + + [Fact] + [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] + public async Task GetConfigurationSettingReturnsSetConfigurationSetting() + { + // Given + var client = new ConfigurationClient(_azureAppConfigurationContainer.GetConnectionString()); + + // When + await client.SetConfigurationSettingAsync(nameof(ConfigurationSetting.Key), nameof(ConfigurationSetting.Value)) + .ConfigureAwait(true); + + var response = await client.GetConfigurationSettingAsync(nameof(ConfigurationSetting.Key)) + .ConfigureAwait(true); + + // Then + Assert.Equal(nameof(ConfigurationSetting.Value), response.Value.Value); + } +} \ No newline at end of file diff --git a/tests/Testcontainers.AzureAppConfiguration.Tests/Testcontainers.AzureAppConfiguration.Tests.csproj b/tests/Testcontainers.AzureAppConfiguration.Tests/Testcontainers.AzureAppConfiguration.Tests.csproj new file mode 100644 index 000000000..a98b50ade --- /dev/null +++ b/tests/Testcontainers.AzureAppConfiguration.Tests/Testcontainers.AzureAppConfiguration.Tests.csproj @@ -0,0 +1,18 @@ + + + net8.0 + false + false + + + + + + + + + + + + + diff --git a/tests/Testcontainers.AzureAppConfiguration.Tests/Usings.cs b/tests/Testcontainers.AzureAppConfiguration.Tests/Usings.cs new file mode 100644 index 000000000..9366fb9a1 --- /dev/null +++ b/tests/Testcontainers.AzureAppConfiguration.Tests/Usings.cs @@ -0,0 +1,4 @@ +global using System.Threading.Tasks; +global using Azure.Data.AppConfiguration; +global using DotNet.Testcontainers.Commons; +global using Xunit; \ No newline at end of file From 4091134bf871c2ca4488e42a4d7d22e3bdf4eea8 Mon Sep 17 00:00:00 2001 From: Thomas Clark Date: Thu, 13 Jun 2024 19:30:43 +0100 Subject: [PATCH 2/2] build: add third-party client dependency --- Directory.Packages.props | 1 + .../Testcontainers.AzureAppConfiguration.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 79222b5b3..62c4b4cb1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -28,6 +28,7 @@ + diff --git a/tests/Testcontainers.AzureAppConfiguration.Tests/Testcontainers.AzureAppConfiguration.Tests.csproj b/tests/Testcontainers.AzureAppConfiguration.Tests/Testcontainers.AzureAppConfiguration.Tests.csproj index a98b50ade..f3d1dfff6 100644 --- a/tests/Testcontainers.AzureAppConfiguration.Tests/Testcontainers.AzureAppConfiguration.Tests.csproj +++ b/tests/Testcontainers.AzureAppConfiguration.Tests/Testcontainers.AzureAppConfiguration.Tests.csproj @@ -9,7 +9,7 @@ - +