From 0df3774bb0a747dc8646a8471534e3ab4812df90 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 2 Oct 2024 20:31:38 +0200 Subject: [PATCH 01/13] WireMock.Net.Testcontainers: implement watching the static mapping files + folder for changes --- Directory.Build.props | 2 +- WireMock.Net Solution.sln | 2 +- .../Program.cs | 33 +++++---- .../IWireMockAdminApi.cs | 7 ++ .../WireMock.Net.Testcontainers.csproj | 1 + .../WireMockConfiguration.cs | 21 +++++- .../WireMockContainer.cs | 69 +++++++++++++++++++ .../WireMockContainerBuilder.cs | 4 +- .../Server/WireMockServer.Admin.cs | 22 ++++-- 9 files changed, 137 insertions(+), 24 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 148402b5..37a8fa98 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 1.6.6 + 1.6.7-preview-01 WireMock.Net-Logo.png https://github.com/WireMock-Net/WireMock.Net Apache-2.0 diff --git a/WireMock.Net Solution.sln b/WireMock.Net Solution.sln index ea1480ff..6a4027d8 100644 --- a/WireMock.Net Solution.sln +++ b/WireMock.Net Solution.sln @@ -135,7 +135,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/examples/WireMock.Net.TestcontainersExample/Program.cs b/examples/WireMock.Net.TestcontainersExample/Program.cs index 5dfa9315..223aadd8 100644 --- a/examples/WireMock.Net.TestcontainersExample/Program.cs +++ b/examples/WireMock.Net.TestcontainersExample/Program.cs @@ -1,6 +1,5 @@ // Copyright © WireMock.Net -using System.Runtime.InteropServices; using Newtonsoft.Json; using WireMock.Net.Testcontainers; @@ -12,6 +11,17 @@ private static async Task Main(string[] args) { var original = Console.ForegroundColor; + try + { + Console.ForegroundColor = ConsoleColor.DarkRed; + Console.WriteLine("Automatic"); + await TestAsync(); + } + finally + { + Console.ForegroundColor = original; + } + try { Console.ForegroundColor = ConsoleColor.Yellow; @@ -91,17 +101,6 @@ private static async Task Main(string[] args) { Console.ForegroundColor = original; } - - try - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("Automatic"); - await TestAsync(); - } - finally - { - Console.ForegroundColor = original; - } } private static async Task TestAsync(string? image = null) @@ -109,7 +108,7 @@ private static async Task TestAsync(string? image = null) var mappingsPath = Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "WireMock.Net.Console.NET6", "__admin", "mappings"); var builder = new WireMockContainerBuilder() - .WithAdminUserNameAndPassword("x", "y") + // .WithAdminUserNameAndPassword("x", "y") .WithMappings(mappingsPath) .WithWatchStaticMappings(true) .WithAutoRemove(true) @@ -134,6 +133,8 @@ private static async Task TestAsync(string? image = null) var logs = await container.GetLogsAsync(DateTime.Now.AddDays(-1)); Console.WriteLine("logs = " + logs.Stdout); + Console.WriteLine("PublicUrl = " + container.GetPublicUrl()); + var restEaseApiClient = container.CreateWireMockAdminClient(); var settings = await restEaseApiClient.GetSettingsAsync(); @@ -146,6 +147,12 @@ private static async Task TestAsync(string? image = null) 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(); } } \ No newline at end of file diff --git a/src/WireMock.Net.RestClient/IWireMockAdminApi.cs b/src/WireMock.Net.RestClient/IWireMockAdminApi.cs index 17063f00..13abe8fa 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/readStaticMappings")] + Task ReadStaticMappingsAsync(CancellationToken cancellationToken = default); + /// /// Get a mapping based on the guid /// diff --git a/src/WireMock.Net.Testcontainers/WireMock.Net.Testcontainers.csproj b/src/WireMock.Net.Testcontainers/WireMock.Net.Testcontainers.csproj index ca55a717..513dbd62 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..e56b273f 100644 --- a/src/WireMock.Net.Testcontainers/WireMockContainer.cs +++ b/src/WireMock.Net.Testcontainers/WireMockContainer.cs @@ -1,14 +1,18 @@ // Copyright © WireMock.Net using System; +using System.IO; using System.Net.Http; +using System.Threading.Tasks; 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.Util; namespace WireMock.Net.Testcontainers; @@ -17,10 +21,14 @@ 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. /// @@ -28,6 +36,8 @@ public sealed class WireMockContainer : DockerContainer public WireMockContainer(WireMockConfiguration configuration) : base(configuration) { _configuration = Guard.NotNull(configuration); + + Started += WireMockContainer_Started; } /// @@ -92,6 +102,25 @@ public HttpClient CreateClient(HttpMessageHandler innerHandler, params Delegatin return client; } + /// + protected override ValueTask DisposeAsyncCore() + { + if (_enhancedFileSystemWatcher != null) + { + _enhancedFileSystemWatcher.EnableRaisingEvents = false; + _enhancedFileSystemWatcher.Created -= EnhancedFileSystemWatcherCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Changed -= EnhancedFileSystemWatcherCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Deleted -= EnhancedFileSystemWatcherCreatedChangedOrDeleted; + + _enhancedFileSystemWatcher.Dispose(); + _enhancedFileSystemWatcher = null; + } + + Started -= WireMockContainer_Started; + + return base.DisposeAsyncCore(); + } + private void ValidateIfRunning() { if (State != TestcontainersStates.Running) @@ -100,5 +129,45 @@ private void ValidateIfRunning() } } + private void WireMockContainer_Started(object sender, EventArgs e) + { + if (sender is not WireMockContainer container) + { + return; + } + + if (container._configuration.WatchStaticMappings && !string.IsNullOrEmpty(container._configuration.StaticMappingsPath)) + { + _adminApi = container.CreateWireMockAdminClient(); + + _enhancedFileSystemWatcher = new EnhancedFileSystemWatcher(container._configuration.StaticMappingsPath!, "*.json", EnhancedFileSystemWatcherTimeoutMs) + { + IncludeSubdirectories = container._configuration.WatchStaticMappingsInSubdirectories + }; + _enhancedFileSystemWatcher.Created += EnhancedFileSystemWatcherCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Changed += EnhancedFileSystemWatcherCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Deleted += EnhancedFileSystemWatcherCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.EnableRaisingEvents = true; + } + } + + private async void EnhancedFileSystemWatcherCreatedChangedOrDeleted(object sender, FileSystemEventArgs args) + { + if (_adminApi == null) + { + return; + } + + Logger.LogInformation("MappingFile created, changed or deleted: '{0}'. Triggering ReadStaticMappings.", args.FullPath); + try + { + await _adminApi.ReadStaticMappingsAsync(); + } + catch (Exception ex) + { + Logger.LogWarning(ex, "Error calling /__admin/mappings/readStaticMappings"); + } + } + 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..2559f097 100644 --- a/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs +++ b/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs @@ -130,7 +130,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}"); } /// diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs index a7cf33ca..99e63b00 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/readStaticMappings + Given(Request.Create().WithPath(_adminPaths.Mappings + "/readStaticMappings").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ReadStaticMappings)); + // __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 ReadStaticMappings(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)) { From b600c79e3e35825fdb4e78fcbe99215ad6d57cd1 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 2 Oct 2024 20:40:58 +0200 Subject: [PATCH 02/13] ReloadStaticMappings --- src/WireMock.Net.RestClient/IWireMockAdminApi.cs | 4 ++-- .../WireMockContainer.cs | 6 +++--- src/WireMock.Net/Server/WireMockServer.Admin.cs | 6 +++--- .../AdminApi/WireMockAdminApiTests.cs | 16 +++++++++++++++- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/WireMock.Net.RestClient/IWireMockAdminApi.cs b/src/WireMock.Net.RestClient/IWireMockAdminApi.cs index 13abe8fa..53bedebb 100644 --- a/src/WireMock.Net.RestClient/IWireMockAdminApi.cs +++ b/src/WireMock.Net.RestClient/IWireMockAdminApi.cs @@ -126,8 +126,8 @@ public interface IWireMockAdminApi /// Reload the static mappings. /// /// The optional cancellationToken. - [Post("mappings/readStaticMappings")] - Task ReadStaticMappingsAsync(CancellationToken cancellationToken = default); + [Post("mappings/reloadStaticMappings")] + Task ReloadStaticMappingsAsync(CancellationToken cancellationToken = default); /// /// Get a mapping based on the guid diff --git a/src/WireMock.Net.Testcontainers/WireMockContainer.cs b/src/WireMock.Net.Testcontainers/WireMockContainer.cs index e56b273f..85418ced 100644 --- a/src/WireMock.Net.Testcontainers/WireMockContainer.cs +++ b/src/WireMock.Net.Testcontainers/WireMockContainer.cs @@ -158,14 +158,14 @@ private async void EnhancedFileSystemWatcherCreatedChangedOrDeleted(object sende return; } - Logger.LogInformation("MappingFile created, changed or deleted: '{0}'. Triggering ReadStaticMappings.", args.FullPath); + Logger.LogInformation("MappingFile created, changed or deleted: '{0}'. Triggering ReloadStaticMappings.", args.FullPath); try { - await _adminApi.ReadStaticMappingsAsync(); + await _adminApi.ReloadStaticMappingsAsync(); } catch (Exception ex) { - Logger.LogWarning(ex, "Error calling /__admin/mappings/readStaticMappings"); + Logger.LogWarning(ex, "Error calling /__admin/mappings/reloadStaticMappings"); } } diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs index 99e63b00..39ac7de9 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs @@ -96,8 +96,8 @@ private void InitAdmin() // __admin/mappings/reset Given(Request.Create().WithPath(_adminPaths.Mappings + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsReset)); - // __admin/mappings/readStaticMappings - Given(Request.Create().WithPath(_adminPaths.Mappings + "/readStaticMappings").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ReadStaticMappings)); + // __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)); @@ -572,7 +572,7 @@ private IResponseMessage MappingsReset(IRequestMessage requestMessage) return ResponseMessageBuilder.Create(200, message); } - private IResponseMessage ReadStaticMappings(IRequestMessage _) + private IResponseMessage ReloadStaticMappings(IRequestMessage _) { ReadStaticMappings(); 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 From 878be3b8978abb1abec7c07525c76b00ece1c728 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 2 Oct 2024 21:00:24 +0200 Subject: [PATCH 03/13] fix --- .github/workflows/ci.yml | 24 ++++++++++------ azure-pipelines-ci.yml | 28 +++++++++++++++++-- .../WireMockServer.Proxy.cs | 4 +-- .../WireMockServer.Settings.cs | 10 +++++-- 4 files changed, 50 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f6695aa..15698399 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: 'Execute WireMock.Net.Tests' + run: dotnet test './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' -c Release --framework net8.0 + + - name: 'Execute WireMock.Net.TUnitTests' + run: dotnet test './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' -c Release --framework net8.0 + + - name: 'Execute 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 @@ -36,10 +39,13 @@ jobs: - 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 + run: dotnet test './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' -c Release --framework net8.0 + + - name: 'Execute Tests' + run: dotnet test './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' -c Release --framework net8.0 + + - name: 'Execute 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 diff --git a/azure-pipelines-ci.yml b/azure-pipelines-ci.yml index 498f6bba..d222b482 100644 --- a/azure-pipelines-ci.yml +++ b/azure-pipelines-ci.yml @@ -53,9 +53,19 @@ 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: 'Execute 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: 'Execute 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: 'Execute WireMock.Net.Middleware.Tests with Coverage' - task: CmdLine@2 inputs: @@ -112,11 +122,25 @@ jobs: arguments: '--configuration Debug --framework net8.0' - task: DotNetCoreCLI@2 - displayName: 'Execute Unit Tests with Coverage' + displayName: 'Execute WireMock.Net.Tests 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' + + - task: DotNetCoreCLI@2 + displayName: 'Execute WireMock.Net.TUnitTests with Coverage' + inputs: + command: 'test' + projects: './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' + arguments: '--no-build --configuration Debug --framework net8.0 --collect:"XPlat Code Coverage" --logger trx' + + - task: DotNetCoreCLI@2 + displayName: 'Execute WireMock.Net.Middleware.Tests with Coverage' + inputs: + command: 'test' + projects: './test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj' + arguments: '--no-build --configuration Debug --framework net8.0 --collect:"XPlat Code Coverage" --logger trx' - job: Windows_Release_to_MyGet dependsOn: Windows_Build_Test diff --git a/test/WireMock.Net.Tests/WireMockServer.Proxy.cs b/test/WireMock.Net.Tests/WireMockServer.Proxy.cs index d8efd106..6fecd69d 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); } From 132fd16b46fdd2568118e27c6c783a8da4244915 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 2 Oct 2024 21:12:39 +0200 Subject: [PATCH 04/13] . --- .github/workflows/ci.yml | 14 +++++++------- azure-pipelines-ci.yml | 27 ++++++++++----------------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15698399..cfccb7ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,13 +19,13 @@ jobs: steps: - uses: actions/checkout@v4 - - name: 'Execute WireMock.Net.Tests' + - name: 'WireMock.Net.Tests' run: dotnet test './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' -c Release --framework net8.0 - - name: 'Execute WireMock.Net.TUnitTests' + - name: 'WireMock.Net.TUnitTests' run: dotnet test './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' -c Release --framework net8.0 - - name: 'Execute WireMock.Net.Middleware.Tests' + - 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: @@ -38,17 +38,17 @@ jobs: steps: - uses: actions/checkout@v4 - - name: 'Execute Tests' + - name: 'WireMock.Net.Tests' run: dotnet test './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' -c Release --framework net8.0 - - name: 'Execute Tests' + - name: 'WireMock.Net.TUnitTests' run: dotnet test './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' -c Release --framework net8.0 - - name: 'Execute Tests' + - 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/azure-pipelines-ci.yml b/azure-pipelines-ci.yml index d222b482..1039d8fd 100644 --- a/azure-pipelines-ci.yml +++ b/azure-pipelines-ci.yml @@ -53,25 +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: 'Execute WireMock.Net.Tests with Coverage' + 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: 'Execute WireMock.Net.TUnitTests with Coverage' + 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.Middleware.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' @@ -115,32 +115,25 @@ jobs: version: '8.0.x' - task: DotNetCoreCLI@2 - displayName: 'Build Unit tests' - inputs: - command: 'build' - projects: './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' - arguments: '--configuration Debug --framework net8.0' - - - task: DotNetCoreCLI@2 - displayName: 'Execute WireMock.Net.Tests with Coverage' + displayName: 'WireMock.Net.Tests 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' + arguments: '--configuration Debug --framework net8.0 --collect:"XPlat Code Coverage" --logger trx' - task: DotNetCoreCLI@2 - displayName: 'Execute WireMock.Net.TUnitTests with Coverage' + displayName: 'WireMock.Net.TUnitTests with Coverage' inputs: command: 'test' projects: './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' - arguments: '--no-build --configuration Debug --framework net8.0 --collect:"XPlat Code Coverage" --logger trx' + arguments: '--configuration Debug --framework net8.0 --collect:"XPlat Code Coverage" --logger trx' - task: DotNetCoreCLI@2 - displayName: 'Execute WireMock.Net.Middleware.Tests with Coverage' + displayName: 'WireMock.Net.Middleware.Tests with Coverage' inputs: command: 'test' projects: './test/WireMock.Net.Middleware.Tests/WireMock.Net.Middleware.Tests.csproj' - arguments: '--no-build --configuration Debug --framework net8.0 --collect:"XPlat Code Coverage" --logger trx' + arguments: '--configuration Debug --framework net8.0 --collect:"XPlat Code Coverage" --logger trx' - job: Windows_Release_to_MyGet dependsOn: Windows_Build_Test From fa240d598e40fda10abf0d529512e37bdfaaf661 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 2 Oct 2024 22:19:28 +0200 Subject: [PATCH 05/13] . --- .../WireMock.Net.TestcontainersExample/Program.cs | 6 ++++-- .../WireMockContainer.cs | 14 +++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/examples/WireMock.Net.TestcontainersExample/Program.cs b/examples/WireMock.Net.TestcontainersExample/Program.cs index 223aadd8..0a78f242 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.Images; using Newtonsoft.Json; using WireMock.Net.Testcontainers; @@ -108,11 +109,12 @@ private static async Task TestAsync(string? image = null) var mappingsPath = Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "WireMock.Net.Console.NET6", "__admin", "mappings"); var builder = new WireMockContainerBuilder() - // .WithAdminUserNameAndPassword("x", "y") + .WithAdminUserNameAndPassword("x", "y") .WithMappings(mappingsPath) .WithWatchStaticMappings(true) .WithAutoRemove(true) - .WithCleanUp(true); + .WithCleanUp(true) + .WithImagePullPolicy(PullPolicy.Missing); if (image != null) { diff --git a/src/WireMock.Net.Testcontainers/WireMockContainer.cs b/src/WireMock.Net.Testcontainers/WireMockContainer.cs index 85418ced..6c69f46e 100644 --- a/src/WireMock.Net.Testcontainers/WireMockContainer.cs +++ b/src/WireMock.Net.Testcontainers/WireMockContainer.cs @@ -108,9 +108,9 @@ protected override ValueTask DisposeAsyncCore() if (_enhancedFileSystemWatcher != null) { _enhancedFileSystemWatcher.EnableRaisingEvents = false; - _enhancedFileSystemWatcher.Created -= EnhancedFileSystemWatcherCreatedChangedOrDeleted; - _enhancedFileSystemWatcher.Changed -= EnhancedFileSystemWatcherCreatedChangedOrDeleted; - _enhancedFileSystemWatcher.Deleted -= EnhancedFileSystemWatcherCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Created -= FileCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Changed -= FileCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Deleted -= FileCreatedChangedOrDeleted; _enhancedFileSystemWatcher.Dispose(); _enhancedFileSystemWatcher = null; @@ -144,14 +144,14 @@ private void WireMockContainer_Started(object sender, EventArgs e) { IncludeSubdirectories = container._configuration.WatchStaticMappingsInSubdirectories }; - _enhancedFileSystemWatcher.Created += EnhancedFileSystemWatcherCreatedChangedOrDeleted; - _enhancedFileSystemWatcher.Changed += EnhancedFileSystemWatcherCreatedChangedOrDeleted; - _enhancedFileSystemWatcher.Deleted += EnhancedFileSystemWatcherCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Created += FileCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Changed += FileCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Deleted += FileCreatedChangedOrDeleted; _enhancedFileSystemWatcher.EnableRaisingEvents = true; } } - private async void EnhancedFileSystemWatcherCreatedChangedOrDeleted(object sender, FileSystemEventArgs args) + private async void FileCreatedChangedOrDeleted(object sender, FileSystemEventArgs args) { if (_adminApi == null) { From 28d41c42221e98f6ff22cad7820a5a62fb610c4b Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 3 Oct 2024 07:35:09 +0200 Subject: [PATCH 06/13] . --- .../WireMock.Net.Aspire.csproj | 4 ++++ .../WireMockServerLifecycleHook.cs | 19 +++++++------------ .../WireMockServerResource.cs | 13 +++++++++++++ 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/WireMock.Net.Aspire/WireMock.Net.Aspire.csproj b/src/WireMock.Net.Aspire/WireMock.Net.Aspire.csproj index dd1039e3..3b9167e7 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/WireMockServerLifecycleHook.cs b/src/WireMock.Net.Aspire/WireMockServerLifecycleHook.cs index 6c8049ae..771fbcd5 100644 --- a/src/WireMock.Net.Aspire/WireMockServerLifecycleHook.cs +++ b/src/WireMock.Net.Aspire/WireMockServerLifecycleHook.cs @@ -3,7 +3,6 @@ using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Lifecycle; using Microsoft.Extensions.Logging; -using RestEase; using WireMock.Client; using WireMock.Client.Extensions; @@ -28,25 +27,21 @@ public async Task AfterResourcesCreatedAsync(DistributedApplicationModel appMode var endpoint = wireMockServerResource.GetEndpoint(); if (endpoint.IsAllocated) { - var adminApi = CreateWireMockAdminApi(wireMockServerResource); - + var adminApi = wireMockServerResource.AdminApi.Value; var logger = loggerService.GetLogger(wireMockServerResource); - logger.LogInformation("Checking Health status from WireMock.Net"); + logger.LogInformation("Checking Health status from WireMock.Net"); await adminApi.WaitForHealthAsync(cancellationToken: cancellationToken); - logger.LogInformation("Calling ApiMappingBuilder to add mappings to WireMock.Net"); - var mappingBuilder = adminApi.GetMappingBuilder(); - await wireMockServerResource.Arguments.ApiMappingBuilder!.Invoke(mappingBuilder); + await CallApiMappingBuilderActionAsync(logger, adminApi, wireMockServerResource); } } } - private static IWireMockAdminApi CreateWireMockAdminApi(WireMockServerResource resource) + private static async Task CallApiMappingBuilderActionAsync(ILogger logger, IWireMockAdminApi adminApi, WireMockServerResource wireMockServerResource) { - var adminApi = RestClient.For(resource.GetEndpoint().Url); - return resource.Arguments.HasBasicAuthentication ? - adminApi.WithAuthorization(resource.Arguments.AdminUsername!, resource.Arguments.AdminPassword!) : - adminApi; + logger.LogInformation("Calling ApiMappingBuilder to add mappings to WireMock.Net"); + var mappingBuilder = adminApi.GetMappingBuilder(); + await wireMockServerResource.Arguments.ApiMappingBuilder!.Invoke(mappingBuilder); } } \ No newline at end of file diff --git a/src/WireMock.Net.Aspire/WireMockServerResource.cs b/src/WireMock.Net.Aspire/WireMockServerResource.cs index b6da6600..b3a973b0 100644 --- a/src/WireMock.Net.Aspire/WireMockServerResource.cs +++ b/src/WireMock.Net.Aspire/WireMockServerResource.cs @@ -1,6 +1,9 @@ // Copyright © WireMock.Net +using RestEase; using Stef.Validation; +using WireMock.Client; +using WireMock.Client.Extensions; // ReSharper disable once CheckNamespace namespace Aspire.Hosting.ApplicationModel; @@ -12,6 +15,8 @@ public class WireMockServerResource : ContainerResource, IResourceWithServiceDis { internal WireMockServerArguments Arguments { get; } + internal Lazy AdminApi => new(CreateWireMockAdminApi); + /// /// Initializes a new instance of the class. /// @@ -30,4 +35,12 @@ public EndpointReference GetEndpoint() { return new EndpointReference(this, "http"); } + + private IWireMockAdminApi CreateWireMockAdminApi() + { + var adminApi = RestClient.For(GetEndpoint().Url); + return Arguments.HasBasicAuthentication ? + adminApi.WithAuthorization(Arguments.AdminUsername!, Arguments.AdminPassword!) : + adminApi; + } } \ No newline at end of file From 8fb8b3407ba8b5da9377b516543eec1012359371 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Fri, 4 Oct 2024 13:02:50 +0200 Subject: [PATCH 07/13] . --- examples-Aspire/AspireApp1.AppHost/Program.cs | 2 +- .../WeatherForecastApiMock.cs | 4 +- .../ResourceLoggerServiceExtensions.cs | 14 ++++ .../WireMockServerArguments.cs | 6 +- .../WireMockServerBuilderExtensions.cs | 19 ++++- .../WireMockServerLifecycleHook.cs | 27 ++----- .../WireMockServerResource.cs | 78 ++++++++++++++++++- .../WireMockServerArgumentsTests.cs | 6 +- .../WireMockServerBuilderExtensionsTests.cs | 2 +- 9 files changed, 123 insertions(+), 35 deletions(-) create mode 100644 src/WireMock.Net.Aspire/Extensions/ResourceLoggerServiceExtensions.cs diff --git a/examples-Aspire/AspireApp1.AppHost/Program.cs b/examples-Aspire/AspireApp1.AppHost/Program.cs index 83d60feb..4a238b85 100644 --- a/examples-Aspire/AspireApp1.AppHost/Program.cs +++ b/examples-Aspire/AspireApp1.AppHost/Program.cs @@ -11,7 +11,7 @@ var wiremock = builder .AddWireMock("apiservice", WireMockServerArguments.DefaultPort) .WithMappingsPath(mappingsPath) - .WithReadStaticMappings() + .WithWatchStaticMappings() .WithApiMappingBuilder(WeatherForecastApiMock.BuildAsync); builder.AddProject("webfrontend") diff --git a/examples-Aspire/AspireApp1.AppHost/WeatherForecastApiMock.cs b/examples-Aspire/AspireApp1.AppHost/WeatherForecastApiMock.cs index bcda9855..6e10d7a7 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/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/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 771fbcd5..aeaea393 100644 --- a/src/WireMock.Net.Aspire/WireMockServerLifecycleHook.cs +++ b/src/WireMock.Net.Aspire/WireMockServerLifecycleHook.cs @@ -2,9 +2,7 @@ using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Lifecycle; -using Microsoft.Extensions.Logging; -using WireMock.Client; -using WireMock.Client.Extensions; +using WireMock.Net.Aspire.Extensions; namespace WireMock.Net.Aspire; @@ -14,34 +12,21 @@ public async Task AfterResourcesCreatedAsync(DistributedApplicationModel appMode { var wireMockServerResources = appModel.Resources .OfType() - .Where(resource => resource.Arguments.ApiMappingBuilder is not null) .ToArray(); - if (wireMockServerResources.Length == 0) - { - return; - } - foreach (var wireMockServerResource in wireMockServerResources) { + loggerService.SetLogger(wireMockServerResource); + var endpoint = wireMockServerResource.GetEndpoint(); if (endpoint.IsAllocated) { - var adminApi = wireMockServerResource.AdminApi.Value; - var logger = loggerService.GetLogger(wireMockServerResource); + await wireMockServerResource.WaitForHealthAsync(cancellationToken); - logger.LogInformation("Checking Health status from WireMock.Net"); - await adminApi.WaitForHealthAsync(cancellationToken: cancellationToken); + await wireMockServerResource.CallApiMappingBuilderActionAsync(cancellationToken); - await CallApiMappingBuilderActionAsync(logger, adminApi, wireMockServerResource); + wireMockServerResource.StartWatchingStaticMappings(cancellationToken); } } } - - private static async Task CallApiMappingBuilderActionAsync(ILogger logger, IWireMockAdminApi adminApi, WireMockServerResource wireMockServerResource) - { - logger.LogInformation("Calling ApiMappingBuilder to add mappings to WireMock.Net"); - var mappingBuilder = adminApi.GetMappingBuilder(); - await wireMockServerResource.Arguments.ApiMappingBuilder!.Invoke(mappingBuilder); - } } \ No newline at end of file diff --git a/src/WireMock.Net.Aspire/WireMockServerResource.cs b/src/WireMock.Net.Aspire/WireMockServerResource.cs index b3a973b0..e2eac519 100644 --- a/src/WireMock.Net.Aspire/WireMockServerResource.cs +++ b/src/WireMock.Net.Aspire/WireMockServerResource.cs @@ -1,9 +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; @@ -13,10 +15,14 @@ namespace Aspire.Hosting.ApplicationModel; /// public class WireMockServerResource : ContainerResource, IResourceWithServiceDiscovery { - internal WireMockServerArguments Arguments { get; } + 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. /// @@ -36,6 +42,63 @@ 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); @@ -43,4 +106,17 @@ private IWireMockAdminApi CreateWireMockAdminApi() 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/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 }); From f449660e17414e9ac12b5ca31bbabe789b9479ce Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Fri, 4 Oct 2024 21:07:29 +0200 Subject: [PATCH 08/13] . --- examples-Aspire/AspireApp1.AppHost/Program.cs | 1 + .../WireMockServerLifecycleHook.cs | 21 +++++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/examples-Aspire/AspireApp1.AppHost/Program.cs b/examples-Aspire/AspireApp1.AppHost/Program.cs index 4c2778e4..854c7e06 100644 --- a/examples-Aspire/AspireApp1.AppHost/Program.cs +++ b/examples-Aspire/AspireApp1.AppHost/Program.cs @@ -10,6 +10,7 @@ .AddWireMock("apiservice", WireMockServerArguments.DefaultPort) .WithMappingsPath(mappingsPath) .WithReadStaticMappings() + .WithWatchStaticMappings() .WithApiMappingBuilder(WeatherForecastApiMock.BuildAsync); //var apiServiceUsedForDocs = builder diff --git a/src/WireMock.Net.Aspire/WireMockServerLifecycleHook.cs b/src/WireMock.Net.Aspire/WireMockServerLifecycleHook.cs index aeaea393..d97a4113 100644 --- a/src/WireMock.Net.Aspire/WireMockServerLifecycleHook.cs +++ b/src/WireMock.Net.Aspire/WireMockServerLifecycleHook.cs @@ -2,31 +2,40 @@ using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Lifecycle; -using WireMock.Net.Aspire.Extensions; +using Microsoft.Extensions.Logging; 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() .ToArray(); foreach (var wireMockServerResource in wireMockServerResources) { - loggerService.SetLogger(wireMockServerResource); + wireMockServerResource.SetLogger(loggerFactory.CreateLogger()); var endpoint = wireMockServerResource.GetEndpoint(); if (endpoint.IsAllocated) { - await wireMockServerResource.WaitForHealthAsync(cancellationToken); + await wireMockServerResource.WaitForHealthAsync(cts.Token); - await wireMockServerResource.CallApiMappingBuilderActionAsync(cancellationToken); + await wireMockServerResource.CallApiMappingBuilderActionAsync(cts.Token); - wireMockServerResource.StartWatchingStaticMappings(cancellationToken); + wireMockServerResource.StartWatchingStaticMappings(cts.Token); } } } + + public async ValueTask DisposeAsync() + { + await _shutdownCts.CancelAsync(); + } } \ No newline at end of file From 7c8747b96f7d4d0cfe4325243b9153e4680f4667 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Fri, 4 Oct 2024 21:10:51 +0200 Subject: [PATCH 09/13] . --- .../Extensions/WireMockAdminApiExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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."); } } From 3b5a29a8e9c802777cb20239a4ef76baa0a9db67 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 5 Oct 2024 21:00:56 +0200 Subject: [PATCH 10/13] . --- .../Program.cs | 22 ++++- .../Utils/ContainerInfoProvider.cs | 16 +++ .../WireMockContainer.cs | 98 +++++++++++++------ .../WireMockContainerBuilder.cs | 14 +-- 4 files changed, 109 insertions(+), 41 deletions(-) create mode 100644 src/WireMock.Net.Testcontainers/Utils/ContainerInfoProvider.cs diff --git a/examples/WireMock.Net.TestcontainersExample/Program.cs b/examples/WireMock.Net.TestcontainersExample/Program.cs index 0a78f242..3e807526 100644 --- a/examples/WireMock.Net.TestcontainersExample/Program.cs +++ b/examples/WireMock.Net.TestcontainersExample/Program.cs @@ -1,5 +1,6 @@ // Copyright © WireMock.Net +using System.Runtime.InteropServices; using DotNet.Testcontainers.Images; using Newtonsoft.Json; using WireMock.Net.Testcontainers; @@ -128,12 +129,26 @@ private static async Task TestAsync(string? image = null) var container = builder.Build(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + try + { + await container.CopyAsync(@"C:\temp-wiremock\__admin\mappings\StefBodyAsFileExample.json", @"c:\app\__admin\mappings"); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + await container.StartAsync(); await Task.Delay(1_000); - var logs = await container.GetLogsAsync(DateTime.Now.AddDays(-1)); - Console.WriteLine("logs = " + logs.Stdout); + await container.ReloadStaticMappingsAsync(); + + //var logs = await container.GetLogsAsync(DateTime.Now.AddDays(-1)); + //Console.WriteLine("logs = " + logs.Stdout); Console.WriteLine("PublicUrl = " + container.GetPublicUrl()); @@ -143,7 +158,8 @@ 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)); + var mappingsStef = mappings.Where(m => JsonConvert.SerializeObject(m.Request.Path).Contains("Stef")); + Console.WriteLine("mappingsStef = " + JsonConvert.SerializeObject(mappingsStef, Formatting.Indented)); var client = container.CreateClient(); var result = await client.GetStringAsync("/static/mapping"); 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/WireMockContainer.cs b/src/WireMock.Net.Testcontainers/WireMockContainer.cs index 6c69f46e..337d1510 100644 --- a/src/WireMock.Net.Testcontainers/WireMockContainer.cs +++ b/src/WireMock.Net.Testcontainers/WireMockContainer.cs @@ -3,12 +3,12 @@ using System; using System.IO; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; 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; @@ -35,7 +35,7 @@ public sealed class WireMockContainer : DockerContainer /// The container configuration. public WireMockContainer(WireMockConfiguration configuration) : base(configuration) { - _configuration = Guard.NotNull(configuration); + _configuration = Stef.Validation.Guard.NotNull(configuration); Started += WireMockContainer_Started; } @@ -102,6 +102,47 @@ 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 (ShouldWatchStaticMappingsAndTriggerReload() && 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() { @@ -121,6 +162,11 @@ protected override ValueTask DisposeAsyncCore() return base.DisposeAsyncCore(); } + //private static bool PathStartsWithContainerMappingsPath(string value) + //{ + // return ContainerInfoProvider.Info.Values.Any(ci => value.StartsWith(ci.MappingsPath)); + //} + private void ValidateIfRunning() { if (State != TestcontainersStates.Running) @@ -131,42 +177,38 @@ private void ValidateIfRunning() private void WireMockContainer_Started(object sender, EventArgs e) { - if (sender is not WireMockContainer container) + if (!ShouldWatchStaticMappingsAndTriggerReload()) { return; } - if (container._configuration.WatchStaticMappings && !string.IsNullOrEmpty(container._configuration.StaticMappingsPath)) + _adminApi = CreateWireMockAdminClient(); + + _enhancedFileSystemWatcher = new EnhancedFileSystemWatcher(_configuration.StaticMappingsPath!, "*.json", EnhancedFileSystemWatcherTimeoutMs) { - _adminApi = container.CreateWireMockAdminClient(); - - _enhancedFileSystemWatcher = new EnhancedFileSystemWatcher(container._configuration.StaticMappingsPath!, "*.json", EnhancedFileSystemWatcherTimeoutMs) - { - IncludeSubdirectories = container._configuration.WatchStaticMappingsInSubdirectories - }; - _enhancedFileSystemWatcher.Created += FileCreatedChangedOrDeleted; - _enhancedFileSystemWatcher.Changed += FileCreatedChangedOrDeleted; - _enhancedFileSystemWatcher.Deleted += FileCreatedChangedOrDeleted; - _enhancedFileSystemWatcher.EnableRaisingEvents = true; - } + IncludeSubdirectories = _configuration.WatchStaticMappingsInSubdirectories + }; + _enhancedFileSystemWatcher.Created += FileCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Changed += FileCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.Deleted += FileCreatedChangedOrDeleted; + _enhancedFileSystemWatcher.EnableRaisingEvents = true; + } + + private bool ShouldWatchStaticMappingsAndTriggerReload() + { + return _configuration.WatchStaticMappings && + !string.IsNullOrEmpty(_configuration.StaticMappingsPath); } private async void FileCreatedChangedOrDeleted(object sender, FileSystemEventArgs args) { - if (_adminApi == null) - { - return; - } + await ReloadStaticMappingsAsync(args.FullPath); + } - Logger.LogInformation("MappingFile created, changed or deleted: '{0}'. Triggering ReloadStaticMappings.", args.FullPath); - try - { - await _adminApi.ReloadStaticMappingsAsync(); - } - catch (Exception ex) - { - Logger.LogWarning(ex, "Error calling /__admin/mappings/reloadStaticMappings"); - } + 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; diff --git a/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs b/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs index 2559f097..0defb7bc 100644 --- a/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs +++ b/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs @@ -1,7 +1,6 @@ // Copyright © WireMock.Net using System; -using System.Collections.Generic; using System.Runtime.InteropServices; using System.Threading.Tasks; using Docker.DotNet.Models; @@ -9,7 +8,7 @@ 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,11 +18,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 () => { @@ -183,7 +177,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(); @@ -200,7 +194,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")); } /// @@ -224,6 +218,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 From 92a28e6fe8ba6fb01dcb7113a7c0561eb65924dc Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 6 Oct 2024 11:47:22 +0200 Subject: [PATCH 11/13] CopyAsync --- .../Program.cs | 82 +++++++++++++------ .../Utils/PlatformUtils.cs | 40 +++++++++ .../WireMockContainer.cs | 30 +++---- .../WireMockContainerBuilder.cs | 18 +--- 4 files changed, 111 insertions(+), 59 deletions(-) create mode 100644 src/WireMock.Net.Testcontainers/Utils/PlatformUtils.cs diff --git a/examples/WireMock.Net.TestcontainersExample/Program.cs b/examples/WireMock.Net.TestcontainersExample/Program.cs index 3e807526..7f5d804f 100644 --- a/examples/WireMock.Net.TestcontainersExample/Program.cs +++ b/examples/WireMock.Net.TestcontainersExample/Program.cs @@ -1,7 +1,5 @@ // Copyright © WireMock.Net -using System.Runtime.InteropServices; -using DotNet.Testcontainers.Images; using Newtonsoft.Json; using WireMock.Net.Testcontainers; @@ -13,6 +11,21 @@ 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; @@ -105,6 +118,41 @@ private static async Task Main(string[] args) } } + private static async Task TestCopyAsync() + { + var builder = new WireMockContainerBuilder() + .WithWatchStaticMappings(true) + .WithAutoRemove(true) + .WithCleanUp(true); + + var container = builder.Build(); + + await container.StartAsync(); + + 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) { var mappingsPath = Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "WireMock.Net.Console.NET6", "__admin", "mappings"); @@ -114,8 +162,7 @@ private static async Task TestAsync(string? image = null) .WithMappings(mappingsPath) .WithWatchStaticMappings(true) .WithAutoRemove(true) - .WithCleanUp(true) - .WithImagePullPolicy(PullPolicy.Missing); + .WithCleanUp(true); if (image != null) { @@ -129,22 +176,8 @@ private static async Task TestAsync(string? image = null) var container = builder.Build(); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - try - { - await container.CopyAsync(@"C:\temp-wiremock\__admin\mappings\StefBodyAsFileExample.json", @"c:\app\__admin\mappings"); - } - catch (Exception e) - { - Console.WriteLine(e); - } - } - await container.StartAsync(); - await Task.Delay(1_000); - await container.ReloadStaticMappingsAsync(); //var logs = await container.GetLogsAsync(DateTime.Now.AddDays(-1)); @@ -158,18 +191,17 @@ private static async Task TestAsync(string? image = null) Console.WriteLine("settings = " + JsonConvert.SerializeObject(settings, Formatting.Indented)); var mappings = await restEaseApiClient.GetMappingsAsync(); - var mappingsStef = mappings.Where(m => JsonConvert.SerializeObject(m.Request.Path).Contains("Stef")); - Console.WriteLine("mappingsStef = " + JsonConvert.SerializeObject(mappingsStef, 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(); - } + //if (image == null) + //{ + // Console.WriteLine("Press any key to stop."); + // Console.ReadKey(); + //} await container.StopAsync(); } diff --git a/src/WireMock.Net.Testcontainers/Utils/PlatformUtils.cs b/src/WireMock.Net.Testcontainers/Utils/PlatformUtils.cs new file mode 100644 index 00000000..2d832cfd --- /dev/null +++ b/src/WireMock.Net.Testcontainers/Utils/PlatformUtils.cs @@ -0,0 +1,40 @@ +// 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 PlatformUtils +{ + public static OSPlatform GetCurrentOSPlatform() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return OSPlatform.Windows; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return OSPlatform.Linux; + } + + throw new NotSupportedException("The current OSPlatform is not supported."); + } + + 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/WireMockContainer.cs b/src/WireMock.Net.Testcontainers/WireMockContainer.cs index 337d1510..56568d4d 100644 --- a/src/WireMock.Net.Testcontainers/WireMockContainer.cs +++ b/src/WireMock.Net.Testcontainers/WireMockContainer.cs @@ -5,6 +5,7 @@ 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; @@ -12,6 +13,7 @@ using WireMock.Client; using WireMock.Client.Extensions; using WireMock.Http; +using WireMock.Net.Testcontainers.Utils; using WireMock.Util; namespace WireMock.Net.Testcontainers; @@ -102,7 +104,6 @@ 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. /// @@ -115,13 +116,12 @@ public HttpClient CreateClient(HttpMessageHandler innerHandler, params Delegatin { await base.CopyAsync(source, target, fileMode, ct); - if (ShouldWatchStaticMappingsAndTriggerReload() && PathStartsWithContainerMappingsPath(target)) + if (_configuration.WatchStaticMappings && await PathStartsWithContainerMappingsPath(target)) { await ReloadStaticMappingsAsync(target, ct); } } - */ - + /// /// Reload the static mappings. /// @@ -162,10 +162,12 @@ protected override ValueTask DisposeAsyncCore() return base.DisposeAsyncCore(); } - //private static bool PathStartsWithContainerMappingsPath(string value) - //{ - // return ContainerInfoProvider.Info.Values.Any(ci => value.StartsWith(ci.MappingsPath)); - //} + private static async Task PathStartsWithContainerMappingsPath(string value) + { + var imageOs = await PlatformUtils.GetImageOSAsync.Value; + + return value.StartsWith(ContainerInfoProvider.Info[imageOs].MappingsPath); + } private void ValidateIfRunning() { @@ -177,13 +179,13 @@ private void ValidateIfRunning() private void WireMockContainer_Started(object sender, EventArgs e) { - if (!ShouldWatchStaticMappingsAndTriggerReload()) + _adminApi = CreateWireMockAdminClient(); + + if (!_configuration.WatchStaticMappings || string.IsNullOrEmpty(_configuration.StaticMappingsPath)) { return; } - _adminApi = CreateWireMockAdminClient(); - _enhancedFileSystemWatcher = new EnhancedFileSystemWatcher(_configuration.StaticMappingsPath!, "*.json", EnhancedFileSystemWatcherTimeoutMs) { IncludeSubdirectories = _configuration.WatchStaticMappingsInSubdirectories @@ -194,12 +196,6 @@ private void WireMockContainer_Started(object sender, EventArgs e) _enhancedFileSystemWatcher.EnableRaisingEvents = true; } - private bool ShouldWatchStaticMappingsAndTriggerReload() - { - return _configuration.WatchStaticMappings && - !string.IsNullOrEmpty(_configuration.StaticMappingsPath); - } - private async void FileCreatedChangedOrDeleted(object sender, FileSystemEventArgs args) { await ReloadStaticMappingsAsync(args.FullPath); diff --git a/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs b/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs index 0defb7bc..83d2eafc 100644 --- a/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs +++ b/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs @@ -2,7 +2,6 @@ using System; using System.Runtime.InteropServices; -using System.Threading.Tasks; using Docker.DotNet.Models; using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Configurations; @@ -18,21 +17,6 @@ namespace WireMock.Net.Testcontainers; public sealed class WireMockContainerBuilder : ContainerBuilder { private const string DefaultLogger = "WireMockConsoleLogger"; - - 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; /// @@ -52,7 +36,7 @@ public WireMockContainerBuilder() : this(new WireMockConfiguration()) [PublicAPI] public WireMockContainerBuilder WithImage() { - _imageOS ??= _getOSAsLazy.Value.GetAwaiter().GetResult(); + _imageOS ??= PlatformUtils.GetImageOSAsync.Value.GetAwaiter().GetResult(); return WithImage(_imageOS.Value); } From 4c6ed69ae938757d1a95ec4c544e8a3feb715de8 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 6 Oct 2024 17:41:20 +0200 Subject: [PATCH 12/13] 1.6.7-preview-02 --- Directory.Build.props | 2 +- .../Program.cs | 31 +++++++++++++++---- .../{PlatformUtils.cs => ContainerUtils.cs} | 17 +--------- .../WireMockContainer.cs | 2 +- .../WireMockContainerBuilder.cs | 2 +- 5 files changed, 29 insertions(+), 25 deletions(-) rename src/WireMock.Net.Testcontainers/Utils/{PlatformUtils.cs => ContainerUtils.cs} (68%) diff --git a/Directory.Build.props b/Directory.Build.props index 37a8fa98..49e65a08 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 1.6.7-preview-01 + 1.6.7-preview-02 WireMock.Net-Logo.png https://github.com/WireMock-Net/WireMock.Net Apache-2.0 diff --git a/examples/WireMock.Net.TestcontainersExample/Program.cs b/examples/WireMock.Net.TestcontainersExample/Program.cs index 7f5d804f..b6ddb0c3 100644 --- a/examples/WireMock.Net.TestcontainersExample/Program.cs +++ b/examples/WireMock.Net.TestcontainersExample/Program.cs @@ -1,5 +1,7 @@ // Copyright © WireMock.Net +using DotNet.Testcontainers.Configurations; +using System.Runtime.InteropServices; using Newtonsoft.Json; using WireMock.Net.Testcontainers; @@ -129,13 +131,16 @@ private static async Task TestCopyAsync() await container.StartAsync(); - try - { - await container.CopyAsync(@"C:\temp-wiremock\__admin\mappings\StefBodyAsFileExample.json", "/app/__admin/mappings"); - } - catch (Exception e) + if (await GetImageOSAsync.Value == OSPlatform.Linux) { - Console.WriteLine(e); + 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()); @@ -205,4 +210,18 @@ private static async Task TestAsync(string? image = null) 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.Testcontainers/Utils/PlatformUtils.cs b/src/WireMock.Net.Testcontainers/Utils/ContainerUtils.cs similarity index 68% rename from src/WireMock.Net.Testcontainers/Utils/PlatformUtils.cs rename to src/WireMock.Net.Testcontainers/Utils/ContainerUtils.cs index 2d832cfd..ce0d8113 100644 --- a/src/WireMock.Net.Testcontainers/Utils/PlatformUtils.cs +++ b/src/WireMock.Net.Testcontainers/Utils/ContainerUtils.cs @@ -7,23 +7,8 @@ namespace WireMock.Net.Testcontainers.Utils; -internal static class PlatformUtils +internal static class ContainerUtils { - public static OSPlatform GetCurrentOSPlatform() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return OSPlatform.Windows; - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return OSPlatform.Linux; - } - - throw new NotSupportedException("The current OSPlatform is not supported."); - } - public static Lazy> GetImageOSAsync = new(async () => { if (TestcontainersSettings.OS.DockerEndpointAuthConfig == null) diff --git a/src/WireMock.Net.Testcontainers/WireMockContainer.cs b/src/WireMock.Net.Testcontainers/WireMockContainer.cs index 56568d4d..35394dce 100644 --- a/src/WireMock.Net.Testcontainers/WireMockContainer.cs +++ b/src/WireMock.Net.Testcontainers/WireMockContainer.cs @@ -164,7 +164,7 @@ protected override ValueTask DisposeAsyncCore() private static async Task PathStartsWithContainerMappingsPath(string value) { - var imageOs = await PlatformUtils.GetImageOSAsync.Value; + var imageOs = await ContainerUtils.GetImageOSAsync.Value; return value.StartsWith(ContainerInfoProvider.Info[imageOs].MappingsPath); } diff --git a/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs b/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs index 83d2eafc..48c54eae 100644 --- a/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs +++ b/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs @@ -36,7 +36,7 @@ public WireMockContainerBuilder() : this(new WireMockConfiguration()) [PublicAPI] public WireMockContainerBuilder WithImage() { - _imageOS ??= PlatformUtils.GetImageOSAsync.Value.GetAwaiter().GetResult(); + _imageOS ??= ContainerUtils.GetImageOSAsync.Value.GetAwaiter().GetResult(); return WithImage(_imageOS.Value); } From b53bcd93bc226e09d56dfda5771d72fddfab5fdb Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 17 Oct 2024 18:53:32 +0200 Subject: [PATCH 13/13] 1.6.7-preview-03 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 49e65a08..ee5df795 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 1.6.7-preview-02 + 1.6.7-preview-03 WireMock.Net-Logo.png https://github.com/WireMock-Net/WireMock.Net Apache-2.0