From 2a19b4491f2928538af46a722715d139abc758ab Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 15 Dec 2024 11:31:25 +0100 Subject: [PATCH] WireMock.Net.Testcontainers: implement watching the static mapping folder for changes (#1189) * WireMock.Net.Testcontainers: implement watching the static mapping files + folder for changes * ReloadStaticMappings * fix * . * . * . * . * . * . * . * CopyAsync * 1.6.7-preview-02 * 1.6.7-preview-03 --- .github/workflows/ci.yml | 28 +++-- WireMock.Net Solution.sln | 2 +- azure-pipelines-ci.yml | 35 ++++-- examples-Aspire/AspireApp1.AppHost/Program.cs | 13 +- .../WeatherForecastApiMock.cs | 4 +- .../Program.cs | 100 ++++++++++++++-- .../ResourceLoggerServiceExtensions.cs | 14 +++ .../WireMock.Net.Aspire.csproj | 4 + .../WireMockServerArguments.cs | 6 +- .../WireMockServerBuilderExtensions.cs | 19 ++- .../WireMockServerLifecycleHook.cs | 35 ++---- .../WireMockServerResource.cs | 89 ++++++++++++++ .../Extensions/WireMockAdminApiExtensions.cs | 2 +- .../IWireMockAdminApi.cs | 7 ++ .../Utils/ContainerInfoProvider.cs | 16 +++ .../Utils/ContainerUtils.cs | 25 ++++ .../WireMock.Net.Testcontainers.csproj | 1 + .../WireMockConfiguration.cs | 21 +++- .../WireMockContainer.cs | 111 +++++++++++++++++- .../WireMockContainerBuilder.cs | 36 ++---- .../Server/WireMockServer.Admin.cs | 22 +++- .../WireMockServerArgumentsTests.cs | 6 +- .../WireMockServerBuilderExtensionsTests.cs | 2 +- .../AdminApi/WireMockAdminApiTests.cs | 16 ++- .../WireMockServer.Proxy.cs | 4 +- .../WireMockServer.Settings.cs | 10 +- 26 files changed, 509 insertions(+), 119 deletions(-) create mode 100644 src/WireMock.Net.Aspire/Extensions/ResourceLoggerServiceExtensions.cs create mode 100644 src/WireMock.Net.Testcontainers/Utils/ContainerInfoProvider.cs create mode 100644 src/WireMock.Net.Testcontainers/Utils/ContainerUtils.cs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f6695aa..cfccb7ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,11 +19,14 @@ jobs: steps: - uses: actions/checkout@v4 - - name: 'Execute Tests' - run: | - dotnet test './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' -c Release --framework net8.0 - dotnet test './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' -c Release --framework net8.0 - dotnet test './test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj' -c Release --framework net8.0 + - name: 'WireMock.Net.Tests' + run: dotnet test './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' -c Release --framework net8.0 + + - name: 'WireMock.Net.TUnitTests' + run: dotnet test './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' -c Release --framework net8.0 + + - name: 'WireMock.Net.Middleware.Tests' + run: dotnet test './test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj' -c Release --framework net8.0 linux-build-and-run: name: Run Tests on Linux @@ -35,14 +38,17 @@ jobs: steps: - uses: actions/checkout@v4 - - name: 'Execute Tests' - run: | - dotnet test './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' -c Release --framework net8.0 - dotnet test './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' -c Release --framework net8.0 - dotnet test './test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj' -c Release --framework net8.0 + - name: 'WireMock.Net.Tests' + run: dotnet test './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' -c Release --framework net8.0 + + - name: 'WireMock.Net.TUnitTests' + run: dotnet test './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' -c Release --framework net8.0 + + - name: 'WireMock.Net.Middleware.Tests' + run: dotnet test './test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj' -c Release --framework net8.0 - name: Install .NET Aspire workload run: dotnet workload install aspire - - name: 'Execute .NET Aspire Tests' + - name: 'WireMock.Net.Aspire.Tests' run: dotnet test './test/WireMock.Net.Aspire.Tests/WireMock.Net.Aspire.Tests.csproj' -c Release \ No newline at end of file diff --git a/WireMock.Net Solution.sln b/WireMock.Net Solution.sln index 168771e8..84e54212 100644 --- a/WireMock.Net Solution.sln +++ b/WireMock.Net Solution.sln @@ -138,7 +138,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.TUnit", "src\W EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.TUnitTests", "test\WireMock.Net.TUnitTests\WireMock.Net.TUnitTests.csproj", "{4CD237F7-B616-46B8-872F-E49B4BBB3EAE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.WebApplication", "examples\WireMock.Net.WebApplication\WireMock.Net.WebApplication.csproj", "{E72ADFAB-4B42-439E-B1EE-C06E504B35D2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.WebApplication", "examples\WireMock.Net.WebApplication\WireMock.Net.WebApplication.csproj", "{E72ADFAB-4B42-439E-B1EE-C06E504B35D2}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.AspNetCore.Middleware", "src\WireMock.Net.AspNetCore.Middleware\WireMock.Net.AspNetCore.Middleware.csproj", "{B6269AAC-170A-4346-8B9A-579DED3D9A13}" EndProject diff --git a/azure-pipelines-ci.yml b/azure-pipelines-ci.yml index 498f6bba..1039d8fd 100644 --- a/azure-pipelines-ci.yml +++ b/azure-pipelines-ci.yml @@ -53,15 +53,25 @@ jobs: inputs: script: | dotnet-coverage collect "dotnet test ./test/WireMock.Net.Tests/WireMock.Net.Tests.csproj --configuration Debug --no-build --framework net8.0" -f xml -o "wiremock-coverage-xunit.xml" + displayName: 'WireMock.Net.Tests with Coverage' + + - task: CmdLine@2 + inputs: + script: | dotnet-coverage collect "dotnet test ./test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj --configuration Debug --no-build --framework net8.0" -f xml -o "wiremock-coverage-tunit.xml" + displayName: 'WireMock.Net.TUnitTests with Coverage' + + - task: CmdLine@2 + inputs: + script: | dotnet-coverage collect "dotnet test ./test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj --configuration Debug --no-build --framework net8.0" -f xml -o "wiremock-coverage-middleware.xml" - displayName: 'Execute WireMock.Net.Tests with Coverage' + displayName: 'WireMock.Net.Middleware.Tests with Coverage' - task: CmdLine@2 inputs: script: | dotnet-coverage collect "dotnet test ./test/WireMock.Net.Aspire.Tests/WireMock.Net.Aspire.Tests.csproj --configuration Debug --no-build" -f xml -o "wiremock-coverage-aspire.xml" - displayName: 'Execute WireMock.Net.Aspire.Tests with Coverage' + displayName: 'WireMock.Net.Aspire.Tests with Coverage' - task: CmdLine@2 displayName: 'Merge coverage files' @@ -105,18 +115,25 @@ jobs: version: '8.0.x' - task: DotNetCoreCLI@2 - displayName: 'Build Unit tests' + displayName: 'WireMock.Net.Tests with Coverage' inputs: - command: 'build' + command: 'test' projects: './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' - arguments: '--configuration Debug --framework net8.0' - + arguments: '--configuration Debug --framework net8.0 --collect:"XPlat Code Coverage" --logger trx' + - task: DotNetCoreCLI@2 - displayName: 'Execute Unit Tests with Coverage' + displayName: 'WireMock.Net.TUnitTests with Coverage' inputs: command: 'test' - projects: './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' - arguments: '--no-build --configuration Debug --framework net8.0 --collect:"XPlat Code Coverage" --logger trx' + projects: './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' + arguments: '--configuration Debug --framework net8.0 --collect:"XPlat Code Coverage" --logger trx' + + - task: DotNetCoreCLI@2 + displayName: 'WireMock.Net.Middleware.Tests with Coverage' + inputs: + command: 'test' + projects: './test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj' + arguments: '--configuration Debug --framework net8.0 --collect:"XPlat Code Coverage" --logger trx' - job: Windows_Release_to_MyGet dependsOn: Windows_Build_Test diff --git a/examples-Aspire/AspireApp1.AppHost/Program.cs b/examples-Aspire/AspireApp1.AppHost/Program.cs index 700f01ac..854c7e06 100644 --- a/examples-Aspire/AspireApp1.AppHost/Program.cs +++ b/examples-Aspire/AspireApp1.AppHost/Program.cs @@ -2,15 +2,16 @@ var builder = DistributedApplication.CreateBuilder(args); -IResourceBuilder apiService = builder.AddProject("apiservice"); +// IResourceBuilder apiService = builder.AddProject("apiservice"); var mappingsPath = Path.Combine(Directory.GetCurrentDirectory(), "WireMockMappings"); -//IResourceBuilder apiService = builder -// .AddWireMock("apiservice", WireMockServerArguments.DefaultPort) -// .WithMappingsPath(mappingsPath) -// .WithReadStaticMappings() -// .WithApiMappingBuilder(WeatherForecastApiMock.BuildAsync); +IResourceBuilder apiService = builder + .AddWireMock("apiservice", WireMockServerArguments.DefaultPort) + .WithMappingsPath(mappingsPath) + .WithReadStaticMappings() + .WithWatchStaticMappings() + .WithApiMappingBuilder(WeatherForecastApiMock.BuildAsync); //var apiServiceUsedForDocs = builder // .AddWireMock("apiservice1", WireMockServerArguments.DefaultPort) diff --git a/examples-Aspire/AspireApp1.AppHost/WeatherForecastApiMock.cs b/examples-Aspire/AspireApp1.AppHost/WeatherForecastApiMock.cs index 056ccf3a..a6a4895b 100644 --- a/examples-Aspire/AspireApp1.AppHost/WeatherForecastApiMock.cs +++ b/examples-Aspire/AspireApp1.AppHost/WeatherForecastApiMock.cs @@ -4,7 +4,7 @@ namespace AspireApp1.AppHost; internal class WeatherForecastApiMock { - public static async Task BuildAsync(AdminApiMappingBuilder builder) + public static async Task BuildAsync(AdminApiMappingBuilder builder, CancellationToken cancellationToken) { var summaries = new[] { @@ -29,7 +29,7 @@ public static async Task BuildAsync(AdminApiMappingBuilder builder) ) ); - await builder.BuildAndPostAsync(); + await builder.BuildAndPostAsync(cancellationToken); } } diff --git a/examples/WireMock.Net.TestcontainersExample/Program.cs b/examples/WireMock.Net.TestcontainersExample/Program.cs index 5dfa9315..b6ddb0c3 100644 --- a/examples/WireMock.Net.TestcontainersExample/Program.cs +++ b/examples/WireMock.Net.TestcontainersExample/Program.cs @@ -1,5 +1,6 @@ // Copyright © WireMock.Net +using DotNet.Testcontainers.Configurations; using System.Runtime.InteropServices; using Newtonsoft.Json; using WireMock.Net.Testcontainers; @@ -12,6 +13,32 @@ private static async Task Main(string[] args) { var original = Console.ForegroundColor; + try + { + Console.ForegroundColor = ConsoleColor.DarkGreen; + Console.WriteLine("Copy"); + await TestCopyAsync(); + } + catch (Exception e) + { + Console.WriteLine(e); + } + finally + { + Console.ForegroundColor = original; + } + + try + { + Console.ForegroundColor = ConsoleColor.DarkRed; + Console.WriteLine("Automatic"); + await TestAsync(); + } + finally + { + Console.ForegroundColor = original; + } + try { Console.ForegroundColor = ConsoleColor.Yellow; @@ -91,17 +118,44 @@ private static async Task Main(string[] args) { Console.ForegroundColor = original; } + } - try - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("Automatic"); - await TestAsync(); - } - finally + private static async Task TestCopyAsync() + { + var builder = new WireMockContainerBuilder() + .WithWatchStaticMappings(true) + .WithAutoRemove(true) + .WithCleanUp(true); + + var container = builder.Build(); + + await container.StartAsync(); + + if (await GetImageOSAsync.Value == OSPlatform.Linux) { - Console.ForegroundColor = original; + try + { + await container.CopyAsync(@"C:\temp-wiremock\__admin\mappings\StefBodyAsFileExample.json", "/app/__admin/mappings"); + } + catch (Exception e) + { + Console.WriteLine(e); + } } + + Console.WriteLine("PublicUrl = " + container.GetPublicUrl()); + + var adminClient = container.CreateWireMockAdminClient(); + + var mappings = await adminClient.GetMappingsAsync(); + Console.WriteLine("mappings = " + JsonConvert.SerializeObject(mappings, Formatting.Indented)); + + await Task.Delay(1_000); + + //Console.WriteLine("Press any key to stop."); + //Console.ReadKey(); + + await container.StopAsync(); } private static async Task TestAsync(string? image = null) @@ -129,10 +183,12 @@ private static async Task TestAsync(string? image = null) await container.StartAsync(); - await Task.Delay(1_000); + await container.ReloadStaticMappingsAsync(); - var logs = await container.GetLogsAsync(DateTime.Now.AddDays(-1)); - Console.WriteLine("logs = " + logs.Stdout); + //var logs = await container.GetLogsAsync(DateTime.Now.AddDays(-1)); + //Console.WriteLine("logs = " + logs.Stdout); + + Console.WriteLine("PublicUrl = " + container.GetPublicUrl()); var restEaseApiClient = container.CreateWireMockAdminClient(); @@ -140,12 +196,32 @@ private static async Task TestAsync(string? image = null) Console.WriteLine("settings = " + JsonConvert.SerializeObject(settings, Formatting.Indented)); var mappings = await restEaseApiClient.GetMappingsAsync(); - Console.WriteLine("mappings = " + JsonConvert.SerializeObject(mappings, Formatting.Indented)); + Console.WriteLine("mappingsStef = " + JsonConvert.SerializeObject(mappings, Formatting.Indented)); var client = container.CreateClient(); var result = await client.GetStringAsync("/static/mapping"); Console.WriteLine("result = " + result); + //if (image == null) + //{ + // Console.WriteLine("Press any key to stop."); + // Console.ReadKey(); + //} + await container.StopAsync(); } + + private static Lazy> GetImageOSAsync = new(async () => + { + if (TestcontainersSettings.OS.DockerEndpointAuthConfig == null) + { + throw new InvalidOperationException($"The {nameof(TestcontainersSettings.OS.DockerEndpointAuthConfig)} is null. Check if Docker is started."); + } + + using var dockerClientConfig = TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientConfiguration(); + using var dockerClient = dockerClientConfig.CreateClient(); + + var version = await dockerClient.System.GetVersionAsync(); + return version.Os.IndexOf("Windows", StringComparison.OrdinalIgnoreCase) >= 0 ? OSPlatform.Windows : OSPlatform.Linux; + }); } \ No newline at end of file diff --git a/src/WireMock.Net.Aspire/Extensions/ResourceLoggerServiceExtensions.cs b/src/WireMock.Net.Aspire/Extensions/ResourceLoggerServiceExtensions.cs new file mode 100644 index 00000000..cce4798c --- /dev/null +++ b/src/WireMock.Net.Aspire/Extensions/ResourceLoggerServiceExtensions.cs @@ -0,0 +1,14 @@ +// Copyright © WireMock.Net + +using Aspire.Hosting.ApplicationModel; + +namespace WireMock.Net.Aspire.Extensions; + +internal static class ResourceLoggerServiceExtensions +{ + public static void SetLogger(this ResourceLoggerService resourceLoggerService, WireMockServerResource wireMockServerResource) + { + var logger = resourceLoggerService.GetLogger(wireMockServerResource); + wireMockServerResource.SetLogger(logger); + } +} \ No newline at end of file diff --git a/src/WireMock.Net.Aspire/WireMock.Net.Aspire.csproj b/src/WireMock.Net.Aspire/WireMock.Net.Aspire.csproj index 40d6cc81..853ab0a5 100644 --- a/src/WireMock.Net.Aspire/WireMock.Net.Aspire.csproj +++ b/src/WireMock.Net.Aspire/WireMock.Net.Aspire.csproj @@ -30,6 +30,10 @@ + + + + true diff --git a/src/WireMock.Net.Aspire/WireMockServerArguments.cs b/src/WireMock.Net.Aspire/WireMockServerArguments.cs index 2e41790a..8c059c1a 100644 --- a/src/WireMock.Net.Aspire/WireMockServerArguments.cs +++ b/src/WireMock.Net.Aspire/WireMockServerArguments.cs @@ -50,7 +50,7 @@ public class WireMockServerArguments /// /// Default value is false. /// - public bool WithWatchStaticMappings { get; set; } + public bool WatchStaticMappings { get; set; } /// /// Specifies the path for the (static) mapping json files. @@ -65,7 +65,7 @@ public class WireMockServerArguments /// /// Optional delegate that will be invoked to configure the WireMock.Net resource using the . /// - public Func? ApiMappingBuilder { get; set; } + public Func? ApiMappingBuilder { get; set; } /// /// Converts the current instance's properties to an array of command-line arguments for starting the WireMock.Net server. @@ -88,7 +88,7 @@ public string[] GetArgs() Add(args, "--ReadStaticMappings", "true"); } - if (WithWatchStaticMappings) + if (WatchStaticMappings) { Add(args, "--ReadStaticMappings", "true"); Add(args, "--WatchStaticMappings", "true"); diff --git a/src/WireMock.Net.Aspire/WireMockServerBuilderExtensions.cs b/src/WireMock.Net.Aspire/WireMockServerBuilderExtensions.cs index 6925463b..c5e85b32 100644 --- a/src/WireMock.Net.Aspire/WireMockServerBuilderExtensions.cs +++ b/src/WireMock.Net.Aspire/WireMockServerBuilderExtensions.cs @@ -112,7 +112,7 @@ public static IResourceBuilder WithReadStaticMappings(th /// A reference to the . public static IResourceBuilder WithWatchStaticMappings(this IResourceBuilder wiremock) { - Guard.NotNull(wiremock).Resource.Arguments.WithWatchStaticMappings = true; + Guard.NotNull(wiremock).Resource.Arguments.WatchStaticMappings = true; return wiremock; } @@ -124,8 +124,10 @@ public static IResourceBuilder WithWatchStaticMappings(t /// A reference to the . public static IResourceBuilder WithMappingsPath(this IResourceBuilder wiremock, string mappingsPath) { - return Guard.NotNull(wiremock) - .WithBindMount(Guard.NotNullOrWhiteSpace(mappingsPath), DefaultLinuxMappingsPath); + Guard.NotNullOrWhiteSpace(mappingsPath); + Guard.NotNull(wiremock).Resource.Arguments.MappingsPath = mappingsPath; + + return wiremock.WithBindMount(mappingsPath, DefaultLinuxMappingsPath); } /// @@ -151,6 +153,17 @@ public static IResourceBuilder WithAdminUserNameAndPassw /// Delegate that will be invoked to configure the WireMock.Net resource. /// public static IResourceBuilder WithApiMappingBuilder(this IResourceBuilder wiremock, Func configure) + { + return wiremock.WithApiMappingBuilder((adminApiMappingBuilder, _) => configure.Invoke(adminApiMappingBuilder)); + } + + /// + /// Use WireMock Client's AdminApiMappingBuilder to configure the WireMock.Net resource. + /// + /// The . + /// Delegate that will be invoked to configure the WireMock.Net resource. + /// + public static IResourceBuilder WithApiMappingBuilder(this IResourceBuilder wiremock, Func configure) { Guard.NotNull(wiremock); diff --git a/src/WireMock.Net.Aspire/WireMockServerLifecycleHook.cs b/src/WireMock.Net.Aspire/WireMockServerLifecycleHook.cs index 6c8049ae..d97a4113 100644 --- a/src/WireMock.Net.Aspire/WireMockServerLifecycleHook.cs +++ b/src/WireMock.Net.Aspire/WireMockServerLifecycleHook.cs @@ -3,50 +3,39 @@ using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Lifecycle; using Microsoft.Extensions.Logging; -using RestEase; -using WireMock.Client; -using WireMock.Client.Extensions; namespace WireMock.Net.Aspire; -internal class WireMockServerLifecycleHook(ResourceLoggerService loggerService) : IDistributedApplicationLifecycleHook +internal class WireMockServerLifecycleHook(ILoggerFactory loggerFactory) : IDistributedApplicationLifecycleHook, IAsyncDisposable { + private readonly CancellationTokenSource _shutdownCts = new(); + public async Task AfterResourcesCreatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default) { + var cts = CancellationTokenSource.CreateLinkedTokenSource(_shutdownCts.Token, cancellationToken); + var wireMockServerResources = appModel.Resources .OfType() - .Where(resource => resource.Arguments.ApiMappingBuilder is not null) .ToArray(); - if (wireMockServerResources.Length == 0) - { - return; - } - foreach (var wireMockServerResource in wireMockServerResources) { + wireMockServerResource.SetLogger(loggerFactory.CreateLogger()); + var endpoint = wireMockServerResource.GetEndpoint(); if (endpoint.IsAllocated) { - var adminApi = CreateWireMockAdminApi(wireMockServerResource); - - var logger = loggerService.GetLogger(wireMockServerResource); - logger.LogInformation("Checking Health status from WireMock.Net"); + await wireMockServerResource.WaitForHealthAsync(cts.Token); - await adminApi.WaitForHealthAsync(cancellationToken: cancellationToken); + await wireMockServerResource.CallApiMappingBuilderActionAsync(cts.Token); - logger.LogInformation("Calling ApiMappingBuilder to add mappings to WireMock.Net"); - var mappingBuilder = adminApi.GetMappingBuilder(); - await wireMockServerResource.Arguments.ApiMappingBuilder!.Invoke(mappingBuilder); + wireMockServerResource.StartWatchingStaticMappings(cts.Token); } } } - private static IWireMockAdminApi CreateWireMockAdminApi(WireMockServerResource resource) + public async ValueTask DisposeAsync() { - var adminApi = RestClient.For(resource.GetEndpoint().Url); - return resource.Arguments.HasBasicAuthentication ? - adminApi.WithAuthorization(resource.Arguments.AdminUsername!, resource.Arguments.AdminPassword!) : - adminApi; + await _shutdownCts.CancelAsync(); } } \ No newline at end of file diff --git a/src/WireMock.Net.Aspire/WireMockServerResource.cs b/src/WireMock.Net.Aspire/WireMockServerResource.cs index b6da6600..e2eac519 100644 --- a/src/WireMock.Net.Aspire/WireMockServerResource.cs +++ b/src/WireMock.Net.Aspire/WireMockServerResource.cs @@ -1,6 +1,11 @@ // Copyright © WireMock.Net +using Microsoft.Extensions.Logging; +using RestEase; using Stef.Validation; +using WireMock.Client; +using WireMock.Client.Extensions; +using WireMock.Util; // ReSharper disable once CheckNamespace namespace Aspire.Hosting.ApplicationModel; @@ -10,7 +15,13 @@ namespace Aspire.Hosting.ApplicationModel; /// public class WireMockServerResource : ContainerResource, IResourceWithServiceDiscovery { + private const int EnhancedFileSystemWatcherTimeoutMs = 2000; + internal WireMockServerArguments Arguments { get; } + internal Lazy AdminApi => new(CreateWireMockAdminApi); + + private ILogger? _logger; + private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher; /// /// Initializes a new instance of the class. @@ -30,4 +41,82 @@ public EndpointReference GetEndpoint() { return new EndpointReference(this, "http"); } + + internal void SetLogger(ILogger logger) + { + _logger = logger; + } + + internal async Task WaitForHealthAsync(CancellationToken cancellationToken) + { + _logger?.LogInformation("Checking Health status from WireMock.Net"); + await AdminApi.Value.WaitForHealthAsync(cancellationToken: cancellationToken); + } + + internal async Task CallApiMappingBuilderActionAsync(CancellationToken cancellationToken) + { + if (Arguments.ApiMappingBuilder == null) + { + return; + } + + _logger?.LogInformation("Calling ApiMappingBuilder to add mappings to WireMock.Net"); + + var mappingBuilder = AdminApi.Value.GetMappingBuilder(); + await Arguments.ApiMappingBuilder.Invoke(mappingBuilder, cancellationToken); + } + + internal void StartWatchingStaticMappings(CancellationToken cancellationToken) + { + if (!Arguments.WatchStaticMappings || string.IsNullOrEmpty(Arguments.MappingsPath)) + { + return; + } + + cancellationToken.Register(() => + { + if (_enhancedFileSystemWatcher != null) + { + _enhancedFileSystemWatcher.EnableRaisingEvents = false; + _enhancedFileSystemWatcher.Created -= FileCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Changed -= FileCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Deleted -= FileCreatedChangedOrDeleted; + + _enhancedFileSystemWatcher.Dispose(); + _enhancedFileSystemWatcher = null; + } + }); + + _logger?.LogInformation("Starting to watch static mappings on path: '{Path}'. ", Arguments.MappingsPath); + + _enhancedFileSystemWatcher = new EnhancedFileSystemWatcher(Arguments.MappingsPath, "*.json", EnhancedFileSystemWatcherTimeoutMs) + { + IncludeSubdirectories = true + }; + _enhancedFileSystemWatcher.Created += FileCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Changed += FileCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Deleted += FileCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.EnableRaisingEvents = true; + } + + private IWireMockAdminApi CreateWireMockAdminApi() + { + var adminApi = RestClient.For(GetEndpoint().Url); + return Arguments.HasBasicAuthentication ? + adminApi.WithAuthorization(Arguments.AdminUsername!, Arguments.AdminPassword!) : + adminApi; + } + + private async void FileCreatedChangedOrDeleted(object sender, FileSystemEventArgs args) + { + _logger?.LogInformation("MappingFile created, changed or deleted: '{0}'. Triggering ReloadStaticMappings.", args.FullPath); + try + { + await AdminApi.Value.ReloadStaticMappingsAsync(); + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "Error calling /__admin/mappings/reloadStaticMappings"); + } + } } \ No newline at end of file diff --git a/src/WireMock.Net.RestClient/Extensions/WireMockAdminApiExtensions.cs b/src/WireMock.Net.RestClient/Extensions/WireMockAdminApiExtensions.cs index 024011f4..f5d9cc7a 100644 --- a/src/WireMock.Net.RestClient/Extensions/WireMockAdminApiExtensions.cs +++ b/src/WireMock.Net.RestClient/Extensions/WireMockAdminApiExtensions.cs @@ -72,7 +72,7 @@ public static async Task WaitForHealthAsync(this IWireMockAdminApi adminApi, int if (retries >= MaxRetries) { - throw new InvalidOperationException($"The /__admin/health endpoint did not return 'Healthy' after {MaxRetries} retries and {totalWaitTime / 1000.0:0.0} seconds."); + throw new InvalidOperationException($"The /__admin/health endpoint did not return '{HealthStatusHealthy}' after {MaxRetries} retries and {totalWaitTime / 1000.0:0.0} seconds."); } } diff --git a/src/WireMock.Net.RestClient/IWireMockAdminApi.cs b/src/WireMock.Net.RestClient/IWireMockAdminApi.cs index 17063f00..53bedebb 100644 --- a/src/WireMock.Net.RestClient/IWireMockAdminApi.cs +++ b/src/WireMock.Net.RestClient/IWireMockAdminApi.cs @@ -122,6 +122,13 @@ public interface IWireMockAdminApi [Post("mappings/reset")] Task ResetMappingsAsync(bool? reloadStaticMappings = false, CancellationToken cancellationToken = default); + /// + /// Reload the static mappings. + /// + /// The optional cancellationToken. + [Post("mappings/reloadStaticMappings")] + Task ReloadStaticMappingsAsync(CancellationToken cancellationToken = default); + /// /// Get a mapping based on the guid /// diff --git a/src/WireMock.Net.Testcontainers/Utils/ContainerInfoProvider.cs b/src/WireMock.Net.Testcontainers/Utils/ContainerInfoProvider.cs new file mode 100644 index 00000000..bd470239 --- /dev/null +++ b/src/WireMock.Net.Testcontainers/Utils/ContainerInfoProvider.cs @@ -0,0 +1,16 @@ +// Copyright © WireMock.Net + +using System.Collections.Generic; +using System.Runtime.InteropServices; +using WireMock.Net.Testcontainers.Models; + +namespace WireMock.Net.Testcontainers.Utils; + +internal static class ContainerInfoProvider +{ + public static readonly Dictionary Info = new() + { + { OSPlatform.Linux, new ContainerInfo("sheyenrath/wiremock.net-alpine", "/app/__admin/mappings") }, + { OSPlatform.Windows, new ContainerInfo("sheyenrath/wiremock.net-windows", @"c:\app\__admin\mappings") } + }; +} \ No newline at end of file diff --git a/src/WireMock.Net.Testcontainers/Utils/ContainerUtils.cs b/src/WireMock.Net.Testcontainers/Utils/ContainerUtils.cs new file mode 100644 index 00000000..ce0d8113 --- /dev/null +++ b/src/WireMock.Net.Testcontainers/Utils/ContainerUtils.cs @@ -0,0 +1,25 @@ +// Copyright © WireMock.Net + +using System; +using System.Runtime.InteropServices; +using DotNet.Testcontainers.Configurations; +using System.Threading.Tasks; + +namespace WireMock.Net.Testcontainers.Utils; + +internal static class ContainerUtils +{ + public static Lazy> GetImageOSAsync = new(async () => + { + if (TestcontainersSettings.OS.DockerEndpointAuthConfig == null) + { + throw new InvalidOperationException($"The {nameof(TestcontainersSettings.OS.DockerEndpointAuthConfig)} is null. Check if Docker is started."); + } + + using var dockerClientConfig = TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientConfiguration(); + using var dockerClient = dockerClientConfig.CreateClient(); + + var version = await dockerClient.System.GetVersionAsync(); + return version.Os.IndexOf("Windows", StringComparison.OrdinalIgnoreCase) >= 0 ? OSPlatform.Windows : OSPlatform.Linux; + }); +} \ No newline at end of file diff --git a/src/WireMock.Net.Testcontainers/WireMock.Net.Testcontainers.csproj b/src/WireMock.Net.Testcontainers/WireMock.Net.Testcontainers.csproj index 42aacad7..5154a8e0 100644 --- a/src/WireMock.Net.Testcontainers/WireMock.Net.Testcontainers.csproj +++ b/src/WireMock.Net.Testcontainers/WireMock.Net.Testcontainers.csproj @@ -20,6 +20,7 @@ + diff --git a/src/WireMock.Net.Testcontainers/WireMockConfiguration.cs b/src/WireMock.Net.Testcontainers/WireMockConfiguration.cs index 8004f0f6..17fdc0f2 100644 --- a/src/WireMock.Net.Testcontainers/WireMockConfiguration.cs +++ b/src/WireMock.Net.Testcontainers/WireMockConfiguration.cs @@ -1,6 +1,5 @@ // Copyright © WireMock.Net -using System; using Docker.DotNet.Models; using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Configurations; @@ -19,6 +18,10 @@ public sealed class WireMockConfiguration : ContainerConfiguration public string? StaticMappingsPath { get; private set; } + public bool WatchStaticMappings { get; private set; } + + public bool WatchStaticMappingsInSubdirectories { get; private set; } + public bool HasBasicAuthentication => !string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password); public WireMockConfiguration(string? username = null, string? password = null) @@ -65,16 +68,30 @@ public WireMockConfiguration(WireMockConfiguration oldValue, WireMockConfigurati Username = BuildConfiguration.Combine(oldValue.Username, newValue.Username); Password = BuildConfiguration.Combine(oldValue.Password, newValue.Password); StaticMappingsPath = BuildConfiguration.Combine(oldValue.StaticMappingsPath, newValue.StaticMappingsPath); + WatchStaticMappings = BuildConfiguration.Combine(oldValue.WatchStaticMappings, newValue.WatchStaticMappings); + WatchStaticMappingsInSubdirectories = BuildConfiguration.Combine(oldValue.WatchStaticMappingsInSubdirectories, newValue.WatchStaticMappingsInSubdirectories); } /// /// Set the StaticMappingsPath. /// /// The path which contains the StaticMappings. - /// + /// public WireMockConfiguration WithStaticMappingsPath(string path) { StaticMappingsPath = path; return this; } + + /// + /// Watch the static mappings. + /// + /// Also look in SubDirectories. + /// + public WireMockConfiguration WithWatchStaticMappings(bool includeSubDirectories) + { + WatchStaticMappings = true; + WatchStaticMappingsInSubdirectories = includeSubDirectories; + return this; + } } \ No newline at end of file diff --git a/src/WireMock.Net.Testcontainers/WireMockContainer.cs b/src/WireMock.Net.Testcontainers/WireMockContainer.cs index c78aa181..35394dce 100644 --- a/src/WireMock.Net.Testcontainers/WireMockContainer.cs +++ b/src/WireMock.Net.Testcontainers/WireMockContainer.cs @@ -1,14 +1,20 @@ // Copyright © WireMock.Net using System; +using System.IO; using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using DotNet.Testcontainers.Configurations; using DotNet.Testcontainers.Containers; using JetBrains.Annotations; +using Microsoft.Extensions.Logging; using RestEase; -using Stef.Validation; using WireMock.Client; using WireMock.Client.Extensions; using WireMock.Http; +using WireMock.Net.Testcontainers.Utils; +using WireMock.Util; namespace WireMock.Net.Testcontainers; @@ -17,17 +23,23 @@ namespace WireMock.Net.Testcontainers; /// public sealed class WireMockContainer : DockerContainer { + private const int EnhancedFileSystemWatcherTimeoutMs = 2000; internal const int ContainerPort = 80; private readonly WireMockConfiguration _configuration; + private IWireMockAdminApi? _adminApi; + private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher; + /// /// Initializes a new instance of the class. /// /// The container configuration. public WireMockContainer(WireMockConfiguration configuration) : base(configuration) { - _configuration = Guard.NotNull(configuration); + _configuration = Stef.Validation.Guard.NotNull(configuration); + + Started += WireMockContainer_Started; } /// @@ -92,6 +104,71 @@ public HttpClient CreateClient(HttpMessageHandler innerHandler, params Delegatin return client; } + /// + /// Copies a test host directory or file to the container and triggers a reload of the static mappings if required. + /// + /// The source directory or file to be copied. + /// The target directory path to copy the files to. + /// The POSIX file mode permission. + /// Cancellation token. + /// A task that completes when the directory or file has been copied. + public new async Task CopyAsync(string source, string target, UnixFileModes fileMode = Unix.FileMode644, CancellationToken ct = default) + { + await base.CopyAsync(source, target, fileMode, ct); + + if (_configuration.WatchStaticMappings && await PathStartsWithContainerMappingsPath(target)) + { + await ReloadStaticMappingsAsync(target, ct); + } + } + + /// + /// Reload the static mappings. + /// + /// The optional cancellationToken. + public async Task ReloadStaticMappingsAsync(CancellationToken cancellationToken = default) + { + if (_adminApi == null) + { + return; + } + + try + { + await _adminApi.ReloadStaticMappingsAsync(cancellationToken); + } + catch (Exception ex) + { + Logger.LogWarning(ex, "Error calling /__admin/mappings/reloadStaticMappings"); + } + } + + /// + protected override ValueTask DisposeAsyncCore() + { + if (_enhancedFileSystemWatcher != null) + { + _enhancedFileSystemWatcher.EnableRaisingEvents = false; + _enhancedFileSystemWatcher.Created -= FileCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Changed -= FileCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Deleted -= FileCreatedChangedOrDeleted; + + _enhancedFileSystemWatcher.Dispose(); + _enhancedFileSystemWatcher = null; + } + + Started -= WireMockContainer_Started; + + return base.DisposeAsyncCore(); + } + + private static async Task PathStartsWithContainerMappingsPath(string value) + { + var imageOs = await ContainerUtils.GetImageOSAsync.Value; + + return value.StartsWith(ContainerInfoProvider.Info[imageOs].MappingsPath); + } + private void ValidateIfRunning() { if (State != TestcontainersStates.Running) @@ -100,5 +177,35 @@ private void ValidateIfRunning() } } + private void WireMockContainer_Started(object sender, EventArgs e) + { + _adminApi = CreateWireMockAdminClient(); + + if (!_configuration.WatchStaticMappings || string.IsNullOrEmpty(_configuration.StaticMappingsPath)) + { + return; + } + + _enhancedFileSystemWatcher = new EnhancedFileSystemWatcher(_configuration.StaticMappingsPath!, "*.json", EnhancedFileSystemWatcherTimeoutMs) + { + IncludeSubdirectories = _configuration.WatchStaticMappingsInSubdirectories + }; + _enhancedFileSystemWatcher.Created += FileCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Changed += FileCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Deleted += FileCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.EnableRaisingEvents = true; + } + + private async void FileCreatedChangedOrDeleted(object sender, FileSystemEventArgs args) + { + await ReloadStaticMappingsAsync(args.FullPath); + } + + private async Task ReloadStaticMappingsAsync(string path, CancellationToken cancellationToken = default) + { + Logger.LogInformation("MappingFile created, changed or deleted: '{Path}'. Triggering ReloadStaticMappings.", path); + await ReloadStaticMappingsAsync(cancellationToken); + } + private Uri GetPublicUri() => new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(ContainerPort)).Uri; } \ No newline at end of file diff --git a/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs b/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs index 7a843478..48c54eae 100644 --- a/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs +++ b/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs @@ -1,15 +1,13 @@ // Copyright © WireMock.Net using System; -using System.Collections.Generic; using System.Runtime.InteropServices; -using System.Threading.Tasks; using Docker.DotNet.Models; using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Configurations; using JetBrains.Annotations; using Stef.Validation; -using WireMock.Net.Testcontainers.Models; +using WireMock.Net.Testcontainers.Utils; namespace WireMock.Net.Testcontainers; @@ -19,26 +17,6 @@ namespace WireMock.Net.Testcontainers; public sealed class WireMockContainerBuilder : ContainerBuilder { private const string DefaultLogger = "WireMockConsoleLogger"; - private readonly Dictionary _info = new() - { - { OSPlatform.Linux, new ContainerInfo("sheyenrath/wiremock.net-alpine", "/app/__admin/mappings") }, - { OSPlatform.Windows, new ContainerInfo("sheyenrath/wiremock.net-windows", @"c:\app\__admin\mappings") } - }; - - private readonly Lazy> _getOSAsLazy = new(async () => - { - if (TestcontainersSettings.OS.DockerEndpointAuthConfig == null) - { - throw new InvalidOperationException($"The {nameof(TestcontainersSettings.OS.DockerEndpointAuthConfig)} is null. Check if Docker is started."); - } - - using var dockerClientConfig = TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientConfiguration(); - using var dockerClient = dockerClientConfig.CreateClient(); - - var version = await dockerClient.System.GetVersionAsync(); - return version.Os.IndexOf("Windows", StringComparison.OrdinalIgnoreCase) >= 0 ? OSPlatform.Windows : OSPlatform.Linux; - }); - private OSPlatform? _imageOS; /// @@ -58,7 +36,7 @@ public WireMockContainerBuilder() : this(new WireMockConfiguration()) [PublicAPI] public WireMockContainerBuilder WithImage() { - _imageOS ??= _getOSAsLazy.Value.GetAwaiter().GetResult(); + _imageOS ??= ContainerUtils.GetImageOSAsync.Value.GetAwaiter().GetResult(); return WithImage(_imageOS.Value); } @@ -130,7 +108,9 @@ public WireMockContainerBuilder WithReadStaticMappings() [PublicAPI] public WireMockContainerBuilder WithWatchStaticMappings(bool includeSubDirectories) { - return WithCommand("--WatchStaticMappings true").WithCommand($"--WatchStaticMappingsInSubdirectories {includeSubDirectories}"); + return Merge(DockerResourceConfiguration, DockerResourceConfiguration.WithWatchStaticMappings(includeSubDirectories)) + .WithCommand("--WatchStaticMappings true") + .WithCommand($"--WatchStaticMappingsInSubdirectories {includeSubDirectories}"); } /// @@ -181,7 +161,7 @@ public override WireMockContainer Build() if (!string.IsNullOrEmpty(builder.DockerResourceConfiguration.StaticMappingsPath)) { - builder = builder.WithBindMount(builder.DockerResourceConfiguration.StaticMappingsPath, _info[_imageOS.Value].MappingsPath); + builder = builder.WithBindMount(builder.DockerResourceConfiguration.StaticMappingsPath, ContainerInfoProvider.Info[_imageOS.Value].MappingsPath); } builder.Validate(); @@ -198,7 +178,7 @@ protected override WireMockContainerBuilder Init() return builder .WithPortBinding(WireMockContainer.ContainerPort, true) .WithCommand($"--WireMockLogger {DefaultLogger}") - .WithWaitStrategy(waitForContainerOS.UntilMessageIsLogged("By Stef Heyenrath")); + .WithWaitStrategy(waitForContainerOS.UntilMessageIsLogged("WireMock.Net server running")); } /// @@ -222,6 +202,6 @@ protected override WireMockContainerBuilder Merge(WireMockConfiguration oldValue private WireMockContainerBuilder WithImage(OSPlatform os) { _imageOS = os; - return WithImage(_info[os].Image); + return WithImage(ContainerInfoProvider.Info[os].Image); } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs index 8f39a9a7..e79ff52d 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs @@ -96,6 +96,9 @@ private void InitAdmin() // __admin/mappings/reset Given(Request.Create().WithPath(_adminPaths.Mappings + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsReset)); + // __admin/mappings/reloadStaticMappings + Given(Request.Create().WithPath(_adminPaths.Mappings + "/reloadStaticMappings").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ReloadStaticMappings)); + // __admin/mappings/{guid} Given(Request.Create().WithPath(_adminPaths.MappingsGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingGet)); Given(Request.Create().WithPath(_adminPaths.MappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut)); @@ -170,9 +173,9 @@ public void ReadStaticMappings(string? folder = null) return; } - foreach (string filename in _settings.FileSystemHandler.EnumerateFiles(folder, _settings.WatchStaticMappingsInSubdirectories == true).OrderBy(f => f)) + foreach (var filename in _settings.FileSystemHandler.EnumerateFiles(folder, _settings.WatchStaticMappingsInSubdirectories == true).OrderBy(f => f)) { - _settings.Logger.Info("Reading Static MappingFile : '{0}'", filename); + _settings.Logger.Info("Reading Static MappingFile : '{0}'.", filename); try { @@ -556,18 +559,25 @@ private IResponseMessage MappingsReset(IRequestMessage requestMessage) ResetScenarios(); - string message = "Mappings reset"; + var message = "Mappings reset"; if (requestMessage.Query != null && requestMessage.Query.ContainsKey(QueryParamReloadStaticMappings) && - bool.TryParse(requestMessage.Query[QueryParamReloadStaticMappings].ToString(), out bool reloadStaticMappings) && + bool.TryParse(requestMessage.Query[QueryParamReloadStaticMappings].ToString(), out var reloadStaticMappings) && reloadStaticMappings) { ReadStaticMappings(); - message = $"{message} and static mappings reloaded"; + message += " and static mappings reloaded"; } return ResponseMessageBuilder.Create(200, message); } + + private IResponseMessage ReloadStaticMappings(IRequestMessage _) + { + ReadStaticMappings(); + + return ResponseMessageBuilder.Create(200, "Static Mappings reloaded"); + } #endregion Mappings #region Request/{guid} @@ -780,7 +790,7 @@ private void EnhancedFileSystemWatcherChanged(object sender, FileSystemEventArgs private void EnhancedFileSystemWatcherDeleted(object sender, FileSystemEventArgs args) { _settings.Logger.Info("MappingFile deleted : '{0}'", args.FullPath); - string filenameWithoutExtension = Path.GetFileNameWithoutExtension(args.FullPath); + var filenameWithoutExtension = Path.GetFileNameWithoutExtension(args.FullPath); if (Guid.TryParse(filenameWithoutExtension, out var guidFromFilename)) { diff --git a/test/WireMock.Net.Aspire.Tests/WireMockServerArgumentsTests.cs b/test/WireMock.Net.Aspire.Tests/WireMockServerArgumentsTests.cs index 6e391be5..15a4ca3c 100644 --- a/test/WireMock.Net.Aspire.Tests/WireMockServerArgumentsTests.cs +++ b/test/WireMock.Net.Aspire.Tests/WireMockServerArgumentsTests.cs @@ -17,7 +17,7 @@ public void DefaultValues_ShouldBeSetCorrectly() args.AdminUsername.Should().BeNull(); args.AdminPassword.Should().BeNull(); args.ReadStaticMappings.Should().BeFalse(); - args.WithWatchStaticMappings.Should().BeFalse(); + args.WatchStaticMappings.Should().BeFalse(); args.MappingsPath.Should().BeNull(); } @@ -87,7 +87,7 @@ public void GetArgs_WhenWithWatchStaticMappingsIsTrue_ShouldContainWatchStaticMa // Arrange var args = new WireMockServerArguments { - WithWatchStaticMappings = true, + WatchStaticMappings = true, ReadStaticMappings = readStaticMappings }; @@ -104,7 +104,7 @@ public void GetArgs_WhenWithWatchStaticMappingsIsFalse_ShouldNotContainWatchStat // Arrange var args = new WireMockServerArguments { - WithWatchStaticMappings = false + WatchStaticMappings = false }; // Act diff --git a/test/WireMock.Net.Aspire.Tests/WireMockServerBuilderExtensionsTests.cs b/test/WireMock.Net.Aspire.Tests/WireMockServerBuilderExtensionsTests.cs index 04ee8fc1..cad18dea 100644 --- a/test/WireMock.Net.Aspire.Tests/WireMockServerBuilderExtensionsTests.cs +++ b/test/WireMock.Net.Aspire.Tests/WireMockServerBuilderExtensionsTests.cs @@ -63,7 +63,7 @@ public void AddWireMock() AdminPassword = password, AdminUsername = username, ReadStaticMappings = true, - WithWatchStaticMappings = false, + WatchStaticMappings = false, MappingsPath = null, HttpPort = port }); diff --git a/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.cs b/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.cs index 8fdb1826..8e19470d 100644 --- a/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.cs +++ b/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.cs @@ -1126,5 +1126,19 @@ public async Task IWireMockAdminApi_OpenApiSave_Yml() server.Stop(); } + + [Fact] + public async Task IWireMockAdminApi_ReadStaticMappingsAsync() + { + // Arrange + using var server = WireMockServer.StartWithAdminInterface(); + var api = RestClient.For(server.Url); + + // Act + var status = await api.ReloadStaticMappingsAsync().ConfigureAwait(false); + + // Assert + status.Status.Should().Be("Static Mappings reloaded"); + } } -#endif +#endif \ No newline at end of file diff --git a/test/WireMock.Net.Tests/WireMockServer.Proxy.cs b/test/WireMock.Net.Tests/WireMockServer.Proxy.cs index 842bb8ed..0513983a 100644 --- a/test/WireMock.Net.Tests/WireMockServer.Proxy.cs +++ b/test/WireMock.Net.Tests/WireMockServer.Proxy.cs @@ -100,7 +100,7 @@ public async Task WireMockServer_Proxy_AdminTrue_With_SaveMapping_Is_True_And_Sa Url = "http://www.google.com", SaveMapping = true, SaveMappingToFile = false, - ExcludedHeaders = new[] { "Connection" } // Needed for .NET 4.5.x and 4.6.x + ExcludedHeaders = ["Connection"] // Needed for .NET 4.5.x and 4.6.x }, StartAdminInterface = true }; @@ -119,7 +119,7 @@ public async Task WireMockServer_Proxy_AdminTrue_With_SaveMapping_Is_True_And_Sa } // Assert - server.Mappings.Should().HaveCount(36); + server.Mappings.Should().HaveCount(37); } [Fact] diff --git a/test/WireMock.Net.Tests/WireMockServer.Settings.cs b/test/WireMock.Net.Tests/WireMockServer.Settings.cs index d1d5927e..f6a8ab96 100644 --- a/test/WireMock.Net.Tests/WireMockServer.Settings.cs +++ b/test/WireMock.Net.Tests/WireMockServer.Settings.cs @@ -75,6 +75,8 @@ public void WireMockServer_WireMockServerSettings_StartAdminInterfaceFalse_Basic [Fact] public void WireMockServer_WireMockServerSettings_PriorityFromAllAdminMappingsIsLow_When_StartAdminInterface_IsTrue() { + const int count = 35; + // Assign and Act var server = WireMockServer.Start(new WireMockServerSettings { @@ -83,13 +85,15 @@ public void WireMockServer_WireMockServerSettings_PriorityFromAllAdminMappingsIs // Assert server.Mappings.Should().NotBeNull(); - server.Mappings.Should().HaveCount(34); + server.Mappings.Should().HaveCount(count); server.Mappings.All(m => m.Priority == WireMockConstants.AdminPriority).Should().BeTrue(); } [Fact] public void WireMockServer_WireMockServerSettings_ProxyAndRecordSettings_ProxyPriority_IsMinus2000000_When_StartAdminInterface_IsTrue() { + const int count = 36; + // Assign and Act var server = WireMockServer.Start(new WireMockServerSettings { @@ -102,9 +106,9 @@ public void WireMockServer_WireMockServerSettings_ProxyAndRecordSettings_ProxyPr // Assert server.Mappings.Should().NotBeNull(); - server.Mappings.Should().HaveCount(35); + server.Mappings.Should().HaveCount(count); - server.Mappings.Count(m => m.Priority == WireMockConstants.AdminPriority).Should().Be(34); + server.Mappings.Count(m => m.Priority == WireMockConstants.AdminPriority).Should().Be(count - 1); server.Mappings.Count(m => m.Priority == WireMockConstants.ProxyPriority).Should().Be(1); }