From e4389333a1ea427844c2ed2cc626f8487af3d940 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 19 Sep 2026 07:54:10 +0200 Subject: [PATCH 01/12] 2.16.0 --- CHANGELOG.md | 8 ++++++++ Directory.Build.props | 4 ++-- Generate-ReleaseNotes.cmd | 2 +- PackageReleaseNotes.txt | 13 +++++++------ examples/WireMock.Net.Client/Program.cs | 2 -- .../Program.cs | 1 - examples/WireMock.Net.Console.NET8/MainApp.cs | 6 ------ .../WireMock.Net.Console.RequestLogTest/Program.cs | 4 ---- .../WireMock.Net.ConsoleApp.UsingNuGet.csproj | 2 +- .../Program.cs | 2 -- 10 files changed, 19 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb85de5e..cb987e46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 2.16.0 (19 September 2026) +- [#1502](https://github.com/wiremock/WireMock.Net/pull/1502) - Fix #1501: Generate WhenStateIs/WillSetStateTo correctly in ToCSharpCode contributed by [Bafyn](https://github.com/Bafyn) +- [#1503](https://github.com/wiremock/WireMock.Net/pull/1503) - Fix FormUrlEncodedMatcher (+ refactor values / matchers logic) contributed by [StefH](https://github.com/StefH) +- [#1504](https://github.com/wiremock/WireMock.Net/pull/1504) - Update SharpYaml package to version 2.5.1 [dependencies] contributed by [johnthcall](https://github.com/johnthcall) +- [#1505](https://github.com/wiremock/WireMock.Net/pull/1505) - Upgrade dependency RamlToOpenApiConverter to version 0.40.0 [dependencies] contributed by [StefH](https://github.com/StefH) +- [#1500](https://github.com/wiremock/WireMock.Net/issues/1500) - Sending a form-urlencoded body with a repeated field name returns HTTP 500 [bug] +- [#1501](https://github.com/wiremock/WireMock.Net/issues/1501) - Generated C# code for stateful (Scenario) mappings does not compile and drops the WhenStateIs condition [bug] + # 2.15.0 (15 August 2026) - [#1495](https://github.com/wiremock/WireMock.Net/pull/1495) - Fix #1494: `WithClientIP(MatchOperator, params IStringMatcher[])` ignores the MatchOperator argument [bug] contributed by [Bafyn](https://github.com/Bafyn) - [#1497](https://github.com/wiremock/WireMock.Net/pull/1497) - Fix WithParam RejectOnMatch inverting the match result #1496 [bug] contributed by [Bafyn](https://github.com/Bafyn) diff --git a/Directory.Build.props b/Directory.Build.props index 79945350..0b5500be 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 2.15.0 + 2.16.0 WireMock.Net-Logo.png https://github.com/wiremock/WireMock.Net Apache-2.0 @@ -52,7 +52,7 @@ - + diff --git a/Generate-ReleaseNotes.cmd b/Generate-ReleaseNotes.cmd index 58cbc00e..1fe834f1 100644 --- a/Generate-ReleaseNotes.cmd +++ b/Generate-ReleaseNotes.cmd @@ -1,6 +1,6 @@ rem https://github.com/StefH/GitHubReleaseNotes -SET version=2.15.0 +SET version=2.16.0 GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN% diff --git a/PackageReleaseNotes.txt b/PackageReleaseNotes.txt index 40d63159..97cd2708 100644 --- a/PackageReleaseNotes.txt +++ b/PackageReleaseNotes.txt @@ -1,8 +1,9 @@ -# 2.15.0 (15 August 2026) -- #1495 Fix #1494: `WithClientIP(MatchOperator, params IStringMatcher[])` ignores the MatchOperator argument [bug] -- #1497 Fix WithParam RejectOnMatch inverting the match result #1496 [bug] -- #1499 Update Testcontainers [bug] -- #1494 `WithClientIP(MatchOperator, params IStringMatcher[])` ignores the `MatchOperator` argument [bug] -- #1496 `WithParam(key, MatchBehaviour.RejectOnMatch, values)` inverts the match result [bug] +# 2.16.0 (19 September 2026) +- #1502 Fix #1501: Generate WhenStateIs/WillSetStateTo correctly in ToCSharpCode +- #1503 Fix FormUrlEncodedMatcher (+ refactor values / matchers logic) +- #1504 Update SharpYaml package to version 2.5.1 [dependencies] +- #1505 Upgrade dependency RamlToOpenApiConverter to version 0.40.0 [dependencies] +- #1500 Sending a form-urlencoded body with a repeated field name returns HTTP 500 [bug] +- #1501 Generated C# code for stateful (Scenario) mappings does not compile and drops the WhenStateIs condition [bug] The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md \ No newline at end of file diff --git a/examples/WireMock.Net.Client/Program.cs b/examples/WireMock.Net.Client/Program.cs index f9651fbc..0f0c2f78 100644 --- a/examples/WireMock.Net.Client/Program.cs +++ b/examples/WireMock.Net.Client/Program.cs @@ -1,9 +1,7 @@ // Copyright © WireMock.Net -using System; using System.Net.Http.Headers; using System.Text; -using System.Threading.Tasks; using Newtonsoft.Json; using RestEase; using WireMock.Admin.Settings; diff --git a/examples/WireMock.Net.Console.NET8.WithCertificate/Program.cs b/examples/WireMock.Net.Console.NET8.WithCertificate/Program.cs index c81eca17..a1488ea6 100644 --- a/examples/WireMock.Net.Console.NET8.WithCertificate/Program.cs +++ b/examples/WireMock.Net.Console.NET8.WithCertificate/Program.cs @@ -1,6 +1,5 @@ // Copyright © WireMock.Net -using System.IO; using WireMock.Logging; using WireMock.Server; using WireMock.Settings; diff --git a/examples/WireMock.Net.Console.NET8/MainApp.cs b/examples/WireMock.Net.Console.NET8/MainApp.cs index 9023b526..952a65e1 100644 --- a/examples/WireMock.Net.Console.NET8/MainApp.cs +++ b/examples/WireMock.Net.Console.NET8/MainApp.cs @@ -1,14 +1,8 @@ // Copyright © WireMock.Net -using System; -using System.Collections.Generic; using System.Globalization; -using System.IO; -using System.Linq; using System.Net; -using System.Threading.Tasks; using Newtonsoft.Json; -using SharpYaml.Model; using WireMock.Logging; using WireMock.Matchers; using WireMock.Models; diff --git a/examples/WireMock.Net.Console.RequestLogTest/Program.cs b/examples/WireMock.Net.Console.RequestLogTest/Program.cs index d1aeab3b..dbcdc04a 100644 --- a/examples/WireMock.Net.Console.RequestLogTest/Program.cs +++ b/examples/WireMock.Net.Console.RequestLogTest/Program.cs @@ -1,9 +1,5 @@ // Copyright © WireMock.Net -using System; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; using WireMock.Logging; using WireMock.Net.StandAlone; using WireMock.RequestBuilders; diff --git a/examples/WireMock.Net.ConsoleApp.UsingNuGet/WireMock.Net.ConsoleApp.UsingNuGet.csproj b/examples/WireMock.Net.ConsoleApp.UsingNuGet/WireMock.Net.ConsoleApp.UsingNuGet.csproj index 0836b3a1..f37fe696 100644 --- a/examples/WireMock.Net.ConsoleApp.UsingNuGet/WireMock.Net.ConsoleApp.UsingNuGet.csproj +++ b/examples/WireMock.Net.ConsoleApp.UsingNuGet/WireMock.Net.ConsoleApp.UsingNuGet.csproj @@ -16,7 +16,7 @@ - + \ No newline at end of file diff --git a/examples/WireMock.Net.OpenApiParser.ConsoleApp/Program.cs b/examples/WireMock.Net.OpenApiParser.ConsoleApp/Program.cs index 13230f90..4b0cc347 100644 --- a/examples/WireMock.Net.OpenApiParser.ConsoleApp/Program.cs +++ b/examples/WireMock.Net.OpenApiParser.ConsoleApp/Program.cs @@ -1,7 +1,5 @@ // Copyright © WireMock.Net -using System; -using System.IO; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; From 7db5b562d8d0f36e7f35b44c9efb1fc6fc51903e Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Mon, 21 Sep 2026 20:50:49 +0200 Subject: [PATCH 02/12] Revert to RamlToOpenApiConverter.SourceOnly (#1509) --- ...reMock.Net.OpenApiParser.ConsoleApp.csproj | 4 +- .../Extensions/DictionaryExtensions.cs | 3 -- .../Extensions/WireMockServerExtensions.cs | 2 +- .../ILRepack.targets | 29 ++++++++++++ .../IWireMockOpenApiParser.cs | 2 +- .../Models/OpenApiDiagnostic.cs | 25 ++++++++++ .../Models/OpenApiError.cs | 46 +++++++++++++++++++ .../Models/OpenApiMapper.cs | 23 ++++++++++ .../Models/OpenApiSpecVersion.cs | 29 ++++++++++++ .../IWireMockOpenApiParserExampleValues.cs | 1 - ...reMockOpenApiParserDynamicExampleValues.cs | 1 - .../WireMockOpenApiParserExampleValues.cs | 1 - .../Utils/DateTimeUtils.cs | 1 - .../Utils/ExampleValueGenerator.cs | 2 - .../WireMock.Net.OpenApiParser.csproj | 20 +++++++- .../WireMockOpenApiParser.cs | 4 +- 16 files changed, 176 insertions(+), 17 deletions(-) create mode 100644 src/WireMock.Net.OpenApiParser/ILRepack.targets create mode 100644 src/WireMock.Net.OpenApiParser/Models/OpenApiDiagnostic.cs create mode 100644 src/WireMock.Net.OpenApiParser/Models/OpenApiError.cs create mode 100644 src/WireMock.Net.OpenApiParser/Models/OpenApiMapper.cs create mode 100644 src/WireMock.Net.OpenApiParser/Models/OpenApiSpecVersion.cs diff --git a/examples/WireMock.Net.OpenApiParser.ConsoleApp/WireMock.Net.OpenApiParser.ConsoleApp.csproj b/examples/WireMock.Net.OpenApiParser.ConsoleApp/WireMock.Net.OpenApiParser.ConsoleApp.csproj index e35ba9fd..310ef063 100644 --- a/examples/WireMock.Net.OpenApiParser.ConsoleApp/WireMock.Net.OpenApiParser.ConsoleApp.csproj +++ b/examples/WireMock.Net.OpenApiParser.ConsoleApp/WireMock.Net.OpenApiParser.ConsoleApp.csproj @@ -6,15 +6,13 @@ - - - + diff --git a/src/WireMock.Net.OpenApiParser/Extensions/DictionaryExtensions.cs b/src/WireMock.Net.OpenApiParser/Extensions/DictionaryExtensions.cs index aec4ac2f..5a5ea0b8 100644 --- a/src/WireMock.Net.OpenApiParser/Extensions/DictionaryExtensions.cs +++ b/src/WireMock.Net.OpenApiParser/Extensions/DictionaryExtensions.cs @@ -1,8 +1,5 @@ // Copyright © WireMock.Net - -using System.Diagnostics.CodeAnalysis; - namespace System.Collections.Generic; internal static class DictionaryExtensions diff --git a/src/WireMock.Net.OpenApiParser/Extensions/WireMockServerExtensions.cs b/src/WireMock.Net.OpenApiParser/Extensions/WireMockServerExtensions.cs index 7e763b55..bde12912 100644 --- a/src/WireMock.Net.OpenApiParser/Extensions/WireMockServerExtensions.cs +++ b/src/WireMock.Net.OpenApiParser/Extensions/WireMockServerExtensions.cs @@ -1,8 +1,8 @@ // Copyright © WireMock.Net using JetBrains.Annotations; -using Microsoft.OpenApi.Reader; using Stef.Validation; +using WireMock.Net.OpenApiParser.Models; using WireMock.Net.OpenApiParser.Settings; using WireMock.Server; diff --git a/src/WireMock.Net.OpenApiParser/ILRepack.targets b/src/WireMock.Net.OpenApiParser/ILRepack.targets new file mode 100644 index 00000000..71bd22d4 --- /dev/null +++ b/src/WireMock.Net.OpenApiParser/ILRepack.targets @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/IWireMockOpenApiParser.cs b/src/WireMock.Net.OpenApiParser/IWireMockOpenApiParser.cs index 7c451558..c09fa6e7 100644 --- a/src/WireMock.Net.OpenApiParser/IWireMockOpenApiParser.cs +++ b/src/WireMock.Net.OpenApiParser/IWireMockOpenApiParser.cs @@ -1,7 +1,7 @@ // Copyright © WireMock.Net -using Microsoft.OpenApi.Reader; using WireMock.Admin.Mappings; +using WireMock.Net.OpenApiParser.Models; using WireMock.Net.OpenApiParser.Settings; namespace WireMock.Net.OpenApiParser; diff --git a/src/WireMock.Net.OpenApiParser/Models/OpenApiDiagnostic.cs b/src/WireMock.Net.OpenApiParser/Models/OpenApiDiagnostic.cs new file mode 100644 index 00000000..22093244 --- /dev/null +++ b/src/WireMock.Net.OpenApiParser/Models/OpenApiDiagnostic.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +namespace WireMock.Net.OpenApiParser.Models; + +/// +/// Object containing all diagnostic information related to Open API parsing. +/// +public class OpenApiDiagnostic +{ + /// + /// List of all errors. + /// + public List Errors { get; set; } = []; + + /// + /// List of all warnings + /// + public List Warnings { get; set; } = []; + + /// + /// Open API specification version of the document parsed. + /// + public OpenApiSpecVersion SpecificationVersion { get; set; } +} \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Models/OpenApiError.cs b/src/WireMock.Net.OpenApiParser/Models/OpenApiError.cs new file mode 100644 index 00000000..b3d8bbe0 --- /dev/null +++ b/src/WireMock.Net.OpenApiParser/Models/OpenApiError.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +namespace WireMock.Net.OpenApiParser.Models; + +/// +/// Error related to the Open API Document. +/// +public class OpenApiError +{ + /// + /// Initializes the class. + /// + public OpenApiError(string? pointer, string message) + { + Pointer = pointer; + Message = message; + } + + /// + /// Initializes a copy of an object + /// + public OpenApiError(OpenApiError error) + { + Pointer = error.Pointer; + Message = error.Message; + } + + /// + /// Message explaining the error. + /// + public string Message { get; set; } + + /// + /// Pointer to the location of the error. + /// + public string? Pointer { get; set; } + + /// + /// Gets the string representation of . + /// + public override string ToString() + { + return Message + (!string.IsNullOrEmpty(Pointer) ? " [" + Pointer + "]" : ""); + } +} \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Models/OpenApiMapper.cs b/src/WireMock.Net.OpenApiParser/Models/OpenApiMapper.cs new file mode 100644 index 00000000..b203c7f1 --- /dev/null +++ b/src/WireMock.Net.OpenApiParser/Models/OpenApiMapper.cs @@ -0,0 +1,23 @@ +// Copyright © WireMock.Net + +using MicrosoftOpenApiDiagnostic = Microsoft.OpenApi.Reader.OpenApiDiagnostic; + +namespace WireMock.Net.OpenApiParser.Models; + +internal static class OpenApiMapper +{ + internal static OpenApiDiagnostic? Map(MicrosoftOpenApiDiagnostic? openApiDiagnostic) + { + if (openApiDiagnostic == null) + { + return null; + } + + return new OpenApiDiagnostic + { + Errors = openApiDiagnostic.Errors.Select(e => new OpenApiError(e.Pointer, e.Message)).ToList(), + Warnings = openApiDiagnostic.Warnings.Select(e => new OpenApiError(e.Pointer, e.Message)).ToList(), + SpecificationVersion = (OpenApiSpecVersion)openApiDiagnostic.SpecificationVersion + }; + } +} \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Models/OpenApiSpecVersion.cs b/src/WireMock.Net.OpenApiParser/Models/OpenApiSpecVersion.cs new file mode 100644 index 00000000..31433dd4 --- /dev/null +++ b/src/WireMock.Net.OpenApiParser/Models/OpenApiSpecVersion.cs @@ -0,0 +1,29 @@ +// Copyright © WireMock.Net + +namespace WireMock.Net.OpenApiParser.Models; + +/// +/// Represents versions of OpenAPI specification. +/// +public enum OpenApiSpecVersion +{ + /// + /// Represents OpenAPI V2.0 spec + /// + OpenApi2_0, + + /// + /// Represents all patches of OpenAPI V3.0 spec (e.g. 3.0.0, 3.0.1) + /// + OpenApi3_0, + + /// + /// Represents OpenAPI V3.1 spec + /// + OpenApi3_1, + + /// + /// Represents OpenAPI V3.2 spec + /// + OpenApi3_2 +} \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Settings/IWireMockOpenApiParserExampleValues.cs b/src/WireMock.Net.OpenApiParser/Settings/IWireMockOpenApiParserExampleValues.cs index 6c3b3953..68946953 100644 --- a/src/WireMock.Net.OpenApiParser/Settings/IWireMockOpenApiParserExampleValues.cs +++ b/src/WireMock.Net.OpenApiParser/Settings/IWireMockOpenApiParserExampleValues.cs @@ -1,6 +1,5 @@ // Copyright © WireMock.Net -using System; using Microsoft.OpenApi; namespace WireMock.Net.OpenApiParser.Settings; diff --git a/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserDynamicExampleValues.cs b/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserDynamicExampleValues.cs index 0f27bf31..26ce5719 100644 --- a/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserDynamicExampleValues.cs +++ b/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserDynamicExampleValues.cs @@ -1,6 +1,5 @@ // Copyright © WireMock.Net -using System; using Microsoft.OpenApi; using RandomDataGenerator.FieldOptions; using RandomDataGenerator.Randomizers; diff --git a/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserExampleValues.cs b/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserExampleValues.cs index 5acec53a..132d86e1 100644 --- a/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserExampleValues.cs +++ b/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserExampleValues.cs @@ -1,6 +1,5 @@ // Copyright © WireMock.Net -using System; using Microsoft.OpenApi; namespace WireMock.Net.OpenApiParser.Settings; diff --git a/src/WireMock.Net.OpenApiParser/Utils/DateTimeUtils.cs b/src/WireMock.Net.OpenApiParser/Utils/DateTimeUtils.cs index e3178549..40ac3380 100644 --- a/src/WireMock.Net.OpenApiParser/Utils/DateTimeUtils.cs +++ b/src/WireMock.Net.OpenApiParser/Utils/DateTimeUtils.cs @@ -1,6 +1,5 @@ // Copyright © WireMock.Net -using System; using System.Globalization; namespace WireMock.Net.OpenApiParser.Utils; diff --git a/src/WireMock.Net.OpenApiParser/Utils/ExampleValueGenerator.cs b/src/WireMock.Net.OpenApiParser/Utils/ExampleValueGenerator.cs index 1ee9920f..6a09e874 100644 --- a/src/WireMock.Net.OpenApiParser/Utils/ExampleValueGenerator.cs +++ b/src/WireMock.Net.OpenApiParser/Utils/ExampleValueGenerator.cs @@ -1,7 +1,5 @@ // Copyright © WireMock.Net -using System; -using System.Linq; using System.Text.Json.Nodes; using Microsoft.OpenApi; using Stef.Validation; diff --git a/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj b/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj index 3a603126..14cffae4 100644 --- a/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj +++ b/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj @@ -23,7 +23,7 @@ true - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -40,11 +40,27 @@ - + + + + + + + + + + + + + + + + + diff --git a/src/WireMock.Net.OpenApiParser/WireMockOpenApiParser.cs b/src/WireMock.Net.OpenApiParser/WireMockOpenApiParser.cs index 5d675ef3..379f6576 100644 --- a/src/WireMock.Net.OpenApiParser/WireMockOpenApiParser.cs +++ b/src/WireMock.Net.OpenApiParser/WireMockOpenApiParser.cs @@ -9,7 +9,9 @@ using Microsoft.OpenApi.YamlReader; using RamlToOpenApiConverter; using WireMock.Admin.Mappings; using WireMock.Net.OpenApiParser.Mappers; +using WireMock.Net.OpenApiParser.Models; using WireMock.Net.OpenApiParser.Settings; +using OpenApiDiagnostic = WireMock.Net.OpenApiParser.Models.OpenApiDiagnostic; namespace WireMock.Net.OpenApiParser; @@ -125,7 +127,7 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser var result = OpenApiDocument.Load(memoryStream, settings: _readerSettings); - diagnostic = result.Diagnostic ?? new OpenApiDiagnostic(); + diagnostic = OpenApiMapper.Map(result.Diagnostic) ?? new OpenApiDiagnostic(); openApiDocument = result.Document; return openApiDocument != null && !diagnostic.Errors.Any(); } From 471745f704121187531869ff2adf88706585bdc6 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Mon, 21 Sep 2026 20:53:22 +0200 Subject: [PATCH 03/12] 2.17.0 --- CHANGELOG.md | 4 ++++ Directory.Build.props | 2 +- Generate-ReleaseNotes.cmd | 2 +- PackageReleaseNotes.txt | 10 +++------- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb987e46..1d1fed3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.17.0 (21 September 2026) +- [#1509](https://github.com/wiremock/WireMock.Net/pull/1509) - Revert to RamlToOpenApiConverter.SourceOnly [bug] contributed by [StefH](https://github.com/StefH) +- [#1508](https://github.com/wiremock/WireMock.Net/issues/1508) - Transitive Dependency Conflict with Microsoft.OpenApi in 2.16.0 [bug] + # 2.16.0 (19 September 2026) - [#1502](https://github.com/wiremock/WireMock.Net/pull/1502) - Fix #1501: Generate WhenStateIs/WillSetStateTo correctly in ToCSharpCode contributed by [Bafyn](https://github.com/Bafyn) - [#1503](https://github.com/wiremock/WireMock.Net/pull/1503) - Fix FormUrlEncodedMatcher (+ refactor values / matchers logic) contributed by [StefH](https://github.com/StefH) diff --git a/Directory.Build.props b/Directory.Build.props index 0b5500be..b5b4ccd8 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 2.16.0 + 2.17.0 WireMock.Net-Logo.png https://github.com/wiremock/WireMock.Net Apache-2.0 diff --git a/Generate-ReleaseNotes.cmd b/Generate-ReleaseNotes.cmd index 1fe834f1..d35cdffe 100644 --- a/Generate-ReleaseNotes.cmd +++ b/Generate-ReleaseNotes.cmd @@ -1,6 +1,6 @@ rem https://github.com/StefH/GitHubReleaseNotes -SET version=2.16.0 +SET version=2.17.0 GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN% diff --git a/PackageReleaseNotes.txt b/PackageReleaseNotes.txt index 97cd2708..9425a8e0 100644 --- a/PackageReleaseNotes.txt +++ b/PackageReleaseNotes.txt @@ -1,9 +1,5 @@ -# 2.16.0 (19 September 2026) -- #1502 Fix #1501: Generate WhenStateIs/WillSetStateTo correctly in ToCSharpCode -- #1503 Fix FormUrlEncodedMatcher (+ refactor values / matchers logic) -- #1504 Update SharpYaml package to version 2.5.1 [dependencies] -- #1505 Upgrade dependency RamlToOpenApiConverter to version 0.40.0 [dependencies] -- #1500 Sending a form-urlencoded body with a repeated field name returns HTTP 500 [bug] -- #1501 Generated C# code for stateful (Scenario) mappings does not compile and drops the WhenStateIs condition [bug] +# 2.17.0 (21 September 2026) +- #1509 Revert to RamlToOpenApiConverter.SourceOnly [bug] +- #1508 Transitive Dependency Conflict with Microsoft.OpenApi in 2.16.0 [bug] The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md \ No newline at end of file From 90819f031ed3aeb3df46448a1a406d60186971b1 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 22 Sep 2026 22:16:13 +0200 Subject: [PATCH 04/12] WireMock.Net.OpenApiParser: fix depencency on RamlToOpenApiConverter.SourceOnly (#1510) --- .../WireMock.Net.OpenApiParser.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj b/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj index 14cffae4..6e20755f 100644 --- a/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj +++ b/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj @@ -46,7 +46,7 @@ - + From f19d94acaec0db1e2ab5a9a0b04d2762791f4668 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 22 Sep 2026 22:18:47 +0200 Subject: [PATCH 05/12] 2.18.0 --- CHANGELOG.md | 4 +++- Directory.Build.props | 2 +- Generate-ReleaseNotes.cmd | 2 +- PackageReleaseNotes.txt | 5 ++--- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d1fed3b..2f67f505 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ +# 2.18.0 (22 September 2026) +- [#1510](https://github.com/wiremock/WireMock.Net/pull/1510) - WireMock.Net.OpenApiParser: fix depencency on RamlToOpenApiConverter.SourceOnly [bug] contributed by [StefH](https://github.com/StefH) + # 2.17.0 (21 September 2026) - [#1509](https://github.com/wiremock/WireMock.Net/pull/1509) - Revert to RamlToOpenApiConverter.SourceOnly [bug] contributed by [StefH](https://github.com/StefH) -- [#1508](https://github.com/wiremock/WireMock.Net/issues/1508) - Transitive Dependency Conflict with Microsoft.OpenApi in 2.16.0 [bug] # 2.16.0 (19 September 2026) - [#1502](https://github.com/wiremock/WireMock.Net/pull/1502) - Fix #1501: Generate WhenStateIs/WillSetStateTo correctly in ToCSharpCode contributed by [Bafyn](https://github.com/Bafyn) diff --git a/Directory.Build.props b/Directory.Build.props index b5b4ccd8..dceb15ad 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 2.17.0 + 2.18.0 WireMock.Net-Logo.png https://github.com/wiremock/WireMock.Net Apache-2.0 diff --git a/Generate-ReleaseNotes.cmd b/Generate-ReleaseNotes.cmd index d35cdffe..fd715f52 100644 --- a/Generate-ReleaseNotes.cmd +++ b/Generate-ReleaseNotes.cmd @@ -1,6 +1,6 @@ rem https://github.com/StefH/GitHubReleaseNotes -SET version=2.17.0 +SET version=2.18.0 GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN% diff --git a/PackageReleaseNotes.txt b/PackageReleaseNotes.txt index 9425a8e0..bda9c060 100644 --- a/PackageReleaseNotes.txt +++ b/PackageReleaseNotes.txt @@ -1,5 +1,4 @@ -# 2.17.0 (21 September 2026) -- #1509 Revert to RamlToOpenApiConverter.SourceOnly [bug] -- #1508 Transitive Dependency Conflict with Microsoft.OpenApi in 2.16.0 [bug] +# 2.18.0 (22 September 2026) +- #1510 WireMock.Net.OpenApiParser: fix depencency on RamlToOpenApiConverter.SourceOnly [bug] The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md \ No newline at end of file From dc4cdfae507bcb41205eb79e96c0eb59c5fedc06 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 22 Sep 2026 22:21:50 +0200 Subject: [PATCH 06/12] 2.18.0 --- CHANGELOG.md | 1 + PackageReleaseNotes.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f67f505..62a15b86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # 2.18.0 (22 September 2026) - [#1510](https://github.com/wiremock/WireMock.Net/pull/1510) - WireMock.Net.OpenApiParser: fix depencency on RamlToOpenApiConverter.SourceOnly [bug] contributed by [StefH](https://github.com/StefH) +- [#1508](https://github.com/wiremock/WireMock.Net/issues/1508) - Transitive Dependency Conflict with Microsoft.OpenApi in 2.16.0 [bug] # 2.17.0 (21 September 2026) - [#1509](https://github.com/wiremock/WireMock.Net/pull/1509) - Revert to RamlToOpenApiConverter.SourceOnly [bug] contributed by [StefH](https://github.com/StefH) diff --git a/PackageReleaseNotes.txt b/PackageReleaseNotes.txt index bda9c060..8b68a7f7 100644 --- a/PackageReleaseNotes.txt +++ b/PackageReleaseNotes.txt @@ -1,4 +1,5 @@ # 2.18.0 (22 September 2026) - #1510 WireMock.Net.OpenApiParser: fix depencency on RamlToOpenApiConverter.SourceOnly [bug] +- #1508 Transitive Dependency Conflict with Microsoft.OpenApi in 2.16.0 [bug] The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md \ No newline at end of file From b0f9924933f1d62cbaa87259576f20d64d91bbc3 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 6 Oct 2026 22:51:17 +0200 Subject: [PATCH 07/12] Add example /test (#1513) --- examples/WireMock.Net.Console.NET8/MainApp.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/examples/WireMock.Net.Console.NET8/MainApp.cs b/examples/WireMock.Net.Console.NET8/MainApp.cs index 952a65e1..11a2b5e1 100644 --- a/examples/WireMock.Net.Console.NET8/MainApp.cs +++ b/examples/WireMock.Net.Console.NET8/MainApp.cs @@ -285,11 +285,21 @@ namespace WireMock.Net.ConsoleApplication var server = WireMockServer.Start(new WireMockServerSettings { + StartAdminInterface = true, Logger = new WireMockConsoleLogger(), - Port = 9091 }); + server + .WhenRequest(r => r + .WithPath("/test") + .UsingAnyMethod() + ) + .ThenRespondWith(r => r + .WithStatusCode(HttpStatusCode.OK) + .WithBody("OK") + ); + server .WhenRequest(r => r .WithPath("/Content-Length") From dd762f218b27e2c950ca35c0b5ed7aea446164c7 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 6 Oct 2026 22:58:33 +0200 Subject: [PATCH 08/12] Use IDateTimeUtils in WireMockWebSocketContext (#1512) --- .../ResponseProviders/WebSocketResponseProvider.cs | 3 ++- .../WebSockets/WireMockWebSocketContext.cs | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/WireMock.Net.Minimal/ResponseProviders/WebSocketResponseProvider.cs b/src/WireMock.Net.Minimal/ResponseProviders/WebSocketResponseProvider.cs index 64460954..23a03bc6 100644 --- a/src/WireMock.Net.Minimal/ResponseProviders/WebSocketResponseProvider.cs +++ b/src/WireMock.Net.Minimal/ResponseProviders/WebSocketResponseProvider.cs @@ -67,7 +67,8 @@ internal class WebSocketResponseProvider(WebSocketBuilder builder, IGuidUtils gu builder, options, logger, - guidUtils + guidUtils, + dateTimeUtils ); // Add to registry diff --git a/src/WireMock.Net.Minimal/WebSockets/WireMockWebSocketContext.cs b/src/WireMock.Net.Minimal/WebSockets/WireMockWebSocketContext.cs index 5486c292..e5a9d380 100644 --- a/src/WireMock.Net.Minimal/WebSockets/WireMockWebSocketContext.cs +++ b/src/WireMock.Net.Minimal/WebSockets/WireMockWebSocketContext.cs @@ -18,6 +18,8 @@ namespace WireMock.WebSockets; /// public class WireMockWebSocketContext : IWebSocketContext { + private readonly IDateTimeUtils _dateTimeUtils; + /// public Guid ConnectionId { get; } @@ -50,7 +52,8 @@ public class WireMockWebSocketContext : IWebSocketContext WebSocketBuilder builder, IWireMockMiddlewareOptions options, IWireMockMiddlewareLogger logger, - IGuidUtils guidUtils + IGuidUtils guidUtils, + IDateTimeUtils dateTimeUtils ) { HttpContext = httpContext; @@ -61,6 +64,7 @@ public class WireMockWebSocketContext : IWebSocketContext Builder = builder; Options = options; Logger = logger; + _dateTimeUtils = dateTimeUtils; ConnectionId = guidUtils.NewGuid(); } @@ -169,7 +173,7 @@ public class WireMockWebSocketContext : IWebSocketContext null ) { - DateTime = DateTime.UtcNow + DateTime = _dateTimeUtils.UtcNow }; } else @@ -179,7 +183,7 @@ public class WireMockWebSocketContext : IWebSocketContext { Method = method, BodyData = bodyData, - DateTime = DateTime.UtcNow + DateTime = _dateTimeUtils.UtcNow }; } From cb4503b1a628d44a0c0d95c8b7dc03e707d300e0 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 7 Oct 2026 17:35:58 +0200 Subject: [PATCH 09/12] Fix DateTime in Response (#1514) * Fix DateTime in Response * // Set the DateTime from the Response to the current DateTime --------- Co-authored-by: Stef Heyenrath --- .../IRequestMessage.cs | 2 +- .../IResponseMessage.cs | 2 +- .../Extensions/HttpContextExtensions.cs | 14 +++++++++ .../Owin/Mappers/OwinRequestMapper.cs | 4 +-- .../Owin/WireMockMiddleware.cs | 8 ++++- .../Owin/WireMockMiddlewareLogger.cs | 6 ++-- .../ResponseBuilders/Response.cs | 2 +- src/WireMock.Net.Minimal/ResponseMessage.cs | 1 - .../Facts/RunOnDockerFact.cs | 29 +++++++++++++++++++ .../Facts/RunOnDockerPlatformFact.cs | 14 ++++++++- .../Owin/WireMockMiddlewareTests.cs | 11 ++++++- .../Testcontainers/TestcontainersTests.cs | 6 ++-- .../Testcontainers/TestcontainersTestsGrpc.cs | 11 +++---- .../WireMockServer.Proxy.cs | 1 + .../WireMockServer.Proxy2.cs | 4 +-- 15 files changed, 92 insertions(+), 23 deletions(-) create mode 100644 src/WireMock.Net.Minimal/Extensions/HttpContextExtensions.cs create mode 100644 test/WireMock.Net.Tests/Facts/RunOnDockerFact.cs diff --git a/src/WireMock.Net.Abstractions/IRequestMessage.cs b/src/WireMock.Net.Abstractions/IRequestMessage.cs index 02a23b63..c60fbe8f 100644 --- a/src/WireMock.Net.Abstractions/IRequestMessage.cs +++ b/src/WireMock.Net.Abstractions/IRequestMessage.cs @@ -32,7 +32,7 @@ public interface IRequestMessage string? ProxyUrl { get; set; } /// - /// Gets the DateTime. + /// The DateTime when this request was done. /// DateTime DateTime { get; } diff --git a/src/WireMock.Net.Abstractions/IResponseMessage.cs b/src/WireMock.Net.Abstractions/IResponseMessage.cs index 8abf4114..4993a649 100644 --- a/src/WireMock.Net.Abstractions/IResponseMessage.cs +++ b/src/WireMock.Net.Abstractions/IResponseMessage.cs @@ -52,7 +52,7 @@ public interface IResponseMessage object? StatusCode { get; set; } /// - /// Gets the DateTime. + /// The DateTime when this response was sent. /// DateTime DateTime { get; } diff --git a/src/WireMock.Net.Minimal/Extensions/HttpContextExtensions.cs b/src/WireMock.Net.Minimal/Extensions/HttpContextExtensions.cs new file mode 100644 index 00000000..07ca7b81 --- /dev/null +++ b/src/WireMock.Net.Minimal/Extensions/HttpContextExtensions.cs @@ -0,0 +1,14 @@ +// Copyright © WireMock.Net + +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Http; + +namespace WireMock.Extensions; + +internal static class HttpContextExtensions +{ + internal static bool TryGet(this HttpContext httpContext, [NotNullWhen(true)] out T? value) + { + return httpContext.Items.TryGetValue(nameof(T), out value); + } +} \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Owin/Mappers/OwinRequestMapper.cs b/src/WireMock.Net.Minimal/Owin/Mappers/OwinRequestMapper.cs index 0db51eb4..a0d2c3c7 100644 --- a/src/WireMock.Net.Minimal/Owin/Mappers/OwinRequestMapper.cs +++ b/src/WireMock.Net.Minimal/Owin/Mappers/OwinRequestMapper.cs @@ -12,7 +12,7 @@ namespace WireMock.Owin.Mappers; /// /// OwinRequestMapper /// -internal class OwinRequestMapper : IOwinRequestMapper +internal class OwinRequestMapper(IDateTimeUtils dateTimeUtils) : IOwinRequestMapper { /// public async Task MapAsync(HttpContext context, IWireMockMiddlewareOptions options) @@ -72,7 +72,7 @@ internal class OwinRequestMapper : IOwinRequestMapper await request.HttpContext.Connection.GetClientCertificateAsync() ) { - DateTime = DateTime.UtcNow + DateTime = dateTimeUtils.UtcNow }; } diff --git a/src/WireMock.Net.Minimal/Owin/WireMockMiddleware.cs b/src/WireMock.Net.Minimal/Owin/WireMockMiddleware.cs index bd193e11..89528b3f 100644 --- a/src/WireMock.Net.Minimal/Owin/WireMockMiddleware.cs +++ b/src/WireMock.Net.Minimal/Owin/WireMockMiddleware.cs @@ -171,6 +171,12 @@ internal class WireMockMiddleware( } finally { + // Set the DateTime from the Response to the current DateTime + if (response is ResponseMessage responseMessage) + { + responseMessage.DateTime = dateTimeUtils.UtcNow; + } + logger.LogRequestAndResponse(logRequest, request, response, result.Match, result.Partial, activity); try @@ -255,4 +261,4 @@ internal class WireMockMiddleware( scenario.Finished = mapping.NextState == null; }); } -} +} \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Owin/WireMockMiddlewareLogger.cs b/src/WireMock.Net.Minimal/Owin/WireMockMiddlewareLogger.cs index 2f35b2ce..d44eaf7d 100644 --- a/src/WireMock.Net.Minimal/Owin/WireMockMiddlewareLogger.cs +++ b/src/WireMock.Net.Minimal/Owin/WireMockMiddlewareLogger.cs @@ -73,20 +73,20 @@ internal class WireMockMiddlewareLogger( var logEntries = options.LogEntries.ToList(); foreach (var logEntry in logEntries - .OrderBy(le => le.RequestMessage?.DateTime ?? le.ResponseMessage?.DateTime) + .OrderBy(le => le.RequestMessage?.DateTime) .Take(logEntries.Count - options.MaxRequestLogCount.Value)) { TryRemoveLogEntry(logEntry); } } - // In case RequestLogExpirationDuration has a value greater than 0, try to delete existing request logs based on the date. + // In case RequestLogExpirationDuration has a value greater than 0, try to delete existing request logs based on the RequestMessage date. if (options.RequestLogExpirationDuration is > 0) { var logEntries = options.LogEntries.ToList(); var checkTime = dateTimeUtils.UtcNow.AddHours(-options.RequestLogExpirationDuration.Value); - foreach (var logEntry in logEntries.Where(le => le.RequestMessage?.DateTime < checkTime || le.ResponseMessage?.DateTime < checkTime)) + foreach (var logEntry in logEntries.Where(le => le.RequestMessage?.DateTime < checkTime)) { TryRemoveLogEntry(logEntry); } diff --git a/src/WireMock.Net.Minimal/ResponseBuilders/Response.cs b/src/WireMock.Net.Minimal/ResponseBuilders/Response.cs index 60151c0e..b14631f5 100644 --- a/src/WireMock.Net.Minimal/ResponseBuilders/Response.cs +++ b/src/WireMock.Net.Minimal/ResponseBuilders/Response.cs @@ -87,7 +87,7 @@ public partial class Response : IResponseBuilder { var message = responseMessage ?? new ResponseMessage { - DateTime = DateTime.UtcNow // We set it here to the current time. + DateTime = DateTime.UtcNow // Initially, this is set to the current time, but will be updated to the real time once the response has been sent. }; return new Response(message); diff --git a/src/WireMock.Net.Minimal/ResponseMessage.cs b/src/WireMock.Net.Minimal/ResponseMessage.cs index 1b5d3f9c..70ae16c1 100644 --- a/src/WireMock.Net.Minimal/ResponseMessage.cs +++ b/src/WireMock.Net.Minimal/ResponseMessage.cs @@ -2,7 +2,6 @@ // This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. // For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. -using System.Linq; using Stef.Validation; using WireMock.ResponseBuilders; using WireMock.Types; diff --git a/test/WireMock.Net.Tests/Facts/RunOnDockerFact.cs b/test/WireMock.Net.Tests/Facts/RunOnDockerFact.cs new file mode 100644 index 00000000..dd179b1c --- /dev/null +++ b/test/WireMock.Net.Tests/Facts/RunOnDockerFact.cs @@ -0,0 +1,29 @@ +// Copyright © WireMock.Net +#if NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using WireMock.Net.Testcontainers.Utils; + +namespace WireMock.Net.Tests.Facts; + +[ExcludeFromCodeCoverage] +public sealed class RunOnDockerFact : FactAttribute +{ + public RunOnDockerFact( + [CallerFilePath] string? sourceFilePath = null, + [CallerLineNumber] int sourceLineNumber = -1) : base(sourceFilePath, sourceLineNumber) + { + + OSPlatform currentPlatform; + try + { + currentPlatform = TestcontainersUtils.GetImageOSAsync.Value.Result; + } + catch + { + Skip = $"Only run test when Docker is installed."; + } + } +} +#endif \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Facts/RunOnDockerPlatformFact.cs b/test/WireMock.Net.Tests/Facts/RunOnDockerPlatformFact.cs index d12cdb4f..ee6980f1 100644 --- a/test/WireMock.Net.Tests/Facts/RunOnDockerPlatformFact.cs +++ b/test/WireMock.Net.Tests/Facts/RunOnDockerPlatformFact.cs @@ -15,7 +15,19 @@ public sealed class RunOnDockerPlatformFact : FactAttribute [CallerFilePath] string? sourceFilePath = null, [CallerLineNumber] int sourceLineNumber = -1) : base(sourceFilePath, sourceLineNumber) { - if (TestcontainersUtils.GetImageOSAsync.Value.Result != OSPlatform.Create(platform)) + + OSPlatform currentPlatform; + try + { + currentPlatform = TestcontainersUtils.GetImageOSAsync.Value.Result; + } + catch + { + Skip = $"Only run test when Docker is installed."; + return; + } + + if (currentPlatform != OSPlatform.Create(platform)) { Skip = $"Only run test when Docker OS Platform {platform} is used."; } diff --git a/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs b/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs index fd149196..d25de1a0 100644 --- a/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs +++ b/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs @@ -106,6 +106,15 @@ public class WireMockMiddlewareTests [Fact] public async Task WireMockMiddleware_Invoke_NoMatch() { + // Arrange + var inititalDateTime = new DateTime(2027, 1, 1, 0, 0, 0, DateTimeKind.Utc); + var responseDateTime = new DateTime(2027, 1, 1, 0, 0, 42, DateTimeKind.Utc); + + _dateTimeUtilsMock.Reset(); + _dateTimeUtilsMock.SetupSequence(d => d.UtcNow) + .Returns(inititalDateTime) + .Returns(responseDateTime); + // Act await _sut.Invoke(_contextMock.Object); @@ -115,7 +124,7 @@ public class WireMockMiddlewareTests Expression> match = r => (int)r.StatusCode! == 404 && ((StatusModel)r.BodyData!.BodyAsJson!).Status == "No matching mapping found" && - r.DateTime == UtcNow; + r.DateTime == responseDateTime; _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); } diff --git a/test/WireMock.Net.Tests/Testcontainers/TestcontainersTests.cs b/test/WireMock.Net.Tests/Testcontainers/TestcontainersTests.cs index 2de6a1a1..ace85d23 100644 --- a/test/WireMock.Net.Tests/Testcontainers/TestcontainersTests.cs +++ b/test/WireMock.Net.Tests/Testcontainers/TestcontainersTests.cs @@ -21,7 +21,7 @@ public class TestcontainersTests(ITestOutputHelper testOutputHelper) TimestampFormat = "yyy-MM-dd HH:mm:ss.fff" }); - [Fact] + [RunOnDockerFact] public async Task WireMockContainer_Build_And_StartAsync_and_StopAsync() { // Act @@ -57,7 +57,7 @@ public class TestcontainersTests(ITestOutputHelper testOutputHelper) await StopAsync(wireMockContainer); } - [Fact] + [RunOnDockerFact] public async Task WireMockContainer_Build_WithImage_And_StartAsync_and_StopAsync() { // Arrange @@ -83,7 +83,7 @@ public class TestcontainersTests(ITestOutputHelper testOutputHelper) await StopAsync(wireMockContainer); } - [Fact] + [RunOnDockerFact] public async Task WireMockContainer_Build_WithImageAsText_And_StartAsync_and_StopAsync() { // Arrange diff --git a/test/WireMock.Net.Tests/Testcontainers/TestcontainersTestsGrpc.cs b/test/WireMock.Net.Tests/Testcontainers/TestcontainersTestsGrpc.cs index e01655c4..05a60757 100644 --- a/test/WireMock.Net.Tests/Testcontainers/TestcontainersTestsGrpc.cs +++ b/test/WireMock.Net.Tests/Testcontainers/TestcontainersTestsGrpc.cs @@ -10,6 +10,7 @@ using Meziantou.Extensions.Logging.Xunit.v3; using Microsoft.Extensions.Logging; using WireMock.Constants; using WireMock.Net.Testcontainers; +using WireMock.Net.Tests.Facts; using WireMock.Util; namespace WireMock.Net.Tests.Testcontainers; @@ -25,7 +26,7 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper) TimestampFormat = "yyy-MM-dd HH:mm:ss.fff" }); - [Fact] + [RunOnDockerFact] public async Task WireMockContainer_Build_Grpc_TestPortsAndUrls1() { // Arrange @@ -82,7 +83,7 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper) } } - [Fact] + [RunOnDockerFact] public async Task WireMockContainer_Build_Grpc_TestPortsAndUrls2() { // Arrange @@ -136,7 +137,7 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper) } } - [Fact] + [RunOnDockerFact] public async Task WireMockContainer_Build_Grpc_ProtoDefinitionFromJson_UsingGrpcGeneratedClient() { var wireMockContainer = await Given_WireMockContainerIsStartedForHttpAndGrpcAsync(); @@ -150,7 +151,7 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper) await StopAsync(wireMockContainer); } - [Fact] + [RunOnDockerFact] public async Task WireMockContainer_Build_Grpc_ProtoDefinitionAtServerLevel_UsingGrpcGeneratedClient() { var wireMockContainer = await Given_WireMockContainerWithProtoDefinitionAtServerLevelIsStartedForHttpAndGrpcAsync(); @@ -164,7 +165,7 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper) await StopAsync(wireMockContainer); } - [Fact] + [RunOnDockerFact] public async Task WireMockContainer_Build_Grpc_ProtoDefinitionAtServerLevel_UsingGrpcGeneratedClient_AndWithWatchStaticMappings() { var wireMockContainer = await Given_WireMockContainerWithProtoDefinitionAtServerLevelWithWatchStaticMappingsIsStartedForHttpAndGrpcAsync(); diff --git a/test/WireMock.Net.Tests/WireMockServer.Proxy.cs b/test/WireMock.Net.Tests/WireMockServer.Proxy.cs index df28f611..245940fe 100644 --- a/test/WireMock.Net.Tests/WireMockServer.Proxy.cs +++ b/test/WireMock.Net.Tests/WireMockServer.Proxy.cs @@ -18,6 +18,7 @@ using WireMock.Util; namespace WireMock.Net.Tests; +[Collection(nameof(WireMockServerProxyTests))] public class WireMockServerProxyTests { private readonly CancellationToken _ct = TestContext.Current.CancellationToken; diff --git a/test/WireMock.Net.Tests/WireMockServer.Proxy2.cs b/test/WireMock.Net.Tests/WireMockServer.Proxy2.cs index da27e226..ba77567a 100644 --- a/test/WireMock.Net.Tests/WireMockServer.Proxy2.cs +++ b/test/WireMock.Net.Tests/WireMockServer.Proxy2.cs @@ -45,6 +45,4 @@ public class WireMockServerProxy2Tests server.Dispose(); serverAsProxy.Dispose(); } -} - - +} \ No newline at end of file From a5589e1e2f70a996ea7e5004d9d8c88b1830e369 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 7 Oct 2026 18:22:51 +0200 Subject: [PATCH 10/12] 2.19.0 --- CHANGELOG.md | 5 +++++ Generate-ReleaseNotes.cmd | 2 +- PackageReleaseNotes.txt | 7 ++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62a15b86..dac4f24d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.19.0 (07 October 2026) +- [#1512](https://github.com/wiremock/WireMock.Net/pull/1512) - Use IDateTimeUtils in WireMockWebSocketContext [bug] contributed by [StefH](https://github.com/StefH) +- [#1514](https://github.com/wiremock/WireMock.Net/pull/1514) - Fix DateTime in Response [bug] contributed by [StefH](https://github.com/StefH) +- [#1511](https://github.com/wiremock/WireMock.Net/issues/1511) - Incorrect response datetime for mapped response [bug] + # 2.18.0 (22 September 2026) - [#1510](https://github.com/wiremock/WireMock.Net/pull/1510) - WireMock.Net.OpenApiParser: fix depencency on RamlToOpenApiConverter.SourceOnly [bug] contributed by [StefH](https://github.com/StefH) - [#1508](https://github.com/wiremock/WireMock.Net/issues/1508) - Transitive Dependency Conflict with Microsoft.OpenApi in 2.16.0 [bug] diff --git a/Generate-ReleaseNotes.cmd b/Generate-ReleaseNotes.cmd index fd715f52..19fad88e 100644 --- a/Generate-ReleaseNotes.cmd +++ b/Generate-ReleaseNotes.cmd @@ -1,6 +1,6 @@ rem https://github.com/StefH/GitHubReleaseNotes -SET version=2.18.0 +SET version=2.19.0 GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN% diff --git a/PackageReleaseNotes.txt b/PackageReleaseNotes.txt index 8b68a7f7..877be2da 100644 --- a/PackageReleaseNotes.txt +++ b/PackageReleaseNotes.txt @@ -1,5 +1,6 @@ -# 2.18.0 (22 September 2026) -- #1510 WireMock.Net.OpenApiParser: fix depencency on RamlToOpenApiConverter.SourceOnly [bug] -- #1508 Transitive Dependency Conflict with Microsoft.OpenApi in 2.16.0 [bug] +# 2.19.0 (07 October 2026) +- #1512 Use IDateTimeUtils in WireMockWebSocketContext [bug] +- #1514 Fix DateTime in Response [bug] +- #1511 Incorrect response datetime for mapped response [bug] The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md \ No newline at end of file From 64734c4f92685afdc4744f6b293a50b604f22053 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 7 Oct 2026 18:25:30 +0200 Subject: [PATCH 11/12] 2.19.0 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index dceb15ad..3c7ff66d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 2.18.0 + 2.19.0 WireMock.Net-Logo.png https://github.com/wiremock/WireMock.Net Apache-2.0 From 0132ec873a5963ca4592a41d2bf8af6b4f76b246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Sundstr=C3=B6m?= Date: Sun, 18 Oct 2026 09:29:11 +0200 Subject: [PATCH 12/12] Add ScribanRegistrationCallback to register custom Scriban functions (#1515) (#1516) - Add WireMockServerSettings.ScribanRegistrationCallback (Action?) so code-configured servers can add functions, objects or variables to Scriban and Scriban-DotLiquid templates. - Pass the settings to ScribanContextFactory, which invokes the callback with a new ScriptObject per transformer context. - Push the custom globals beneath the template model in ScribanContext so request, response, mapping and data take precedence; keep the original Template.Render call when no callback is configured. - Add the strongly typed WithScribanRegistration extension method. - Add a Scriban example to the .NET 8 console app. - Make the proxy tests use a local target server instead of www.google.com, and skip the IPv6 loopback test when the OS has no IPv6 support. Co-authored-by: Mattias <13809209+Quattordici@users.noreply.github.com> --- examples/WireMock.Net.Console.NET8/MainApp.cs | 15 ++ .../WireMockServerSettingsExtensions.cs | 40 +++++ .../Transformers/Scriban/ScribanContext.cs | 26 ++- .../Scriban/ScribanContextFactory.cs | 20 ++- .../Transformers/TransformerFactory.cs | 4 +- .../Settings/WireMockServerSettings.cs | 14 ++ .../ResponseWithScribanTests.cs | 47 +++++ .../WireMockServerSettingsExtensionsTests.cs | 82 +++++++++ .../Scriban/ScribanContextFactoryTests.cs | 64 ++++++- .../Scriban/ScribanContextTests.cs | 165 ++++++++++++++++++ .../WireMockServer.Proxy.cs | 37 +++- .../WireMockServer.Settings.cs | 33 ++++ .../WireMock.Net.Tests/WireMockServerTests.cs | 2 + 13 files changed, 527 insertions(+), 22 deletions(-) create mode 100644 src/WireMock.Net.Minimal/Settings/WireMockServerSettingsExtensions.cs create mode 100644 test/WireMock.Net.Tests/Settings/WireMockServerSettingsExtensionsTests.cs create mode 100644 test/WireMock.Net.Tests/Transformers/Scriban/ScribanContextTests.cs diff --git a/examples/WireMock.Net.Console.NET8/MainApp.cs b/examples/WireMock.Net.Console.NET8/MainApp.cs index 11a2b5e1..43fe8bb8 100644 --- a/examples/WireMock.Net.Console.NET8/MainApp.cs +++ b/examples/WireMock.Net.Console.NET8/MainApp.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Net; using Newtonsoft.Json; +using Scriban.Runtime; using WireMock.Logging; using WireMock.Matchers; using WireMock.Models; @@ -409,12 +410,26 @@ namespace WireMock.Net.ConsoleApplication // handlebarsContext.RegisterHelper(transformer.Name, transformer.Render); TODO }, + ScribanRegistrationCallback = (scriptObject, fileSystemHandler) => + { + var globals = (ScriptObject)scriptObject; + globals.Import("greet", new Func(name => $"Hello {name}")); + }, + // Uncomment below if you want to use the CustomFileSystemFileHandler // FileSystemHandler = new CustomFileSystemFileHandler() }); System.Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls)); server.SetBasicAuthentication("a", "b"); + + server + .Given(Request.Create().WithPath("/scriban/greet").UsingGet()) + .RespondWith(Response.Create() + .WithBody("{{ greet 'WireMock.Net' }}") + .WithTransformer(TransformerType.Scriban) + ); + //server.SetAzureADAuthentication(Environment.GetEnvironmentVariable("WIREMOCK_AAD_TENANT")!, "api://e083d51a-01a6-446c-8ad5-0c5c7f002208"); //var http = new HttpClient(); diff --git a/src/WireMock.Net.Minimal/Settings/WireMockServerSettingsExtensions.cs b/src/WireMock.Net.Minimal/Settings/WireMockServerSettingsExtensions.cs new file mode 100644 index 00000000..7e347be9 --- /dev/null +++ b/src/WireMock.Net.Minimal/Settings/WireMockServerSettingsExtensions.cs @@ -0,0 +1,40 @@ +// Copyright © WireMock.Net + +using JetBrains.Annotations; +using Scriban.Runtime; +using Stef.Validation; +using WireMock.Handlers; + +namespace WireMock.Settings; + +/// +/// Extension methods for . +/// +public static class WireMockServerSettingsExtensions +{ + /// + /// Adds a strongly typed Scriban registration to . + /// + /// + /// Members added to the are available as globals in Scriban and Scriban-DotLiquid templates. + /// When a callback is already configured, it is kept and invoked before . + /// + /// The settings. + /// The action which registers functions, objects or variables on the . + /// The same instance. + [PublicAPI] + public static WireMockServerSettings WithScribanRegistration(this WireMockServerSettings settings, Action registrationCallback) + { + Guard.NotNull(settings); + Guard.NotNull(registrationCallback); + + var existingCallback = settings.ScribanRegistrationCallback; + settings.ScribanRegistrationCallback = (scriptObject, fileSystemHandler) => + { + existingCallback?.Invoke(scriptObject, fileSystemHandler); + registrationCallback((ScriptObject)scriptObject, fileSystemHandler); + }; + + return settings; + } +} diff --git a/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContext.cs b/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContext.cs index c54febbf..ea21cdee 100644 --- a/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContext.cs +++ b/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContext.cs @@ -1,6 +1,7 @@ // Copyright © WireMock.Net using Scriban; +using Scriban.Runtime; using Stef.Validation; using WireMock.Handlers; using WireMock.Types; @@ -9,21 +10,40 @@ namespace WireMock.Transformers.Scriban; internal class ScribanContext : ITransformerContext { + private static readonly MemberRenamerDelegate MemberRenamer = member => member.Name; + private readonly TransformerType _transformerType; + private readonly ScriptObject? _customGlobals; public IFileSystemHandler FileSystemHandler { get; } - public ScribanContext(IFileSystemHandler fileSystemHandler, TransformerType transformerType) + public ScribanContext(IFileSystemHandler fileSystemHandler, TransformerType transformerType, ScriptObject? customGlobals = null) { FileSystemHandler = Guard.NotNull(fileSystemHandler); _transformerType = transformerType; + _customGlobals = customGlobals; } public string ParseAndRender(string text, object model) { - var template = _transformerType == TransformerType.ScribanDotLiquid ? Template.ParseLiquid(text) : Template.Parse(text); + var isLiquid = _transformerType == TransformerType.ScribanDotLiquid; + var template = isLiquid ? Template.ParseLiquid(text) : Template.Parse(text); - return template.Render(model, member => member.Name); + if (_customGlobals is null) + { + return template.Render(model, MemberRenamer); + } + + // Same as Template.Render(model, MemberRenamer), but with the custom globals pushed beneath the model so that the model takes precedence. + var context = isLiquid ? new LiquidTemplateContext() : new TemplateContext(); + context.MemberRenamer = MemberRenamer; + context.PushGlobal(_customGlobals); + + var modelGlobals = new ScriptObject(); + modelGlobals.Import(model, renamer: MemberRenamer); + context.PushGlobal(modelGlobals); + + return template.Render(context); } public object? ParseAndEvaluate(string text, object model) diff --git a/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContextFactory.cs b/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContextFactory.cs index 91db290c..1a829a5a 100644 --- a/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContextFactory.cs +++ b/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContextFactory.cs @@ -1,24 +1,32 @@ // Copyright © WireMock.Net -using WireMock.Handlers; -using WireMock.Types; +using Scriban.Runtime; using Stef.Validation; +using WireMock.Settings; +using WireMock.Types; namespace WireMock.Transformers.Scriban; internal class ScribanContextFactory : ITransformerContextFactory { - private readonly IFileSystemHandler _fileSystemHandler; + private readonly WireMockServerSettings _settings; private readonly TransformerType _transformerType; - public ScribanContextFactory(IFileSystemHandler fileSystemHandler, TransformerType transformerType) + public ScribanContextFactory(WireMockServerSettings settings, TransformerType transformerType) { - _fileSystemHandler = Guard.NotNull(fileSystemHandler); + _settings = Guard.NotNull(settings); _transformerType = Guard.Condition(transformerType, t => t is TransformerType.Scriban or TransformerType.ScribanDotLiquid); } public ITransformerContext Create() { - return new ScribanContext(_fileSystemHandler, _transformerType); + ScriptObject? customGlobals = null; + if (_settings.ScribanRegistrationCallback is { } registrationCallback) + { + customGlobals = new ScriptObject(); + registrationCallback(customGlobals, _settings.FileSystemHandler); + } + + return new ScribanContext(_settings.FileSystemHandler, _transformerType, customGlobals); } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Transformers/TransformerFactory.cs b/src/WireMock.Net.Minimal/Transformers/TransformerFactory.cs index c398d140..7d390274 100644 --- a/src/WireMock.Net.Minimal/Transformers/TransformerFactory.cs +++ b/src/WireMock.Net.Minimal/Transformers/TransformerFactory.cs @@ -19,8 +19,8 @@ internal static class TransformerFactory case TransformerType.Scriban: case TransformerType.ScribanDotLiquid: - var factoryDotLiquid = new ScribanContextFactory(settings.FileSystemHandler, transformerType); - return new Transformer(settings, factoryDotLiquid); + var factoryScriban = new ScribanContextFactory(settings, transformerType); + return new Transformer(settings, factoryScriban); default: throw new NotSupportedException($"{nameof(TransformerType)} '{transformerType}' is not supported."); diff --git a/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs b/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs index e11f630c..941120eb 100644 --- a/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs +++ b/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs @@ -194,6 +194,20 @@ public class WireMockServerSettings [JsonIgnore] public Action? HandlebarsRegistrationCallback { get; set; } + /// + /// Action which can be used to add additional Scriban registrations (functions, objects or variables). [Optional] + /// + /// + /// The first argument is a Scriban.Runtime.ScriptObject from the Scriban.Signed package. + /// It is typed as so that this package does not depend on Scriban. + /// Members added to it are available as globals in and templates. + /// On a name clash the template model (request, response, mapping and data) takes precedence. + /// The action is invoked each time a transformer context is created. + /// + [PublicAPI] + [JsonIgnore] + public Action? ScribanRegistrationCallback { get; set; } + /// /// Allow the usage of CSharpCodeMatcher (default is not allowed). /// diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithScribanTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithScribanTests.cs index f4a64c5a..2c628c37 100644 --- a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithScribanTests.cs +++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithScribanTests.cs @@ -8,6 +8,8 @@ using WireMock.Util; using WireMock.Handlers; using Moq; using Microsoft.AspNetCore.Http; +using Newtonsoft.Json; +using Scriban.Runtime; namespace WireMock.Net.Tests.ResponseBuilders; @@ -65,5 +67,50 @@ public class ResponseWithScribanTests // Assert response.Message.BodyData.BodyAsString.Should().Be("test http://localhost/foo /foo POSt"); } + + [Theory] + [InlineData(TransformerType.Scriban)] + [InlineData(TransformerType.ScribanDotLiquid)] + public async Task Response_ProvideResponse_WithScribanRegistrationCallback_UsesCustomFunctionInBodyAndHeaders(TransformerType transformerType) + { + // Arrange + _settings.ScribanRegistrationCallback = (scriptObject, _) => + ((ScriptObject)scriptObject).Import("fake_name", new Func(() => "Jane Doe")); + + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", ClientIp); + + var responseBuilder = Response.Create() + .WithBody("{{ fake_name }} {{ request.Path }}") + .WithHeader("x-name", "{{ fake_name }}") + .WithTransformer(transformerType); + + // Act + var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, Mock.Of(), request, _settings); + + // Assert + response.Message.BodyData!.BodyAsString.Should().Be("Jane Doe /foo"); + response.Message.Headers!["x-name"].Should().ContainSingle().Which.Should().Be("Jane Doe"); + } + + [Theory] + [InlineData(TransformerType.Scriban)] + [InlineData(TransformerType.ScribanDotLiquid)] + public async Task Response_ProvideResponse_WithScribanRegistrationCallback_UsesCustomFunctionInBodyAsJson(TransformerType transformerType) + { + // Arrange + _settings.WithScribanRegistration((scriptObject, _) => scriptObject.Import("fake_name", new Func(() => "Jane Doe"))); + + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", ClientIp); + + var responseBuilder = Response.Create() + .WithBodyAsJson(new { name = "{{ fake_name }}", path = "{{ request.Path }}" }) + .WithTransformer(transformerType); + + // Act + var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, Mock.Of(), request, _settings); + + // Assert + JsonConvert.SerializeObject(response.Message.BodyData!.BodyAsJson).Should().Be("{\"name\":\"Jane Doe\",\"path\":\"/foo\"}"); + } } diff --git a/test/WireMock.Net.Tests/Settings/WireMockServerSettingsExtensionsTests.cs b/test/WireMock.Net.Tests/Settings/WireMockServerSettingsExtensionsTests.cs new file mode 100644 index 00000000..c1fe17bd --- /dev/null +++ b/test/WireMock.Net.Tests/Settings/WireMockServerSettingsExtensionsTests.cs @@ -0,0 +1,82 @@ +// Copyright © WireMock.Net + +using Moq; +using Scriban.Runtime; +using WireMock.Handlers; +using WireMock.Settings; + +namespace WireMock.Net.Tests.Settings; + +public class WireMockServerSettingsExtensionsTests +{ + private readonly IFileSystemHandler _fileSystemHandler = Mock.Of(); + + [Fact] + public void WithScribanRegistration_SetsCallbackWhichPassesTypedScriptObjectAndFileSystemHandler() + { + // Arrange + ScriptObject? receivedScriptObject = null; + IFileSystemHandler? receivedFileSystemHandler = null; + var settings = new WireMockServerSettings(); + var scriptObject = new ScriptObject(); + + // Act + var result = settings.WithScribanRegistration((so, fs) => + { + receivedScriptObject = so; + receivedFileSystemHandler = fs; + }); + settings.ScribanRegistrationCallback!(scriptObject, _fileSystemHandler); + + // Assert + result.Should().BeSameAs(settings); + receivedScriptObject.Should().BeSameAs(scriptObject); + receivedFileSystemHandler.Should().BeSameAs(_fileSystemHandler); + } + + [Fact] + public void WithScribanRegistration_WithExistingCallback_InvokesBothInRegistrationOrder() + { + // Arrange + var calls = new List(); + var settings = new WireMockServerSettings + { + ScribanRegistrationCallback = (_, _) => calls.Add("existing") + }; + + // Act + settings + .WithScribanRegistration((_, _) => calls.Add("first")) + .WithScribanRegistration((_, _) => calls.Add("second")); + settings.ScribanRegistrationCallback!(new ScriptObject(), _fileSystemHandler); + + // Assert + calls.Should().Equal("existing", "first", "second"); + } + + [Fact] + public void WithScribanRegistration_WithNullSettings_ThrowsArgumentNullException() + { + // Arrange + WireMockServerSettings settings = null!; + + // Act + Action act = () => settings.WithScribanRegistration((_, _) => { }); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void WithScribanRegistration_WithNullCallback_ThrowsArgumentNullException() + { + // Arrange + var settings = new WireMockServerSettings(); + + // Act + Action act = () => settings.WithScribanRegistration(null!); + + // Assert + act.Should().Throw(); + } +} diff --git a/test/WireMock.Net.Tests/Transformers/Scriban/ScribanContextFactoryTests.cs b/test/WireMock.Net.Tests/Transformers/Scriban/ScribanContextFactoryTests.cs index 23aee732..52ad7b81 100644 --- a/test/WireMock.Net.Tests/Transformers/Scriban/ScribanContextFactoryTests.cs +++ b/test/WireMock.Net.Tests/Transformers/Scriban/ScribanContextFactoryTests.cs @@ -1,7 +1,9 @@ // Copyright © WireMock.Net using Moq; +using Scriban.Runtime; using WireMock.Handlers; +using WireMock.Settings; using WireMock.Transformers.Scriban; using WireMock.Types; @@ -17,23 +19,79 @@ namespace WireMock.Net.Tests.Transformers.Scriban public void Create_With_Scriban_TransformerType_Creates_ITransformerContext(TransformerType transformerType) { // Arrange - var sut = new ScribanContextFactory(_fileSystemHandlerMock.Object, transformerType); + var settings = new WireMockServerSettings + { + FileSystemHandler = _fileSystemHandlerMock.Object + }; + var sut = new ScribanContextFactory(settings, transformerType); // Act var result = sut.Create(); // Assert result.Should().NotBeNull(); + result.FileSystemHandler.Should().BeSameAs(_fileSystemHandlerMock.Object); } [Fact] public void Create_With_Invalid_TransformerType_Throws_Exception() { + // Arrange + var settings = new WireMockServerSettings + { + FileSystemHandler = _fileSystemHandlerMock.Object + }; + // Act - Action action = () => new ScribanContextFactory(_fileSystemHandlerMock.Object, TransformerType.Handlebars); + Action action = () => new ScribanContextFactory(settings, TransformerType.Handlebars); // Assert action.Should().Throw(); } + + [Theory] + [InlineData(TransformerType.Scriban)] + [InlineData(TransformerType.ScribanDotLiquid)] + public void Create_WithScribanRegistrationCallback_InvokesCallbackOnceWithScriptObjectAndFileSystemHandler(TransformerType transformerType) + { + // Arrange + var calls = new List<(object ScriptObject, IFileSystemHandler FileSystemHandler)>(); + var settings = new WireMockServerSettings + { + FileSystemHandler = _fileSystemHandlerMock.Object, + ScribanRegistrationCallback = (scriptObject, fileSystemHandler) => calls.Add((scriptObject, fileSystemHandler)) + }; + var sut = new ScribanContextFactory(settings, transformerType); + + // Act + var result = sut.Create(); + + // Assert + result.Should().NotBeNull(); + calls.Should().ContainSingle(); + calls[0].ScriptObject.Should().BeOfType(); + calls[0].FileSystemHandler.Should().BeSameAs(_fileSystemHandlerMock.Object); + } + + [Fact] + public void Create_CalledTwice_InvokesScribanRegistrationCallbackWithNewScriptObjectEachTime() + { + // Arrange + var scriptObjects = new List(); + var settings = new WireMockServerSettings + { + FileSystemHandler = _fileSystemHandlerMock.Object, + ScribanRegistrationCallback = (scriptObject, _) => scriptObjects.Add(scriptObject) + }; + var sut = new ScribanContextFactory(settings, TransformerType.Scriban); + + // Act + sut.Create(); + sut.Create(); + + // Assert + scriptObjects.Should().HaveCount(2); + scriptObjects[0].Should().NotBeSameAs(scriptObjects[1]); + } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Transformers/Scriban/ScribanContextTests.cs b/test/WireMock.Net.Tests/Transformers/Scriban/ScribanContextTests.cs new file mode 100644 index 00000000..a3f08e85 --- /dev/null +++ b/test/WireMock.Net.Tests/Transformers/Scriban/ScribanContextTests.cs @@ -0,0 +1,165 @@ +// Copyright © WireMock.Net + +using Moq; +using Scriban.Runtime; +using WireMock.Handlers; +using WireMock.Models; +using WireMock.Transformers; +using WireMock.Transformers.Scriban; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock.Net.Tests.Transformers.Scriban; + +public class ScribanContextTests +{ + private readonly IFileSystemHandler _fileSystemHandler = Mock.Of(); + + [Theory] + [InlineData(TransformerType.Scriban)] + [InlineData(TransformerType.ScribanDotLiquid)] + public void ParseAndRender_WithCustomFunction_RendersFunctionResult(TransformerType transformerType) + { + // Arrange + var customGlobals = new ScriptObject(); + customGlobals.Import("fake_name", new Func(() => "Jane Doe")); + var sut = new ScribanContext(_fileSystemHandler, transformerType, customGlobals); + + // Act + var result = sut.ParseAndRender("Hello {{ fake_name }} at {{ request.Path }}", CreateModel()); + + // Assert + result.Should().Be("Hello Jane Doe at /foo/bar"); + } + + [Fact] + public void ParseAndRender_Scriban_WithCustomFunctionWithArgument_RendersFunctionResult() + { + // Arrange + var customGlobals = new ScriptObject(); + customGlobals.Import("greet", new Func(name => $"Hello {name}")); + var sut = new ScribanContext(_fileSystemHandler, TransformerType.Scriban, customGlobals); + + // Act + var result = sut.ParseAndRender("{{ greet request.Method }}", CreateModel()); + + // Assert + result.Should().Be("Hello GET"); + } + + [Fact] + public void ParseAndRender_ScribanDotLiquid_WithCustomFunctionAsFilter_RendersFunctionResult() + { + // Arrange + var customGlobals = new ScriptObject(); + customGlobals.Import("greet", new Func(name => $"Hello {name}")); + var sut = new ScribanContext(_fileSystemHandler, TransformerType.ScribanDotLiquid, customGlobals); + + // Act + var result = sut.ParseAndRender("{{ request.Method | greet }}", CreateModel()); + + // Assert + result.Should().Be("Hello GET"); + } + + [Theory] + [InlineData(TransformerType.Scriban)] + [InlineData(TransformerType.ScribanDotLiquid)] + public void ParseAndRender_WithCustomGlobalNamedLikeModelMember_ModelTakesPrecedence(TransformerType transformerType) + { + // Arrange + var customGlobals = new ScriptObject(); + customGlobals.SetValue("request", "custom", false); + customGlobals.SetValue("other", "custom", false); + var sut = new ScribanContext(_fileSystemHandler, transformerType, customGlobals); + + // Act + var result = sut.ParseAndRender("{{ request.Path }} {{ other }}", CreateModel()); + + // Assert + result.Should().Be("/foo/bar custom"); + } + + [Theory] + [InlineData(TransformerType.Scriban)] + [InlineData(TransformerType.ScribanDotLiquid)] + public void ParseAndRender_WithCustomGlobalNamedLikeBuiltin_CustomGlobalTakesPrecedence(TransformerType transformerType) + { + // Arrange + var customGlobals = new ScriptObject(); + customGlobals.SetValue("date", "custom date", false); + var sut = new ScribanContext(_fileSystemHandler, transformerType, customGlobals); + + // Act + var result = sut.ParseAndRender("{{ date }}", CreateModel()); + + // Assert + result.Should().Be("custom date"); + } + + [Fact] + public void ParseAndRender_WithTemplateAssignment_DoesNotMutateCustomGlobals() + { + // Arrange + var customGlobals = new ScriptObject(); + customGlobals.SetValue("x", "custom", false); + var sut = new ScribanContext(_fileSystemHandler, TransformerType.Scriban, customGlobals); + + // Act + var first = sut.ParseAndRender("{{ x = 'assigned'; x }} {{ y = 1; y }}", CreateModel()); + var second = sut.ParseAndRender("{{ x }}", CreateModel()); + + // Assert + first.Should().Be("assigned 1"); + second.Should().Be("custom"); + customGlobals.Keys.Should().BeEquivalentTo("x"); + customGlobals["x"].Should().Be("custom"); + } + + [Theory] + [InlineData(TransformerType.Scriban, "{{ request.Url }} {{ request.Path }} {{ request.Method }}")] + [InlineData(TransformerType.Scriban, "{{ request.PathSegments[1] }} {{ request.Headers.Accept }}")] + [InlineData(TransformerType.Scriban, "{{ for s in request.PathSegments }}[{{ s }}]{{ end }}")] + [InlineData(TransformerType.Scriban, "{{ request.Body | string.upcase }} {{ data.Id }}")] + [InlineData(TransformerType.Scriban, "{{ x = 5; x * 2 }}")] + [InlineData(TransformerType.ScribanDotLiquid, "{{ request.Url }} {{ request.Path }} {{ request.Method }}")] + [InlineData(TransformerType.ScribanDotLiquid, "{% for s in request.PathSegments %}[{{ s }}]{% endfor %}")] + [InlineData(TransformerType.ScribanDotLiquid, "{{ request.Body | upcase }} {{ data.Id }}")] + [InlineData(TransformerType.ScribanDotLiquid, "{% assign x = 5 %}{{ x | times: 2 }}")] + public void ParseAndRender_WithEmptyCustomGlobals_RendersSameAsWithoutCustomGlobals(TransformerType transformerType, string template) + { + // Arrange + var model = CreateModel(); + var withoutCustomGlobals = new ScribanContext(_fileSystemHandler, transformerType); + var withCustomGlobals = new ScribanContext(_fileSystemHandler, transformerType, new ScriptObject()); + + // Act + var expected = withoutCustomGlobals.ParseAndRender(template, model); + var result = withCustomGlobals.ParseAndRender(template, model); + + // Assert + expected.Should().NotBeNullOrWhiteSpace(); + result.Should().Be(expected); + } + + private static TransformModel CreateModel() + { + var body = new BodyData + { + BodyAsString = "hello", + DetectedBodyType = BodyType.String + }; + var headers = new Dictionary + { + { "Accept", ["application/json"] } + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo/bar"), "GET", "::1", body, headers); + + return new TransformModel + { + mapping = Mock.Of(), + request = request, + data = new { Id = 42 } + }; + } +} diff --git a/test/WireMock.Net.Tests/WireMockServer.Proxy.cs b/test/WireMock.Net.Tests/WireMockServer.Proxy.cs index 245940fe..7262479f 100644 --- a/test/WireMock.Net.Tests/WireMockServer.Proxy.cs +++ b/test/WireMock.Net.Tests/WireMockServer.Proxy.cs @@ -59,12 +59,13 @@ public class WireMockServerProxyTests public async Task WireMockServer_Proxy_AdminFalse_With_SaveMapping_Is_True_And_SaveMappingToFile_Is_False_Should_AddInternalMappingOnly() { // Assign + using var target = StartProxyTargetServer(); var cancellationToken = TestContext.Current.CancellationToken; var settings = new WireMockServerSettings { ProxyAndRecordSettings = new ProxyAndRecordSettings { - Url = "http://www.google.com", + Url = target.Url!, SaveMapping = true, SaveMappingToFile = false, ExcludedHeaders = ["Connection"] // Needed for .NET 4.5.x and 4.6.x @@ -93,12 +94,13 @@ public class WireMockServerProxyTests public async Task WireMockServer_Proxy_AdminTrue_With_SaveMapping_Is_True_And_SaveMappingToFile_Is_False_Should_AddInternalMappingOnly() { // Assign + using var target = StartProxyTargetServer(); var cancellationToken = TestContext.Current.CancellationToken; var settings = new WireMockServerSettings { ProxyAndRecordSettings = new ProxyAndRecordSettings { - Url = "http://www.google.com", + Url = target.Url!, SaveMapping = true, SaveMappingToFile = false, ExcludedHeaders = ["Connection"] // Needed for .NET 4.5.x and 4.6.x @@ -185,6 +187,7 @@ public class WireMockServerProxyTests public async Task WireMockServer_Proxy_With_SaveMapping_Is_False_And_SaveMappingToFile_Is_True_ShouldSaveMappingToFile() { // Assign + using var target = StartProxyTargetServer(); var fileSystemHandlerMock = new Mock(); fileSystemHandlerMock.Setup(f => f.GetMappingFolder()).Returns("m"); @@ -192,7 +195,7 @@ public class WireMockServerProxyTests { ProxyAndRecordSettings = new ProxyAndRecordSettings { - Url = "http://www.google.com", + Url = target.Url!, SaveMapping = false, SaveMappingToFile = true }, @@ -221,6 +224,7 @@ public class WireMockServerProxyTests public async Task WireMockServer_Proxy_With_SaveMappingForStatusCodePattern_Is_False_Should_Not_SaveMapping() { // Assign + using var target = StartProxyTargetServer(); var fileSystemHandlerMock = new Mock(); fileSystemHandlerMock.Setup(f => f.GetMappingFolder()).Returns("m"); @@ -228,7 +232,7 @@ public class WireMockServerProxyTests { ProxyAndRecordSettings = new ProxyAndRecordSettings { - Url = "http://www.google.com", + Url = target.Url!, SaveMapping = true, SaveMappingToFile = true, SaveMappingForStatusCodePattern = "999" // Just make sure that we don't want this mapping @@ -258,6 +262,7 @@ public class WireMockServerProxyTests public async Task WireMockServer_Proxy_With_DoNotSaveMappingForHttpMethod_Should_Not_SaveMapping() { // Assign + using var target = StartProxyTargetServer(); var fileSystemHandlerMock = new Mock(); fileSystemHandlerMock.Setup(f => f.GetMappingFolder()).Returns("m"); @@ -265,7 +270,7 @@ public class WireMockServerProxyTests { ProxyAndRecordSettings = new ProxyAndRecordSettings { - Url = "http://www.google.com", + Url = target.Url!, SaveMapping = true, SaveMappingToFile = true, SaveMappingSettings = new ProxySaveMappingSettings @@ -298,11 +303,12 @@ public class WireMockServerProxyTests public async Task WireMockServer_Proxy_Should_log_proxied_requests() { // Assign + using var target = StartProxyTargetServer(); var settings = new WireMockServerSettings { ProxyAndRecordSettings = new ProxyAndRecordSettings { - Url = "http://www.google.com", + Url = target.Url!, SaveMapping = true, SaveMappingToFile = false } @@ -328,12 +334,13 @@ public class WireMockServerProxyTests public async Task WireMockServer_Proxy_Should_proxy_responses() { // Assign + using var target = StartProxyTargetServer(); var cancellationToken = TestContext.Current.CancellationToken; string path = $"/prx_{Guid.NewGuid()}"; var server = WireMockServer.Start(); server .Given(Request.Create().WithPath(path)) - .RespondWith(Response.Create().WithProxy("http://www.google.com")); + .RespondWith(Response.Create().WithProxy(target.Url!)); // Act var requestMessage = new HttpRequestMessage @@ -349,7 +356,7 @@ public class WireMockServerProxyTests // Assert server.Mappings.Should().HaveCount(1); server.LogEntries.Should().HaveCount(1); - content.Should().Contain("google"); + content.Should().Be(ProxyTargetBody); server.Stop(); } @@ -947,6 +954,20 @@ public class WireMockServerProxyTests server.LogEntries.Should().HaveCount(1); server.Stop(); } + + private const string ProxyTargetBody = "response from proxy target"; + + private static WireMockServer StartProxyTargetServer() + { + var target = WireMockServer.Start(); + target + .Given(Request.Create().WithPath("/*")) + .RespondWith(Response.Create() + .WithStatusCode(HttpStatusCode.OK) + .WithBody(ProxyTargetBody)); + + return target; + } } diff --git a/test/WireMock.Net.Tests/WireMockServer.Settings.cs b/test/WireMock.Net.Tests/WireMockServer.Settings.cs index 4e34a03b..b9ec4921 100644 --- a/test/WireMock.Net.Tests/WireMockServer.Settings.cs +++ b/test/WireMock.Net.Tests/WireMockServer.Settings.cs @@ -1,13 +1,18 @@ // Copyright © WireMock.Net +using System.Net.Http; using Moq; +using Scriban.Runtime; using WireMock.Authentication; using WireMock.Constants; using WireMock.Logging; using WireMock.Owin; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; using WireMock.Server; using WireMock.Settings; +using WireMock.Types; namespace WireMock.Net.Tests; @@ -228,5 +233,33 @@ public class WireMockServerSettingsTests options.ActivityTracingOptions.Should().BeNull(); } #endif + + [Theory] + [InlineData(TransformerType.Scriban)] + [InlineData(TransformerType.ScribanDotLiquid)] + public async Task WireMockServer_WireMockServerSettings_ScribanRegistrationCallback_IsUsedInResponseTemplate(TransformerType transformerType) + { + // Arrange + var cancellationToken = TestContext.Current.CancellationToken; + var path = $"/scriban_{Guid.NewGuid()}"; + using var server = WireMockServer.Start(new WireMockServerSettings + { + ScribanRegistrationCallback = (scriptObject, _) => + ((ScriptObject)scriptObject).Import("fake_name", new Func(() => "Jane Doe")) + }); + + server + .Given(Request.Create().WithPath(path).UsingGet()) + .RespondWith(Response.Create() + .WithBody("{{ fake_name }} {{ request.Path }}") + .WithTransformer(transformerType) + ); + + // Act + var response = await new HttpClient().GetStringAsync(server.Url + path, cancellationToken); + + // Assert + response.Should().Be($"Jane Doe {path}"); + } } diff --git a/test/WireMock.Net.Tests/WireMockServerTests.cs b/test/WireMock.Net.Tests/WireMockServerTests.cs index 086411be..8853998a 100644 --- a/test/WireMock.Net.Tests/WireMockServerTests.cs +++ b/test/WireMock.Net.Tests/WireMockServerTests.cs @@ -640,6 +640,8 @@ public partial class WireMockServerTests(ITestOutputHelper testOutputHelper) [Fact] public async Task WireMockServer_Should_respond_to_ipv6_loopback() { + Assert.SkipUnless(Socket.OSSupportsIPv6, "IPv6 is not supported on this machine."); + // Assign var server = WireMockServer.Start();