From 317fcb1b307c726675a5cb09bcfef59c31946cce Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 18 Jan 2026 19:54:34 +0100 Subject: [PATCH 1/2] 1.24.0 --- CHANGELOG.md | 5 +++++ Directory.Build.props | 2 +- Generate-ReleaseNotes.cmd | 2 +- PackageReleaseNotes.txt | 9 ++++----- README.md | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42ab62e7..e3855144 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.24.0 (18 January 2026) +- [#1417](https://github.com/wiremock/WireMock.Net/pull/1417) - Update aspire to 13.1 (examples + code) [feature] contributed by [petrroll](https://github.com/petrroll) +- [#1418](https://github.com/wiremock/WireMock.Net/pull/1418) - Add OTEL tracing support for Wiremock + automatic OTEL for Aspire integration [feature] contributed by [petrroll](https://github.com/petrroll) +- [#1214](https://github.com/wiremock/WireMock.Net/issues/1214) - OpenTelemetry Support for .NET Aspire [feature] + # 1.23.0 (05 January 2026) - [#1414](https://github.com/wiremock/WireMock.Net/pull/1414) - Pass the parameter matchOperator in Request.WithPath to its inner calls [bug] contributed by [gbamqzkdyg](https://github.com/gbamqzkdyg) - [#1416](https://github.com/wiremock/WireMock.Net/pull/1416) - Fix: Pass AllowedHandlebarsHelpers configuration to Handlebars.Net.Helpers library contributed by [samlatham](https://github.com/samlatham) diff --git a/Directory.Build.props b/Directory.Build.props index 305a7415..71175b26 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 1.23.0 + 1.24.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 90e00c82..912e819f 100644 --- a/Generate-ReleaseNotes.cmd +++ b/Generate-ReleaseNotes.cmd @@ -1,6 +1,6 @@ rem https://github.com/StefH/GitHubReleaseNotes -SET version=1.23.0 +SET version=1.24.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 8d7293e0..c31c935a 100644 --- a/PackageReleaseNotes.txt +++ b/PackageReleaseNotes.txt @@ -1,7 +1,6 @@ -# 1.23.0 (05 January 2026) -- #1414 Pass the parameter matchOperator in Request.WithPath to its inner calls [bug] -- #1416 Fix: Pass AllowedHandlebarsHelpers configuration to Handlebars.Net.Helpers library -- #1413 Parameter `matchOperator` is not respected in the method Request.WithPath [bug] -- #1415 HandlebarsSettings AllowedHandlebarsHelpers Configuration Not Applied [bug] +# 1.24.0 (18 January 2026) +- #1417 Update aspire to 13.1 (examples + code) [feature] +- #1418 Add OTEL tracing support for Wiremock + automatic OTEL for Aspire integration [feature] +- #1214 OpenTelemetry Support for .NET Aspire [feature] 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/README.md b/README.md index 29b4f48e..828e5d25 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
-🔺 **WireMock.Net.Minimal** does not include *WireMock.Net.MimePart*, *WireMock.Net.GraphQL* and *WireMock.Net.ProtoBuf*. +🔺 **WireMock.Net.Minimal** does not include *WireMock.Net.MimePart*, *WireMock.Net.GraphQL*, *WireMock.Net.ProtoBuf* and *WireMock.Net.OpenTelemetry*. --- From 702e156ddc219e6baae2dba520d4ddd6eb7fe335 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 24 Jan 2026 09:15:43 +0100 Subject: [PATCH 2/2] Fix MimePartMatcher and add more tests (#1389) * mp * . * --return * Fixed * -- * ... * fix * ... * . --- WireMock.Net Solution.sln | 15 ++++ .../WireMock.Net.Console.MimePart/MainApp.cs | 82 +++++++++++++++++++ .../WireMock.Net.Console.MimePart/Program.cs | 23 ++++++ .../WireMock.Net.Console.MimePart.csproj | 29 +++++++ .../b9c82182-e469-41da-bcaf-b6e3157fefdc.json | 79 ++++++++++++++++++ .../log4net.config | 20 +++++ examples/WireMock.Net.Console.NET8/MainApp.cs | 35 +------- .../Admin/Mappings/BodyModel.cs | 6 ++ .../Admin/Requests/LogRequestMatchModel.cs | 3 +- .../Matchers/Request/IRequestMatchResult.cs | 7 ++ .../Matchers/Request/MatchDetail.cs | 12 ++- .../Matchers/GraphQLMatcher.cs | 2 +- .../Matchers/CSharpCodeMatcher.cs | 2 +- .../Matchers/MimePartMatcher.cs | 57 +++++++------ .../Util/MimeKitUtils.cs | 7 +- .../AzureADAuthenticationMatcher.cs | 6 +- .../Logging/WireMockConsoleLogger.cs | 2 +- .../Matchers/CompositeMatcher.cs | 46 +++++++++++ .../Matchers/ContentTypeMatcher.cs | 2 +- .../Matchers/ExactMatcher.cs | 2 +- .../Matchers/FormUrlEncodedMatcher.cs | 6 +- .../Matchers/JSONPathMatcher.cs | 4 +- .../Matchers/JmesPathMatcher.cs | 4 +- .../Matchers/JsonMatcher.cs | 2 +- .../Matchers/Request/RequestMatchResult.cs | 18 +++- .../Request/RequestMessageBodyMatcher.cs | 48 +++++------ .../Request/RequestMessageBodyMatcher`1.cs | 8 +- .../Request/RequestMessageClientIPMatcher.cs | 12 +-- .../Request/RequestMessageCookieMatcher.cs | 12 +-- .../Request/RequestMessageHeaderMatcher.cs | 14 ++-- .../RequestMessageHttpVersionMatcher.cs | 12 +-- .../Request/RequestMessageMultiPartMatcher.cs | 18 ++-- .../Request/RequestMessagePathMatcher.cs | 11 +-- .../Request/RequestMessageUrlMatcher.cs | 7 +- .../Matchers/SimMetricsMatcher.cs | 2 +- .../Matchers/XPathMatcher.cs | 2 +- .../RequestBuilders/Request.WithMultiPart.cs | 2 +- .../Serialization/LogEntryMapper.cs | 11 +-- .../Serialization/MatcherMapper.cs | 4 +- .../Server/WireMockServer.ConvertMapping.cs | 11 ++- .../Matchers/ProtoBufMatcher.cs | 8 +- .../Matchers/ExactObjectMatcher.cs | 2 +- .../Helpers/BodyDataMatchScoreCalculator.cs | 38 +++++---- .../Matchers/MatchBehaviourHelper.cs | 2 +- .../Matchers/MatchResult.cs | 77 +++++++++++------ .../Matchers/NotNullOrEmptyMatcher.cs | 5 +- .../Matchers/RegexMatcher.cs | 2 +- .../Request/RequestMessageGraphQLMatcher.cs | 6 +- .../IMultiPartRequestBuilder.cs | 2 +- .../WireMock.Net.Tests.UsingNuGet.csproj | 2 +- .../WireMockServerTests.WithMultiPart.cs | 2 +- .../Matchers/MimePartMatcherTests.cs | 45 +++++----- .../RequestMessageBodyMatcherTests.cs | 26 +++--- .../RequestMessageGraphQLMatcherTests.cs | 16 ++-- .../RequestMessageMultiPartMatcher.cs | 38 +++++++++ .../Serialization/CustomPathParamMatcher.cs | 12 +-- 56 files changed, 665 insertions(+), 263 deletions(-) create mode 100644 examples/WireMock.Net.Console.MimePart/MainApp.cs create mode 100644 examples/WireMock.Net.Console.MimePart/Program.cs create mode 100644 examples/WireMock.Net.Console.MimePart/WireMock.Net.Console.MimePart.csproj create mode 100644 examples/WireMock.Net.Console.MimePart/__admin/mappings/b9c82182-e469-41da-bcaf-b6e3157fefdc.json create mode 100644 examples/WireMock.Net.Console.MimePart/log4net.config create mode 100644 src/WireMock.Net.Minimal/Matchers/CompositeMatcher.cs diff --git a/WireMock.Net Solution.sln b/WireMock.Net Solution.sln index 6a3bdd94..ed9519f6 100644 --- a/WireMock.Net Solution.sln +++ b/WireMock.Net Solution.sln @@ -152,6 +152,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.OpenTelemetry" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.OpenTelemetryDemo", "examples\WireMock.Net.OpenTelemetryDemo\WireMock.Net.OpenTelemetryDemo.csproj", "{9957038D-F9C3-CA5D-E8AE-BE188E512635}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Console.MimePart", "examples\WireMock.Net.Console.MimePart\WireMock.Net.Console.MimePart.csproj", "{4005E20C-D42B-138A-79BE-B3F5420C563F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -810,6 +812,18 @@ Global {9957038D-F9C3-CA5D-E8AE-BE188E512635}.Release|x64.Build.0 = Release|Any CPU {9957038D-F9C3-CA5D-E8AE-BE188E512635}.Release|x86.ActiveCfg = Release|Any CPU {9957038D-F9C3-CA5D-E8AE-BE188E512635}.Release|x86.Build.0 = Release|Any CPU + {4005E20C-D42B-138A-79BE-B3F5420C563F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4005E20C-D42B-138A-79BE-B3F5420C563F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4005E20C-D42B-138A-79BE-B3F5420C563F}.Debug|x64.ActiveCfg = Debug|Any CPU + {4005E20C-D42B-138A-79BE-B3F5420C563F}.Debug|x64.Build.0 = Debug|Any CPU + {4005E20C-D42B-138A-79BE-B3F5420C563F}.Debug|x86.ActiveCfg = Debug|Any CPU + {4005E20C-D42B-138A-79BE-B3F5420C563F}.Debug|x86.Build.0 = Debug|Any CPU + {4005E20C-D42B-138A-79BE-B3F5420C563F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4005E20C-D42B-138A-79BE-B3F5420C563F}.Release|Any CPU.Build.0 = Release|Any CPU + {4005E20C-D42B-138A-79BE-B3F5420C563F}.Release|x64.ActiveCfg = Release|Any CPU + {4005E20C-D42B-138A-79BE-B3F5420C563F}.Release|x64.Build.0 = Release|Any CPU + {4005E20C-D42B-138A-79BE-B3F5420C563F}.Release|x86.ActiveCfg = Release|Any CPU + {4005E20C-D42B-138A-79BE-B3F5420C563F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -871,6 +885,7 @@ Global {2DBBD70D-8051-441F-92BB-FF9B8B4B4982} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} {C8F4E6D2-9A3B-4F1C-8D5E-7A2B3C4D5E6F} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} {9957038D-F9C3-CA5D-E8AE-BE188E512635} = {985E0ADB-D4B4-473A-AA40-567E279B7946} + {4005E20C-D42B-138A-79BE-B3F5420C563F} = {985E0ADB-D4B4-473A-AA40-567E279B7946} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458} diff --git a/examples/WireMock.Net.Console.MimePart/MainApp.cs b/examples/WireMock.Net.Console.MimePart/MainApp.cs new file mode 100644 index 00000000..0178f2b2 --- /dev/null +++ b/examples/WireMock.Net.Console.MimePart/MainApp.cs @@ -0,0 +1,82 @@ +// Copyright © WireMock.Net + +using Newtonsoft.Json; +using WireMock.Logging; +using WireMock.Matchers; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; +using WireMock.Server; +using WireMock.Settings; + +namespace WireMock.Net.Console.MimePart; + +// Test this CURL: +// curl -X POST http://localhost:9091/multipart -F "plainText=This is some plain text;type=text/plain" -F "jsonData={ `"Key`": `"Value`" };type=application/json" -F "image=@image.png;type=image/png" +// +// curl -X POST http://localhost:9091/multipart2 -F "plainText=This is some plain text;type=text/plain" -F "jsonData={ `"Key`": `"Value`" };type=application/json" -F "image=@image.png;type=image/png" + +public static class MainApp +{ + public static async Task RunAsync() + { + using var server = WireMockServer.Start(new WireMockServerSettings + { + Port = 9091, + StartAdminInterface = true, + + ReadStaticMappings = true, + //WatchStaticMappings = true, + //WatchStaticMappingsInSubdirectories = true, + + Logger = new WireMockConsoleLogger() + }); + System.Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls)); + + var textPlainContentTypeMatcher = new ContentTypeMatcher("text/plain"); + var textPlainContentMatcher = new ExactMatcher("This is some plain text"); + var textPlainMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textPlainContentTypeMatcher, null, null, textPlainContentMatcher); + + var textJsonContentTypeMatcher = new ContentTypeMatcher("application/json"); + var textJsonContentMatcher = new JsonMatcher(new { Key = "Value" }, true); + var textJsonMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textJsonContentTypeMatcher, null, null, textJsonContentMatcher); + + var imagePngContentTypeMatcher = new ContentTypeMatcher("image/png"); + var imagePngContentDispositionMatcher = new ExactMatcher("form-data; name=\"image\"; filename=\"image.png\""); + var imagePngContentTransferEncodingMatcher = new ExactMatcher("default"); + var imagePngContentMatcher = new ExactObjectMatcher(Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC")); + var imagePngMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, imagePngContentTypeMatcher, imagePngContentDispositionMatcher, imagePngContentTransferEncodingMatcher, imagePngContentMatcher); + + var matchers = new IMatcher[] + { + textPlainMatcher, + textJsonMatcher, + imagePngMatcher + }; + + server + .Given(Request.Create() + .WithPath("/multipart") + .UsingPost() + .WithMultiPart(matchers) + ) + .WithGuid("b9c82182-e469-41da-bcaf-b6e3157fefdb") + .RespondWith(Response.Create() + .WithBody("MultiPart is ok") + ); + + // server.SaveStaticMappings(); + + System.Console.WriteLine(JsonConvert.SerializeObject(server.MappingModels, Formatting.Indented)); + + System.Console.WriteLine("Press any key to stop the server"); + System.Console.ReadKey(); + server.Stop(); + + System.Console.WriteLine("Displaying all requests"); + var allRequests = server.LogEntries; + System.Console.WriteLine(JsonConvert.SerializeObject(allRequests, Formatting.Indented)); + + System.Console.WriteLine("Press any key to quit"); + System.Console.ReadKey(); + } +} \ No newline at end of file diff --git a/examples/WireMock.Net.Console.MimePart/Program.cs b/examples/WireMock.Net.Console.MimePart/Program.cs new file mode 100644 index 00000000..85d66ba5 --- /dev/null +++ b/examples/WireMock.Net.Console.MimePart/Program.cs @@ -0,0 +1,23 @@ +// Copyright © WireMock.Net + +using System.Reflection; +using log4net; +using log4net.Config; +using log4net.Repository; + +namespace WireMock.Net.Console.MimePart; + +static class Program +{ + private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); + private static readonly ILog Log = LogManager.GetLogger(typeof(Program)); + + static async Task Main(params string[] args) + { + Log.Info("Starting WireMock.Net.Console.MimePart..."); + + XmlConfigurator.Configure(LogRepository, new FileInfo("log4net.config")); + + await MainApp.RunAsync(); + } +} \ No newline at end of file diff --git a/examples/WireMock.Net.Console.MimePart/WireMock.Net.Console.MimePart.csproj b/examples/WireMock.Net.Console.MimePart/WireMock.Net.Console.MimePart.csproj new file mode 100644 index 00000000..537b650a --- /dev/null +++ b/examples/WireMock.Net.Console.MimePart/WireMock.Net.Console.MimePart.csproj @@ -0,0 +1,29 @@ + + + + Exe + net8.0 + $(DefineConstants);GRAPHQL;MIMEKIT;PROTOBUF + enable + enable + + + + + PreserveNewest + + + + + + + + + + + + PreserveNewest + + + + \ No newline at end of file diff --git a/examples/WireMock.Net.Console.MimePart/__admin/mappings/b9c82182-e469-41da-bcaf-b6e3157fefdc.json b/examples/WireMock.Net.Console.MimePart/__admin/mappings/b9c82182-e469-41da-bcaf-b6e3157fefdc.json new file mode 100644 index 00000000..0bc68029 --- /dev/null +++ b/examples/WireMock.Net.Console.MimePart/__admin/mappings/b9c82182-e469-41da-bcaf-b6e3157fefdc.json @@ -0,0 +1,79 @@ +{ + "Guid": "b9c82182-e469-41da-bcaf-b6e3157fefdc", + "UpdatedAt": "2025-12-18T17:21:57.3879723Z", + "Request": { + "Path": { + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": "/multipart2", + "IgnoreCase": false + } + ] + }, + "Methods": [ + "POST" + ], + "Body": { + "MatcherName": "MultiPartMatcher", + "Matchers": [ + { + "Name": "MimePartMatcher", + "ContentTypeMatcher": { + "Name": "ContentTypeMatcher", + "Pattern": "text/plain", + "IgnoreCase": false + }, + "ContentMatcher": { + "Name": "ExactMatcher", + "Pattern": "This is some plain text", + "IgnoreCase": false + } + }, + { + "Name": "MimePartMatcher", + "ContentTypeMatcher": { + "Name": "ContentTypeMatcher", + "Pattern": "application/json", + "IgnoreCase": false + }, + "ContentMatcher": { + "Name": "JsonMatcher", + "Pattern": { + "Key": "Value" + }, + "IgnoreCase": true, + "Regex": false + } + }, + { + "Name": "MimePartMatcher", + "ContentTypeMatcher": { + "Name": "ContentTypeMatcher", + "Pattern": "image/png", + "IgnoreCase": false + }, + "ContentDispositionMatcher": { + "Name": "ExactMatcher", + "Pattern": "form-data; name=\"image\"; filename=\"image.png\"", + "IgnoreCase": false + }, + "ContentTransferEncodingMatcher": { + "Name": "ExactMatcher", + "Pattern": "default", + "IgnoreCase": false + }, + "ContentMatcher": { + "Name": "ExactObjectMatcher", + "Pattern": "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC" + } + } + ], + "MatchOperator": "Or" + } + }, + "Response": { + "BodyDestination": "SameAsSource", + "Body": "MultiPart2 is ok" + } +} \ No newline at end of file diff --git a/examples/WireMock.Net.Console.MimePart/log4net.config b/examples/WireMock.Net.Console.MimePart/log4net.config new file mode 100644 index 00000000..feae9952 --- /dev/null +++ b/examples/WireMock.Net.Console.MimePart/log4net.config @@ -0,0 +1,20 @@ + + + +
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/WireMock.Net.Console.NET8/MainApp.cs b/examples/WireMock.Net.Console.NET8/MainApp.cs index fa88484c..fce4d3f7 100644 --- a/examples/WireMock.Net.Console.NET8/MainApp.cs +++ b/examples/WireMock.Net.Console.NET8/MainApp.cs @@ -339,7 +339,7 @@ namespace WireMock.Net.ConsoleApplication } } System.Console.WriteLine("X = {0} ; default = {1} ; pX = {2:0.00} ; valueX = {3:0.00}", xCount, defaultCount, pX, 1.0 * xCount / tot); - return; + using var httpAndHttpsWithPort = WireMockServer.Start(new WireMockServerSettings { HostingScheme = HostingScheme.HttpAndHttps, @@ -556,39 +556,6 @@ namespace WireMock.Net.ConsoleApplication ); #endif -#if MIMEKIT - var textPlainContentTypeMatcher = new ContentTypeMatcher("text/plain"); - var textPlainContentMatcher = new ExactMatcher("This is some plain text"); - var textPlainMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textPlainContentTypeMatcher, null, null, textPlainContentMatcher); - - var textJsonContentTypeMatcher = new ContentTypeMatcher("text/json"); - var textJsonContentMatcher = new JsonMatcher(new { Key = "Value" }, true); - var textJsonMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textJsonContentTypeMatcher, null, null, textJsonContentMatcher); - - var imagePngContentTypeMatcher = new ContentTypeMatcher("image/png"); - var imagePngContentDispositionMatcher = new ExactMatcher("attachment; filename=\"image.png\""); - var imagePngContentTransferEncodingMatcher = new ExactMatcher("base64"); - var imagePngContentMatcher = new ExactObjectMatcher(Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC")); - var imagePngMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, imagePngContentTypeMatcher, imagePngContentDispositionMatcher, imagePngContentTransferEncodingMatcher, imagePngContentMatcher); - - var matchers = new IMatcher[] - { - textPlainMatcher, - textJsonMatcher, - imagePngMatcher - }; - - server - .Given(Request.Create() - .WithPath("/multipart") - .UsingPost() - .WithMultiPart(matchers) - ) - .WithGuid("b9c82182-e469-41da-bcaf-b6e3157fefdb") - .RespondWith(Response.Create() - .WithBody("MultiPart is ok") - ); -#endif // 400 ms server .Given(Request.Create() diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/BodyModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/BodyModel.cs index d958bfd9..30b8469e 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/BodyModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/BodyModel.cs @@ -8,6 +8,12 @@ namespace WireMock.Admin.Mappings; [FluentBuilder.AutoGenerateBuilder] public class BodyModel { + /// + /// The name of the body matcher. + /// Currently only "MultiPartMatcher" is supported. + /// + public string? MatcherName { get; set; } + /// /// Gets or sets the matcher. /// diff --git a/src/WireMock.Net.Abstractions/Admin/Requests/LogRequestMatchModel.cs b/src/WireMock.Net.Abstractions/Admin/Requests/LogRequestMatchModel.cs index 6a379627..4a2451f3 100644 --- a/src/WireMock.Net.Abstractions/Admin/Requests/LogRequestMatchModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Requests/LogRequestMatchModel.cs @@ -1,6 +1,7 @@ // Copyright © WireMock.Net using System.Collections.Generic; +using WireMock.Matchers.Request; namespace WireMock.Admin.Requests; @@ -47,5 +48,5 @@ public class LogRequestMatchModel /// /// The match details. /// - public IList MatchDetails { get; set; } + public IList MatchDetails { get; set; } = []; } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Matchers/Request/IRequestMatchResult.cs b/src/WireMock.Net.Abstractions/Matchers/Request/IRequestMatchResult.cs index c5640113..9dee2645 100644 --- a/src/WireMock.Net.Abstractions/Matchers/Request/IRequestMatchResult.cs +++ b/src/WireMock.Net.Abstractions/Matchers/Request/IRequestMatchResult.cs @@ -55,4 +55,11 @@ public interface IRequestMatchResult : IComparable /// The exception [Optional]. /// The score. double AddScore(Type matcherType, double score, Exception? exception); + + /// + /// Adds the score. + /// + /// The matchDetail. + /// The score. + double AddMatchDetail(MatchDetail matchDetail); } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Matchers/Request/MatchDetail.cs b/src/WireMock.Net.Abstractions/Matchers/Request/MatchDetail.cs index 6f38a330..adac58e2 100644 --- a/src/WireMock.Net.Abstractions/Matchers/Request/MatchDetail.cs +++ b/src/WireMock.Net.Abstractions/Matchers/Request/MatchDetail.cs @@ -12,7 +12,12 @@ public class MatchDetail /// /// Gets or sets the type of the matcher. /// - public Type MatcherType { get; set; } = null!; + public required Type MatcherType { get; set; } + + /// + /// Gets or sets the type of the matcher. + /// + public required string Name { get; set; } /// /// Gets or sets the score between 0.0 and 1.0 @@ -24,4 +29,9 @@ public class MatchDetail /// [Optional] /// public Exception? Exception { get; set; } + + /// + /// The child MatchResults in case of multiple matchers. + /// + public MatchDetail[]? MatchDetails { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net.GraphQL/Matchers/GraphQLMatcher.cs b/src/WireMock.Net.GraphQL/Matchers/GraphQLMatcher.cs index aaf830d1..6c4f2d5d 100644 --- a/src/WireMock.Net.GraphQL/Matchers/GraphQLMatcher.cs +++ b/src/WireMock.Net.GraphQL/Matchers/GraphQLMatcher.cs @@ -140,7 +140,7 @@ public class GraphQLMatcher : IGraphQLMatcher } } - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); + return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); } /// diff --git a/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs b/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs index 7e557b24..eb7cd034 100644 --- a/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs +++ b/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs @@ -97,7 +97,7 @@ public class CSharpCodeMatcher : ICSharpCodeMatcher } } - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); + return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); } private bool IsMatch(dynamic input, string pattern) diff --git a/src/WireMock.Net.MimePart/Matchers/MimePartMatcher.cs b/src/WireMock.Net.MimePart/Matchers/MimePartMatcher.cs index fe17cdd4..32b2df37 100644 --- a/src/WireMock.Net.MimePart/Matchers/MimePartMatcher.cs +++ b/src/WireMock.Net.MimePart/Matchers/MimePartMatcher.cs @@ -1,6 +1,7 @@ // Copyright © WireMock.Net using System; +using System.Collections.Generic; using WireMock.Matchers.Helpers; using WireMock.Models.Mime; using WireMock.Util; @@ -12,7 +13,7 @@ namespace WireMock.Matchers; /// public class MimePartMatcher : IMimePartMatcher { - private readonly Func[] _funcs; + private readonly IList<(string Name, Func func)> _matcherFunctions; /// public string Name => nameof(MimePartMatcher); @@ -49,34 +50,47 @@ public class MimePartMatcher : IMimePartMatcher ContentTransferEncodingMatcher = contentTransferEncodingMatcher; ContentMatcher = contentMatcher; - _funcs = - [ - mp => ContentTypeMatcher?.IsMatch(GetContentTypeAsString(mp.ContentType)) ?? MatchScores.Perfect, - mp => ContentDispositionMatcher?.IsMatch(mp.ContentDisposition?.ToString()?.Replace("Content-Disposition: ", string.Empty)) ?? MatchScores.Perfect, - mp => ContentTransferEncodingMatcher?.IsMatch(mp.ContentTransferEncoding.ToLowerInvariant()) ?? MatchScores.Perfect, - MatchOnContent - ]; + _matcherFunctions = []; + if (ContentTypeMatcher != null) + { + _matcherFunctions.Add((nameof(ContentTypeMatcher), mp => ContentTypeMatcher.IsMatch(GetContentTypeAsString(mp.ContentType)))); + } + + if (ContentDispositionMatcher != null) + { + _matcherFunctions.Add((nameof(ContentDispositionMatcher), mp => ContentDispositionMatcher.IsMatch(mp.ContentDisposition?.ToString()?.Replace("Content-Disposition: ", string.Empty)))); + } + + if (ContentTransferEncodingMatcher != null) + { + _matcherFunctions.Add((nameof(ContentTransferEncodingMatcher), mp => ContentTransferEncodingMatcher.IsMatch(mp.ContentTransferEncoding.ToLowerInvariant()))); + } + + if (ContentMatcher != null) + { + _matcherFunctions.Add((ContentMatcher.Name, MatchOnContent)); + } } /// public MatchResult IsMatch(IMimePartData value) { - var score = MatchScores.Mismatch; - Exception? exception = null; + var results = new List(); - try + foreach (var matcherFunction in _matcherFunctions) { - if (Array.TrueForAll(_funcs, func => func(value).IsPerfect())) + try { - score = MatchScores.Perfect; + var matchResult = matcherFunction.func(value); + results.Add(MatchResult.From(matcherFunction.Name, matchResult.Score)); + } + catch (Exception ex) + { + results.Add(MatchResult.From(matcherFunction.Name, MatchScores.Mismatch, ex)); } } - catch (Exception ex) - { - exception = ex; - } - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); + return MatchResult.From(nameof(MimePartMatcher), results, MatchOperator.And); } /// @@ -87,11 +101,6 @@ public class MimePartMatcher : IMimePartMatcher private MatchResult MatchOnContent(IMimePartData mimePart) { - if (ContentMatcher == null) - { - return MatchScores.Perfect; - } - var bodyParserSettings = new BodyParserSettings { Stream = mimePart.Open(), @@ -102,7 +111,7 @@ public class MimePartMatcher : IMimePartMatcher }; var bodyData = BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false).GetAwaiter().GetResult(); - return BodyDataMatchScoreCalculator.CalculateMatchScore(bodyData, ContentMatcher); + return BodyDataMatchScoreCalculator.CalculateMatchScore(bodyData, ContentMatcher!); } private static string? GetContentTypeAsString(IContentTypeData? contentType) diff --git a/src/WireMock.Net.MimePart/Util/MimeKitUtils.cs b/src/WireMock.Net.MimePart/Util/MimeKitUtils.cs index 9c39b79b..0aebb53d 100644 --- a/src/WireMock.Net.MimePart/Util/MimeKitUtils.cs +++ b/src/WireMock.Net.MimePart/Util/MimeKitUtils.cs @@ -52,7 +52,7 @@ internal class MimeKitUtils : IMimeKitUtils return false; } - var fixedBytes = FixBytes(bytes, contentTypeHeader[0]); + var fixedBytes = PrependContentTypeHeader(bytes, contentTypeHeader[0]); mimeMessageData = LoadFromStream(new MemoryStream(fixedBytes)); return true; @@ -68,7 +68,10 @@ internal class MimeKitUtils : IMimeKitUtils return contentTypeHeader.Any(ct => ct.TrimStart().StartsWith("multipart/", StringComparison.OrdinalIgnoreCase)); } - private static byte[] FixBytes(byte[] bytes, WireMockList contentType) + /// + /// Prepends the Content-Type header to the byte array to make it a valid MIME message for MimeKit. + /// + private static byte[] PrependContentTypeHeader(byte[] bytes, WireMockList contentType) { var contentTypeBytes = Encoding.UTF8.GetBytes($"{HttpKnownHeaderNames.ContentType}: {contentType}\r\n\r\n"); diff --git a/src/WireMock.Net.Minimal/Authentication/AzureADAuthenticationMatcher.cs b/src/WireMock.Net.Minimal/Authentication/AzureADAuthenticationMatcher.cs index 93d0fedd..65763ff8 100644 --- a/src/WireMock.Net.Minimal/Authentication/AzureADAuthenticationMatcher.cs +++ b/src/WireMock.Net.Minimal/Authentication/AzureADAuthenticationMatcher.cs @@ -54,7 +54,7 @@ internal class AzureADAuthenticationMatcher : IStringMatcher { if (string.IsNullOrEmpty(input)) { - return MatchScores.Mismatch; + return MatchResult.From(Name); } var token = Regex.Replace(input, BearerPrefix, string.Empty, RegexOptions.IgnoreCase, RegexConstants.DefaultTimeout); @@ -83,11 +83,11 @@ internal class AzureADAuthenticationMatcher : IStringMatcher // Throws an Exception as the token is invalid (expired, invalid-formatted, tenant mismatch, etc.) _jwtSecurityTokenHandler.ValidateToken(token, validationParameters, out _); - return MatchScores.Perfect; + return MatchResult.From(Name, MatchScores.Perfect); } catch (Exception ex) { - return new MatchResult(MatchScores.Mismatch, ex); + return MatchResult.From(Name, ex); } } diff --git a/src/WireMock.Net.Minimal/Logging/WireMockConsoleLogger.cs b/src/WireMock.Net.Minimal/Logging/WireMockConsoleLogger.cs index 04ba6939..72f35fbb 100644 --- a/src/WireMock.Net.Minimal/Logging/WireMockConsoleLogger.cs +++ b/src/WireMock.Net.Minimal/Logging/WireMockConsoleLogger.cs @@ -69,7 +69,7 @@ public class WireMockConsoleLogger : IWireMockLogger /// public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest) { - string message = JsonConvert.SerializeObject(logEntryModel, Formatting.Indented); + string message = JsonConvert.SerializeObject(logEntryModel, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); Console.WriteLine(Format("DebugRequestResponse", "Admin[{0}] {1}", isAdminRequest, message)); } diff --git a/src/WireMock.Net.Minimal/Matchers/CompositeMatcher.cs b/src/WireMock.Net.Minimal/Matchers/CompositeMatcher.cs new file mode 100644 index 00000000..3bd2dd26 --- /dev/null +++ b/src/WireMock.Net.Minimal/Matchers/CompositeMatcher.cs @@ -0,0 +1,46 @@ +// Copyright © WireMock.Net + +using System; + +namespace WireMock.Matchers; + +/// +/// Represents a matcher that combines multiple matching strategies into a single composite operation. +/// +public class CompositeMatcher : IMatcher +{ + /// + public string Name => nameof(CompositeMatcher); + + /// + /// The logical operator used to combine the results of the matchers. + /// + public MatchOperator MatchOperator { get; } + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + /// All matchers. + /// + public IMatcher[] Matchers { get; } + + /// + /// Initializes a new instance of the CompositeMatcher class with the specified matchers, operator, and match behaviour. + /// + /// An array of matchers to be combined. Cannot be null or contain null elements. + /// The logical operator used to combine the results of the matchers. + /// The behaviour that determines how the composite matcher interprets the combined results. + public CompositeMatcher(IMatcher[] matchers, MatchOperator matchOperator, MatchBehaviour matchBehaviour) + { + Matchers = matchers; + MatchOperator = matchOperator; + MatchBehaviour = matchBehaviour; + } + + /// + public string GetCSharpCodeArguments() + { + throw new NotImplementedException(); + } +} diff --git a/src/WireMock.Net.Minimal/Matchers/ContentTypeMatcher.cs b/src/WireMock.Net.Minimal/Matchers/ContentTypeMatcher.cs index 88a56d0f..fdbc06e1 100644 --- a/src/WireMock.Net.Minimal/Matchers/ContentTypeMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/ContentTypeMatcher.cs @@ -62,7 +62,7 @@ public class ContentTypeMatcher : WildcardMatcher { if (string.IsNullOrEmpty(input) || !MediaTypeHeaderValue.TryParse(input, out var contentType)) { - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch); + return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch)); } return base.IsMatch(contentType.MediaType); diff --git a/src/WireMock.Net.Minimal/Matchers/ExactMatcher.cs b/src/WireMock.Net.Minimal/Matchers/ExactMatcher.cs index 67ce552b..5a745054 100644 --- a/src/WireMock.Net.Minimal/Matchers/ExactMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/ExactMatcher.cs @@ -75,7 +75,7 @@ public class ExactMatcher : IStringMatcher, IIgnoreCaseMatcher : pattern => pattern == input; var score = MatchScores.ToScore(_values.Select(v => equals(v)).ToArray(), MatchOperator); - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score)); + return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score)); } /// diff --git a/src/WireMock.Net.Minimal/Matchers/FormUrlEncodedMatcher.cs b/src/WireMock.Net.Minimal/Matchers/FormUrlEncodedMatcher.cs index 5dd81f5b..65f7ba20 100644 --- a/src/WireMock.Net.Minimal/Matchers/FormUrlEncodedMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/FormUrlEncodedMatcher.cs @@ -106,18 +106,18 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher // Input is null or empty and if no patterns defined, return Perfect match. if (string.IsNullOrEmpty(input) && _patterns.Length == 0) { - return new MatchResult(MatchScores.Perfect); + return MatchResult.From(Name, MatchScores.Perfect); } if (!QueryStringParser.TryParse(input, IgnoreCase, out var inputNameValueCollection)) { - return new MatchResult(MatchScores.Mismatch); + return MatchResult.From(Name, MatchScores.Mismatch); } var matches = GetMatches(inputNameValueCollection); var score = MatchScores.ToScore(matches, MatchOperator); - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score)); + return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score)); } private bool[] GetMatches(IDictionary inputNameValueCollection) diff --git a/src/WireMock.Net.Minimal/Matchers/JSONPathMatcher.cs b/src/WireMock.Net.Minimal/Matchers/JSONPathMatcher.cs index 517a4d7d..043e83c4 100644 --- a/src/WireMock.Net.Minimal/Matchers/JSONPathMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/JSONPathMatcher.cs @@ -80,7 +80,7 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher } } - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); + return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); } /// @@ -104,7 +104,7 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher } } - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); + return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); } /// diff --git a/src/WireMock.Net.Minimal/Matchers/JmesPathMatcher.cs b/src/WireMock.Net.Minimal/Matchers/JmesPathMatcher.cs index 3e468448..1b6cfb7e 100644 --- a/src/WireMock.Net.Minimal/Matchers/JmesPathMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/JmesPathMatcher.cs @@ -87,7 +87,7 @@ public class JmesPathMatcher : IStringMatcher, IObjectMatcher } } - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); + return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); } /// @@ -102,7 +102,7 @@ public class JmesPathMatcher : IStringMatcher, IObjectMatcher return IsMatch(inputAsString); } - return MatchBehaviourHelper.Convert(MatchBehaviour, score); + return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score)); } /// diff --git a/src/WireMock.Net.Minimal/Matchers/JsonMatcher.cs b/src/WireMock.Net.Minimal/Matchers/JsonMatcher.cs index 52ee6960..8d10f28b 100644 --- a/src/WireMock.Net.Minimal/Matchers/JsonMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/JsonMatcher.cs @@ -96,7 +96,7 @@ public class JsonMatcher : IJsonMatcher } } - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), error); + return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), error); } /// diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMatchResult.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMatchResult.cs index 8342853f..bfd80d92 100644 --- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMatchResult.cs +++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMatchResult.cs @@ -24,14 +24,26 @@ public class RequestMatchResult : IRequestMatchResult public double AverageTotalScore => TotalNumber == 0 ? MatchScores.Mismatch : TotalScore / TotalNumber; /// - public IList MatchDetails { get; } = new List(); + public IList MatchDetails { get; } = []; /// public double AddScore(Type matcherType, double score, Exception? exception) { - MatchDetails.Add(new MatchDetail { MatcherType = matcherType, Score = score, Exception = exception }); + return AddMatchDetail(new MatchDetail + { + Name = matcherType.Name.Replace("RequestMessage", string.Empty), + MatcherType = matcherType, + Score = score, + Exception = exception + }); + } - return score; + /// + public double AddMatchDetail(MatchDetail matchDetail) + { + MatchDetails.Add(matchDetail); + + return matchDetail.Score; } /// diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher.cs index e05ddf94..5f38189b 100644 --- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher.cs @@ -17,27 +17,27 @@ public class RequestMessageBodyMatcher : IRequestMatcher /// /// The body function /// - public Func? Func { get; } + public Func? MatchOnBodyAsStringFunc { get; } /// /// The body data function for byte[] /// - public Func? DataFunc { get; } + public Func? MatchOnBodyAsBytesFunc { get; } /// /// The body data function for json /// - public Func? JsonFunc { get; } + public Func? MatchOnBodyAsJsonFunc { get; } /// /// The body data function for BodyData /// - public Func? BodyDataFunc { get; } + public Func? MatchOnBodyAsBodyDataFunc { get; } /// /// The body data function for FormUrlEncoded /// - public Func?, bool>? FormUrlEncodedFunc { get; } + public Func?, bool>? MatchOnBodyAsFormUrlEncodedFunc { get; } /// /// The matchers. @@ -85,7 +85,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher /// The function. public RequestMessageBodyMatcher(Func func) { - Func = Guard.NotNull(func); + MatchOnBodyAsStringFunc = Guard.NotNull(func); } /// @@ -94,7 +94,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher /// The function. public RequestMessageBodyMatcher(Func func) { - DataFunc = Guard.NotNull(func); + MatchOnBodyAsBytesFunc = Guard.NotNull(func); } /// @@ -103,7 +103,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher /// The function. public RequestMessageBodyMatcher(Func func) { - JsonFunc = Guard.NotNull(func); + MatchOnBodyAsJsonFunc = Guard.NotNull(func); } /// @@ -112,7 +112,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher /// The function. public RequestMessageBodyMatcher(Func func) { - BodyDataFunc = Guard.NotNull(func); + MatchOnBodyAsBodyDataFunc = Guard.NotNull(func); } /// @@ -121,7 +121,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher /// The function. public RequestMessageBodyMatcher(Func?, bool> func) { - FormUrlEncodedFunc = Guard.NotNull(func); + MatchOnBodyAsFormUrlEncodedFunc = Guard.NotNull(func); } /// @@ -147,43 +147,43 @@ public class RequestMessageBodyMatcher : IRequestMatcher /// public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) { - var (score, exception) = CalculateMatchScore(requestMessage).Expand(); + var (score, exception) = CalculateMatchResult(requestMessage).Expand(); return requestMatchResult.AddScore(GetType(), score, exception); } - private MatchResult CalculateMatchScore(IRequestMessage requestMessage) + private MatchResult CalculateMatchResult(IRequestMessage requestMessage) { if (Matchers != null && Matchers.Any()) { var results = Matchers.Select(matcher => BodyDataMatchScoreCalculator.CalculateMatchScore(requestMessage.BodyData, matcher)).ToArray(); - return MatchResult.From(results, MatchOperator); + return MatchResult.From(nameof(RequestMessageBodyMatcher), results, MatchOperator); } - if (Func != null) + if (MatchOnBodyAsStringFunc != null) { - return MatchScores.ToScore(Func(requestMessage.BodyData?.BodyAsString)); + return MatchResult.From($"{nameof(RequestMessageBodyMatcher)}:{nameof(MatchOnBodyAsStringFunc)}", MatchScores.ToScore(MatchOnBodyAsStringFunc(requestMessage.BodyData?.BodyAsString))); } - if (FormUrlEncodedFunc != null) + if (MatchOnBodyAsFormUrlEncodedFunc != null) { - return MatchScores.ToScore(FormUrlEncodedFunc(requestMessage.BodyData?.BodyAsFormUrlEncoded)); + return MatchResult.From($"{nameof(RequestMessageBodyMatcher)}:{nameof(MatchOnBodyAsFormUrlEncodedFunc)}", MatchScores.ToScore(MatchOnBodyAsFormUrlEncodedFunc(requestMessage.BodyData?.BodyAsFormUrlEncoded))); } - if (JsonFunc != null) + if (MatchOnBodyAsJsonFunc != null) { - return MatchScores.ToScore(JsonFunc(requestMessage.BodyData?.BodyAsJson)); + return MatchResult.From($"{nameof(RequestMessageBodyMatcher)}:{nameof(MatchOnBodyAsJsonFunc)}", MatchScores.ToScore(MatchOnBodyAsJsonFunc(requestMessage.BodyData?.BodyAsJson))); } - if (DataFunc != null) + if (MatchOnBodyAsBytesFunc != null) { - return MatchScores.ToScore(DataFunc(requestMessage.BodyData?.BodyAsBytes)); + return MatchResult.From($"{nameof(RequestMessageBodyMatcher)}:{nameof(MatchOnBodyAsBytesFunc)}", MatchScores.ToScore(MatchOnBodyAsBytesFunc(requestMessage.BodyData?.BodyAsBytes))); } - if (BodyDataFunc != null) + if (MatchOnBodyAsBodyDataFunc != null) { - return MatchScores.ToScore(BodyDataFunc(requestMessage.BodyData)); + return MatchResult.From($"{nameof(RequestMessageBodyMatcher)}:{nameof(MatchOnBodyAsBodyDataFunc)}", MatchScores.ToScore(MatchOnBodyAsBodyDataFunc(requestMessage.BodyData))); } - return default; + return MatchResult.From(nameof(RequestMessageBodyMatcher)); } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher`1.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher`1.cs index 2ee8a4ab..0a85821f 100644 --- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher`1.cs +++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher`1.cs @@ -11,6 +11,8 @@ namespace WireMock.Matchers.Request; /// public class RequestMessageBodyMatcher : IRequestMatcher { + private const string _name = nameof(RequestMessageBodyMatcher); + /// /// The body data function for type T /// @@ -46,15 +48,15 @@ public class RequestMessageBodyMatcher : IRequestMatcher try { var bodyAsT = jsonObject.ToObject(); - return MatchScores.ToScore(Func(bodyAsT)); + return MatchResult.From(_name, MatchScores.ToScore(Func(bodyAsT))); } catch (Exception ex) { - return new MatchResult(ex); + return MatchResult.From(_name, ex); } } } - return default; + return MatchResult.From(_name); } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageClientIPMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageClientIPMatcher.cs index 78d531fa..3c08fb11 100644 --- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageClientIPMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageClientIPMatcher.cs @@ -14,6 +14,8 @@ namespace WireMock.Matchers.Request; /// public class RequestMessageClientIPMatcher : IRequestMatcher { + private const string _name = nameof(RequestMessageClientIPMatcher); + /// /// The matchers /// @@ -77,8 +79,8 @@ public class RequestMessageClientIPMatcher : IRequestMatcher /// public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) { - var (score, exception) = GetMatchResult(requestMessage).Expand(); - return requestMatchResult.AddScore(GetType(), score, exception); + var matchDetail = GetMatchResult(requestMessage).ToMatchDetail(); + return requestMatchResult.AddMatchDetail(matchDetail); } private MatchResult GetMatchResult(IRequestMessage requestMessage) @@ -86,15 +88,15 @@ public class RequestMessageClientIPMatcher : IRequestMatcher if (Matchers != null) { var results = Matchers.Select(m => m.IsMatch(requestMessage.ClientIP)).ToArray(); - return MatchResult.From(results, MatchOperator); + return MatchResult.From(_name, results, MatchOperator); } if (Funcs != null) { var results = Funcs.Select(func => func(requestMessage.ClientIP)).ToArray(); - return MatchScores.ToScore(results, MatchOperator); + return MatchResult.From(_name, MatchScores.ToScore(results, MatchOperator)); } - return default; + return MatchResult.From(_name); } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageCookieMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageCookieMatcher.cs index 9613d48d..af985d83 100644 --- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageCookieMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageCookieMatcher.cs @@ -13,6 +13,8 @@ namespace WireMock.Matchers.Request; /// public class RequestMessageCookieMatcher : IRequestMatcher { + private const string _name = nameof(RequestMessageCookieMatcher); + /// /// MatchBehaviour /// @@ -104,7 +106,7 @@ public class RequestMessageCookieMatcher : IRequestMatcher { if (requestMessage.Cookies == null) { - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch); + return MatchResult.From(_name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch)); } // Check if we want to use IgnoreCase to compare the Cookie-Name and Cookie-Value @@ -112,19 +114,19 @@ public class RequestMessageCookieMatcher : IRequestMatcher if (Funcs != null) { - return MatchScores.ToScore(Funcs.Any(f => f(cookies))); + return MatchResult.From(_name, MatchScores.ToScore(Funcs.Any(f => f(cookies)))); } if (Matchers == null) { - return default; + return MatchResult.From(_name); } if (!cookies.ContainsKey(Name)) { - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch); + return MatchResult.From(_name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch)); } - return Matchers.Max(m => m.IsMatch(cookies[Name])); + return MatchResult.From(_name, Matchers.Max(m => m.IsMatch(cookies[Name]))?.Score ?? MatchScores.Mismatch); } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHeaderMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHeaderMatcher.cs index 77a5f04c..53d54dc5 100644 --- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHeaderMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHeaderMatcher.cs @@ -14,6 +14,8 @@ namespace WireMock.Matchers.Request; /// public class RequestMessageHeaderMatcher : IRequestMatcher { + private const string _name = nameof(RequestMessageCookieMatcher); + /// /// MatchBehaviour /// @@ -117,7 +119,7 @@ public class RequestMessageHeaderMatcher : IRequestMatcher { if (requestMessage.Headers == null) { - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch); + return MatchResult.From(_name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch)); } // Check if we want to use IgnoreCase to compare the Header-Name and Header-Value(s) @@ -126,14 +128,14 @@ public class RequestMessageHeaderMatcher : IRequestMatcher if (Funcs != null) { var funcResults = Funcs.Select(f => f(headers.ToDictionary(entry => entry.Key, entry => entry.Value.ToArray()))).ToArray(); - return MatchScores.ToScore(funcResults, MatchOperator); + return MatchResult.From(_name, MatchScores.ToScore(funcResults, MatchOperator)); } if (Matchers != null) { if (!headers.ContainsKey(Name)) { - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch); + return MatchResult.From(_name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch)); } var results = new List(); @@ -141,12 +143,12 @@ public class RequestMessageHeaderMatcher : IRequestMatcher { var resultsPerMatcher = headers[Name].Select(matcher.IsMatch).ToArray(); - results.Add(MatchResult.From(resultsPerMatcher, MatchOperator.And)); + results.Add(MatchResult.From(_name, resultsPerMatcher, MatchOperator.And)); } - return MatchResult.From(results, MatchOperator); + return MatchResult.From(_name, results, MatchOperator); } - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch); + return MatchResult.From(_name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch)); } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHttpVersionMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHttpVersionMatcher.cs index 2de8c924..6125806d 100644 --- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHttpVersionMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHttpVersionMatcher.cs @@ -11,6 +11,8 @@ namespace WireMock.Matchers.Request; /// public class RequestMessageHttpVersionMatcher : IRequestMatcher { + private const string _name = nameof(RequestMessageHttpVersionMatcher); + /// /// The matcher. /// @@ -19,7 +21,7 @@ public class RequestMessageHttpVersionMatcher : IRequestMatcher /// /// The func. /// - public Func? Func { get; } + public Func? MatcherOnStringFunc { get; } /// /// The @@ -61,7 +63,7 @@ public class RequestMessageHttpVersionMatcher : IRequestMatcher /// The function. public RequestMessageHttpVersionMatcher(Func func) { - Func = Guard.NotNull(func); + MatcherOnStringFunc = Guard.NotNull(func); } /// @@ -78,11 +80,11 @@ public class RequestMessageHttpVersionMatcher : IRequestMatcher return Matcher.IsMatch(requestMessage.HttpVersion); } - if (Func != null) + if (MatcherOnStringFunc != null) { - return MatchScores.ToScore(Func(requestMessage.HttpVersion)); + return MatchResult.From($"{_name}:{nameof(MatcherOnStringFunc)}", MatchScores.ToScore(MatcherOnStringFunc(requestMessage.HttpVersion))); } - return default; + return MatchResult.From(_name); } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMultiPartMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMultiPartMatcher.cs index 31e2ff93..31e8a0e7 100644 --- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMultiPartMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMultiPartMatcher.cs @@ -12,6 +12,11 @@ namespace WireMock.Matchers.Request; /// public class RequestMessageMultiPartMatcher : IRequestMatcher { + /// + /// The name of this matcher. + /// + public const string Name = "MultiPartMatcher"; + private readonly IMimeKitUtils _mimeKitUtils = LoadMimeKitUtils(); /// @@ -22,7 +27,7 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher /// /// The /// - public MatchOperator MatchOperator { get; } = MatchOperator.Or; + public MatchOperator MatchOperator { get; } = MatchOperator.And; /// /// The @@ -54,19 +59,20 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher /// public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) { - var score = MatchScores.Mismatch; + var matchDetail = MatchResult.From(Name).ToMatchDetail(); Exception? exception = null; - if (Matchers?.Any() != true) + if (Matchers == null) { - return requestMatchResult.AddScore(GetType(), score, null); + return requestMatchResult.AddMatchDetail(matchDetail); } if (!_mimeKitUtils.TryGetMimeMessage(requestMessage, out var message)) { - return requestMatchResult.AddScore(GetType(), score, null); + return requestMatchResult.AddMatchDetail(matchDetail); } + double score = MatchScores.Mismatch; try { foreach (var mimePartMatcher in Matchers.OfType().ToArray()) @@ -94,7 +100,7 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher exception = ex; } - return requestMatchResult.AddScore(GetType(), score, exception); + return requestMatchResult.AddMatchDetail(MatchResult.From(Name, score, exception).ToMatchDetail()); } private static IMimeKitUtils LoadMimeKitUtils() diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessagePathMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessagePathMatcher.cs index 3cf4c037..09f8e8df 100644 --- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessagePathMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessagePathMatcher.cs @@ -77,8 +77,8 @@ public class RequestMessagePathMatcher : IRequestMatcher /// public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) { - var (score, exception) = GetMatchResult(requestMessage).Expand(); - return requestMatchResult.AddScore(GetType(), score, exception); + var matchDetail = GetMatchResult(requestMessage).ToMatchDetail(); + return requestMatchResult.AddMatchDetail(matchDetail); } private MatchResult GetMatchResult(IRequestMessage requestMessage) @@ -86,15 +86,16 @@ public class RequestMessagePathMatcher : IRequestMatcher if (Matchers != null) { var results = Matchers.Select(m => m.IsMatch(requestMessage.Path)).ToArray(); - return MatchResult.From(results, MatchOperator); + return MatchResult.From(nameof(RequestMessagePathMatcher), results, MatchOperator); } if (Funcs != null) { var results = Funcs.Select(func => func(requestMessage.Path)).ToArray(); - return MatchScores.ToScore(results, MatchOperator); + var score = MatchScores.ToScore(results, MatchOperator); + return MatchResult.From(nameof(RequestMessagePathMatcher), score); } - return default; + return MatchResult.From(nameof(RequestMessagePathMatcher)); } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageUrlMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageUrlMatcher.cs index 810afb59..9e3986c9 100644 --- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageUrlMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageUrlMatcher.cs @@ -86,15 +86,16 @@ public class RequestMessageUrlMatcher : IRequestMatcher if (Matchers != null) { var results = Matchers.Select(m => m.IsMatch(requestMessage.Url)).ToArray(); - return MatchResult.From(results, MatchOperator); + return MatchResult.From(nameof(RequestMessageUrlMatcher), results, MatchOperator); } if (Funcs != null) { var results = Funcs.Select(func => func(requestMessage.Url)).ToArray(); - return MatchScores.ToScore(results, MatchOperator); + var score = MatchScores.ToScore(results, MatchOperator); + return MatchResult.From(nameof(RequestMessageUrlMatcher), score); } - return default; + return MatchResult.From(nameof(RequestMessageUrlMatcher)); } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Matchers/SimMetricsMatcher.cs b/src/WireMock.Net.Minimal/Matchers/SimMetricsMatcher.cs index eb730b43..1c2ab1d9 100644 --- a/src/WireMock.Net.Minimal/Matchers/SimMetricsMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/SimMetricsMatcher.cs @@ -86,7 +86,7 @@ public class SimMetricsMatcher : IStringMatcher IStringMetric stringMetricType = GetStringMetricType(); var score = MatchScores.ToScore(_patterns.Select(p => stringMetricType.GetSimilarity(p.GetPattern(), input)).ToArray(), MatchOperator); - return MatchBehaviourHelper.Convert(MatchBehaviour, score); + return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score)); } /// diff --git a/src/WireMock.Net.Minimal/Matchers/XPathMatcher.cs b/src/WireMock.Net.Minimal/Matchers/XPathMatcher.cs index 7504ce31..0065fe24 100644 --- a/src/WireMock.Net.Minimal/Matchers/XPathMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/XPathMatcher.cs @@ -116,7 +116,7 @@ public class XPathMatcher : IStringMatcher private MatchResult CreateMatchResult(double score, Exception? exception = null) { - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); + return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); } private sealed class XPathEvaluator diff --git a/src/WireMock.Net.Minimal/RequestBuilders/Request.WithMultiPart.cs b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithMultiPart.cs index 9e89e20a..6394fad8 100644 --- a/src/WireMock.Net.Minimal/RequestBuilders/Request.WithMultiPart.cs +++ b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithMultiPart.cs @@ -15,7 +15,7 @@ public partial class Request } /// - public IRequestBuilder WithMultiPart(IMatcher[] matchers, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.Or) + public IRequestBuilder WithMultiPart(IMatcher[] matchers, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.And) { _requestMatchers.Add(new RequestMessageMultiPartMatcher(matchBehaviour, matchOperator, matchers)); return this; diff --git a/src/WireMock.Net.Minimal/Serialization/LogEntryMapper.cs b/src/WireMock.Net.Minimal/Serialization/LogEntryMapper.cs index 53a5ba7e..c0f439de 100644 --- a/src/WireMock.Net.Minimal/Serialization/LogEntryMapper.cs +++ b/src/WireMock.Net.Minimal/Serialization/LogEntryMapper.cs @@ -166,11 +166,12 @@ internal class LogEntryMapper TotalScore = matchResult.TotalScore, TotalNumber = matchResult.TotalNumber, AverageTotalScore = matchResult.AverageTotalScore, - MatchDetails = matchResult.MatchDetails.Select(md => new - { - Name = md.MatcherType.Name.Replace("RequestMessage", string.Empty), - md.Score - } as object).ToList() + MatchDetails = matchResult.MatchDetails + //MatchDetails = matchResult.MatchDetails.Select(md => new + //{ + // Name = md.MatcherType.Name.Replace("RequestMessage", string.Empty), + // md.Score + //} as object).ToList() }; } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Serialization/MatcherMapper.cs b/src/WireMock.Net.Minimal/Serialization/MatcherMapper.cs index 39ad8fa4..4757e3bd 100644 --- a/src/WireMock.Net.Minimal/Serialization/MatcherMapper.cs +++ b/src/WireMock.Net.Minimal/Serialization/MatcherMapper.cs @@ -25,9 +25,9 @@ internal class MatcherMapper _settings = Guard.NotNull(settings); } - public IMatcher[]? Map(IEnumerable? matchers) + public IMatcher[] Map(IEnumerable? matchers) { - return matchers?.Select(Map).OfType().ToArray(); + return matchers?.Select(Map).OfType().ToArray() ?? []; } public IMatcher? Map(MatcherModel? matcherModel) diff --git a/src/WireMock.Net.Minimal/Server/WireMockServer.ConvertMapping.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.ConvertMapping.cs index d0f7622b..4c8d171b 100644 --- a/src/WireMock.Net.Minimal/Server/WireMockServer.ConvertMapping.cs +++ b/src/WireMock.Net.Minimal/Server/WireMockServer.ConvertMapping.cs @@ -6,6 +6,7 @@ using System.Linq; using Stef.Validation; using WireMock.Admin.Mappings; using WireMock.Matchers; +using WireMock.Matchers.Request; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using WireMock.Serialization; @@ -253,7 +254,15 @@ public partial class WireMockServer else if (requestModel.Body?.Matchers != null) { var matchOperator = StringUtils.ParseMatchOperator(requestModel.Body.MatchOperator); - requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matchers)!, matchOperator); + + if (requestModel.Body.MatcherName == RequestMessageMultiPartMatcher.Name) + { + requestBuilder = requestBuilder.WithMultiPart(_matcherMapper.Map(requestModel.Body.Matchers), matchOperator: matchOperator); + } + else + { + requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matchers), matchOperator); + } } return requestBuilder; diff --git a/src/WireMock.Net.ProtoBuf/Matchers/ProtoBufMatcher.cs b/src/WireMock.Net.ProtoBuf/Matchers/ProtoBufMatcher.cs index 7f79d896..14e4c35a 100644 --- a/src/WireMock.Net.ProtoBuf/Matchers/ProtoBufMatcher.cs +++ b/src/WireMock.Net.ProtoBuf/Matchers/ProtoBufMatcher.cs @@ -57,7 +57,7 @@ public class ProtoBufMatcher : IProtoBufMatcher /// public async Task IsMatchAsync(byte[]? input, CancellationToken cancellationToken = default) { - var result = new MatchResult(); + var result = MatchResult.From(Name); if (input != null) { @@ -65,11 +65,11 @@ public class ProtoBufMatcher : IProtoBufMatcher { var instance = await DecodeAsync(input, true, cancellationToken).ConfigureAwait(false); - result = Matcher?.IsMatch(instance) ?? new MatchResult(MatchScores.Perfect); + result = Matcher?.IsMatch(instance) ?? MatchResult.From(Name, MatchScores.Perfect); } - catch (Exception e) + catch (Exception ex) { - result = new MatchResult(MatchScores.Mismatch, e); + result = MatchResult.From(Name, ex); } } diff --git a/src/WireMock.Net.Shared/Matchers/ExactObjectMatcher.cs b/src/WireMock.Net.Shared/Matchers/ExactObjectMatcher.cs index f37590b1..51641bdb 100644 --- a/src/WireMock.Net.Shared/Matchers/ExactObjectMatcher.cs +++ b/src/WireMock.Net.Shared/Matchers/ExactObjectMatcher.cs @@ -68,7 +68,7 @@ public class ExactObjectMatcher : IObjectMatcher equals = Equals(Value, input); } - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(equals)); + return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(equals))); } /// diff --git a/src/WireMock.Net.Shared/Matchers/Helpers/BodyDataMatchScoreCalculator.cs b/src/WireMock.Net.Shared/Matchers/Helpers/BodyDataMatchScoreCalculator.cs index 3e179835..41b2876e 100644 --- a/src/WireMock.Net.Shared/Matchers/Helpers/BodyDataMatchScoreCalculator.cs +++ b/src/WireMock.Net.Shared/Matchers/Helpers/BodyDataMatchScoreCalculator.cs @@ -8,66 +8,68 @@ namespace WireMock.Matchers.Helpers; internal static class BodyDataMatchScoreCalculator { - internal static MatchResult CalculateMatchScore(IBodyData? requestMessage, IMatcher matcher) + private static string _name = nameof(BodyDataMatchScoreCalculator); + + internal static MatchResult CalculateMatchScore(IBodyData? bodyData, IMatcher matcher) { Guard.NotNull(matcher); - if (requestMessage == null) + if (bodyData == null) { - return default; + return MatchResult.From(_name); } if (matcher is NotNullOrEmptyMatcher notNullOrEmptyMatcher) { - switch (requestMessage.DetectedBodyType) + switch (bodyData.DetectedBodyType) { case BodyType.Json: case BodyType.String: case BodyType.FormUrlEncoded: - return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyAsString); + return notNullOrEmptyMatcher.IsMatch(bodyData.BodyAsString); case BodyType.Bytes: - return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyAsBytes); + return notNullOrEmptyMatcher.IsMatch(bodyData.BodyAsBytes); default: - return default; + return MatchResult.From(_name); } } if (matcher is ExactObjectMatcher { Value: byte[] } exactObjectMatcher) { // If the body is a byte array, try to match. - return exactObjectMatcher.IsMatch(requestMessage.BodyAsBytes); + return exactObjectMatcher.IsMatch(bodyData.BodyAsBytes); } // Check if the matcher is a IObjectMatcher if (matcher is IObjectMatcher objectMatcher) { // If the body is a JSON object, try to match. - if (requestMessage.DetectedBodyType == BodyType.Json) + if (bodyData.DetectedBodyType == BodyType.Json) { - return objectMatcher.IsMatch(requestMessage.BodyAsJson); + return objectMatcher.IsMatch(bodyData.BodyAsJson); } // If the body is a byte array, try to match. - if (requestMessage.DetectedBodyType == BodyType.Bytes) + if (bodyData.DetectedBodyType == BodyType.Bytes) { - return objectMatcher.IsMatch(requestMessage.BodyAsBytes); + return objectMatcher.IsMatch(bodyData.BodyAsBytes); } } - // In case the matcher is a IStringMatcher and If body is a Json or a String, use the BodyAsString to match on. - if (matcher is IStringMatcher stringMatcher && requestMessage.DetectedBodyType is BodyType.Json or BodyType.String or BodyType.FormUrlEncoded) + // In case the matcher is a IStringMatcher and if body is a Json or a String, use the BodyAsString to match on. + if (matcher is IStringMatcher stringMatcher && bodyData.DetectedBodyType is BodyType.Json or BodyType.String or BodyType.FormUrlEncoded) { - return stringMatcher.IsMatch(requestMessage.BodyAsString); + return stringMatcher.IsMatch(bodyData.BodyAsString); } // In case the matcher is a IProtoBufMatcher, use the BodyAsBytes to match on. if (matcher is IProtoBufMatcher protoBufMatcher) { - return protoBufMatcher.IsMatchAsync(requestMessage.BodyAsBytes).GetAwaiter().GetResult(); + return protoBufMatcher.IsMatchAsync(bodyData.BodyAsBytes).GetAwaiter().GetResult(); } - return default; + return MatchResult.From(_name); } -} +} \ No newline at end of file diff --git a/src/WireMock.Net.Shared/Matchers/MatchBehaviourHelper.cs b/src/WireMock.Net.Shared/Matchers/MatchBehaviourHelper.cs index a9450282..53d9b500 100644 --- a/src/WireMock.Net.Shared/Matchers/MatchBehaviourHelper.cs +++ b/src/WireMock.Net.Shared/Matchers/MatchBehaviourHelper.cs @@ -36,6 +36,6 @@ internal static class MatchBehaviourHelper /// match result internal static MatchResult Convert(MatchBehaviour matchBehaviour, MatchResult result) { - return matchBehaviour == MatchBehaviour.AcceptOnMatch ? result : new MatchResult(Convert(matchBehaviour, result.Score), result.Exception); + return matchBehaviour == MatchBehaviour.AcceptOnMatch ? result : MatchResult.From(result.Name, Convert(matchBehaviour, result.Score), result.Exception); } } \ No newline at end of file diff --git a/src/WireMock.Net.Shared/Matchers/MatchResult.cs b/src/WireMock.Net.Shared/Matchers/MatchResult.cs index 5419c1ab..f9bff87e 100644 --- a/src/WireMock.Net.Shared/Matchers/MatchResult.cs +++ b/src/WireMock.Net.Shared/Matchers/MatchResult.cs @@ -5,13 +5,14 @@ using System.Collections.Generic; using System.Linq; using Stef.Validation; using WireMock.Extensions; +using WireMock.Matchers.Request; namespace WireMock.Matchers; /// /// The MatchResult which contains the score (value between 0.0 - 1.0 of the similarity) and an optional error message. /// -public struct MatchResult +public class MatchResult { /// /// A value between 0.0 - 1.0 of the similarity. @@ -25,46 +26,55 @@ public struct MatchResult public Exception? Exception { get; set; } /// - /// Create a MatchResult + /// The name or description of the matcher. /// - /// A value between 0.0 - 1.0 of the similarity. - /// The exception in case the matching fails. [Optional] - public MatchResult(double score, Exception? exception = null) - { - Score = score; - Exception = exception; - } + public required string Name { get; set; } /// - /// Create a MatchResult + /// The child MatchResults in case of multiple matchers. /// - /// The exception in case the matching fails. - public MatchResult(Exception exception) - { - Exception = Guard.NotNull(exception); - } - - /// - /// Implicitly converts a double to a MatchResult. - /// - /// The score - public static implicit operator MatchResult(double score) - { - return new MatchResult(score); - } + public MatchResult[]? MatchResults { get; set; } /// /// Is the value a perfect match? /// public bool IsPerfect() => MatchScores.IsPerfect(Score); + /// + /// Create a MatchResult. + /// + /// The name or description of the matcher. + /// A value between 0.0 - 1.0 of the similarity. + /// The exception in case the matching fails. [Optional] + public static MatchResult From(string name, double score = 0, Exception? exception = null) + { + return new MatchResult + { + Name = name, + Score = score, + Exception = exception + }; + } + + /// + /// Create a MatchResult from exception. + /// + /// The name or description of the matcher. + /// The exception in case the matching fails. + /// MatchResult + public static MatchResult From(string name, Exception exception) + { + return From(name, MatchScores.Mismatch, exception); + } + /// /// Create a MatchResult from multiple MatchResults. /// + /// The name or description of the matcher. /// A list of MatchResults. /// The MatchOperator /// MatchResult - public static MatchResult From(IReadOnlyList matchResults, MatchOperator matchOperator) + public static MatchResult From(string name, IReadOnlyList matchResults, MatchOperator matchOperator) { Guard.NotNullOrEmpty(matchResults); @@ -75,6 +85,8 @@ public struct MatchResult return new MatchResult { + Name = name, + MatchResults = matchResults.ToArray(), Score = MatchScores.ToScore(matchResults.Select(r => r.Score).ToArray(), matchOperator), Exception = matchResults.Select(m => m.Exception).OfType().ToArray().ToException() }; @@ -88,4 +100,19 @@ public struct MatchResult { return (Score, Exception); } + + /// + /// Convert to . + /// + public MatchDetail ToMatchDetail() + { + return new MatchDetail + { + Name = Name, + MatcherType = typeof(MatchResult), + Score = Score, + Exception = Exception, + MatchDetails = MatchResults?.Select(mr => mr.ToMatchDetail()).ToArray() + }; + } } \ No newline at end of file diff --git a/src/WireMock.Net.Shared/Matchers/NotNullOrEmptyMatcher.cs b/src/WireMock.Net.Shared/Matchers/NotNullOrEmptyMatcher.cs index 51f89adf..666ab3e2 100644 --- a/src/WireMock.Net.Shared/Matchers/NotNullOrEmptyMatcher.cs +++ b/src/WireMock.Net.Shared/Matchers/NotNullOrEmptyMatcher.cs @@ -1,6 +1,5 @@ // Copyright © WireMock.Net -using System; using System.Linq; using AnyOfTypes; using WireMock.Extensions; @@ -53,7 +52,7 @@ public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher break; } - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)); + return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match))); } /// @@ -61,7 +60,7 @@ public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher { var match = !string.IsNullOrEmpty(input); - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)); + return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match))); } /// diff --git a/src/WireMock.Net.Shared/Matchers/RegexMatcher.cs b/src/WireMock.Net.Shared/Matchers/RegexMatcher.cs index fab9edf6..bf00622d 100644 --- a/src/WireMock.Net.Shared/Matchers/RegexMatcher.cs +++ b/src/WireMock.Net.Shared/Matchers/RegexMatcher.cs @@ -111,7 +111,7 @@ public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher } } - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); + return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); } /// diff --git a/src/WireMock.Net.Shared/Matchers/Request/RequestMessageGraphQLMatcher.cs b/src/WireMock.Net.Shared/Matchers/Request/RequestMessageGraphQLMatcher.cs index 313be8e5..960ad662 100644 --- a/src/WireMock.Net.Shared/Matchers/Request/RequestMessageGraphQLMatcher.cs +++ b/src/WireMock.Net.Shared/Matchers/Request/RequestMessageGraphQLMatcher.cs @@ -73,7 +73,7 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) { var results = CalculateMatchResults(requestMessage); - var (score, exception) = MatchResult.From(results, MatchOperator).Expand(); + var (score, exception) = MatchResult.From(nameof(RequestMessageGraphQLMatcher), results, MatchOperator).Expand(); return requestMatchResult.AddScore(GetType(), score, exception); } @@ -86,12 +86,12 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher return stringMatcher.IsMatch(requestMessage.BodyData.BodyAsString); } - return default; + return MatchResult.From(nameof(RequestMessageGraphQLMatcher)); } private IReadOnlyList CalculateMatchResults(IRequestMessage requestMessage) { - return Matchers == null ? [new MatchResult()] : Matchers.Select(matcher => CalculateMatchResult(requestMessage, matcher)).ToArray(); + return Matchers == null ? [MatchResult.From(nameof(RequestMessageGraphQLMatcher))] : Matchers.Select(matcher => CalculateMatchResult(requestMessage, matcher)).ToArray(); } private static IMatcher[] CreateMatcherArray( diff --git a/src/WireMock.Net.Shared/RequestBuilders/IMultiPartRequestBuilder.cs b/src/WireMock.Net.Shared/RequestBuilders/IMultiPartRequestBuilder.cs index 30e23a97..23f8e74a 100644 --- a/src/WireMock.Net.Shared/RequestBuilders/IMultiPartRequestBuilder.cs +++ b/src/WireMock.Net.Shared/RequestBuilders/IMultiPartRequestBuilder.cs @@ -23,7 +23,7 @@ public interface IMultiPartRequestBuilder : IHttpVersionBuilder /// The to use. /// The to use. /// The . - IRequestBuilder WithMultiPart(IMatcher[] matchers, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.Or); + IRequestBuilder WithMultiPart(IMatcher[] matchers, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.And); /// /// WithMultiPart: MatchBehaviour and IMatcher[] diff --git a/test/WireMock.Net.Tests.UsingNuGet/WireMock.Net.Tests.UsingNuGet.csproj b/test/WireMock.Net.Tests.UsingNuGet/WireMock.Net.Tests.UsingNuGet.csproj index 4facbda3..52ac5745 100644 --- a/test/WireMock.Net.Tests.UsingNuGet/WireMock.Net.Tests.UsingNuGet.csproj +++ b/test/WireMock.Net.Tests.UsingNuGet/WireMock.Net.Tests.UsingNuGet.csproj @@ -20,7 +20,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all diff --git a/test/WireMock.Net.Tests.UsingNuGet/WireMockServerTests.WithMultiPart.cs b/test/WireMock.Net.Tests.UsingNuGet/WireMockServerTests.WithMultiPart.cs index 4237df8e..57c9b0f8 100644 --- a/test/WireMock.Net.Tests.UsingNuGet/WireMockServerTests.WithMultiPart.cs +++ b/test/WireMock.Net.Tests.UsingNuGet/WireMockServerTests.WithMultiPart.cs @@ -27,7 +27,7 @@ public partial class WireMockServerTests var textPlainMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textPlainContentTypeMatcher, null, null, textPlainContentMatcher); var textJson = "{ \"Key\" : \"Value\" }"; - var textJsonContentType = "text/json"; + var textJsonContentType = "applicatiom/json"; var textJsonContentTypeMatcher = new ContentTypeMatcher(textJsonContentType); var textJsonContentMatcher = new JsonMatcher(new { Key = "Value" }, true); var jsonMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textJsonContentTypeMatcher, null, null, textJsonContentMatcher); diff --git a/test/WireMock.Net.Tests/Matchers/MimePartMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/MimePartMatcherTests.cs index c6b6596b..65106b31 100644 --- a/test/WireMock.Net.Tests/Matchers/MimePartMatcherTests.cs +++ b/test/WireMock.Net.Tests/Matchers/MimePartMatcherTests.cs @@ -15,32 +15,31 @@ public class MimePartMatcherTests private const string TestMultiPart = """ - From: - Date: Sun, 23 Jul 2023 16:13:13 +0200 - Subject: - Message-Id: - MIME-Version: 1.0 - Content-Type: multipart/mixed; boundary="=-5XgmpXt0XOfzdtcgNJc2ZQ==" + Content-Type: multipart/mixed; boundary=----MyBoundary123 - --=-5XgmpXt0XOfzdtcgNJc2ZQ== - Content-Type: text/plain; charset=utf-8 + ------MyBoundary123 + Content-Type: text/plain + Content-Disposition: form-data; name="textPart" - This is some plain text - --=-5XgmpXt0XOfzdtcgNJc2ZQ== - Content-Type: text/json; charset=utf-8 + This is some plain text. + ------MyBoundary123 + Content-Type: application/json + Content-Disposition: form-data; name="jsonPart" { - "Key": "Value" + "id": 42, + "message": "Hello from JSON" } - --=-5XgmpXt0XOfzdtcgNJc2ZQ== - Content-Type: image/png; name=image.png - Content-Disposition: attachment; filename=image.png + + ------MyBoundary123 + Content-Type: image/png + Content-Disposition: form-data; name="imagePart"; filename="example.png" Content-Transfer-Encoding: base64 - iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjl - AAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC + iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4 + //8wEzIABCMDgAEMwEAAAwAA//8DAKkCBf4AAAAASUVORK5CYII= - --=-5XgmpXt0XOfzdtcgNJc2ZQ==-- + ------MyBoundary123-- """; [Fact] @@ -52,7 +51,7 @@ public class MimePartMatcherTests // Act var contentTypeMatcher = new ContentTypeMatcher("text/plain"); - var contentMatcher = new ExactMatcher("This is some plain text"); + var contentMatcher = new ExactMatcher("This is some plain text."); var matcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, contentTypeMatcher, null, null, contentMatcher); var result = matcher.IsMatch(part); @@ -70,8 +69,8 @@ public class MimePartMatcherTests var part = message.BodyParts[1]; // Act - var contentTypeMatcher = new ContentTypeMatcher("text/json"); - var contentMatcher = new JsonMatcher(new { Key = "Value" }, true); + var contentTypeMatcher = new ContentTypeMatcher("application/json"); + var contentMatcher = new JsonPartialMatcher(new { id = 42 }, true); var matcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, contentTypeMatcher, null, null, contentMatcher); var result = matcher.IsMatch(part); @@ -89,9 +88,9 @@ public class MimePartMatcherTests // Act var contentTypeMatcher = new ContentTypeMatcher("image/png"); - var contentDispositionMatcher = new ExactMatcher("attachment; filename=\"image.png\""); + var contentDispositionMatcher = new WildcardMatcher("*filename=\"example.png\""); var contentTransferEncodingMatcher = new ExactMatcher("base64"); - var contentMatcher = new ExactObjectMatcher(Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC")); + var contentMatcher = new ExactObjectMatcher(Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4\r\n//8wEzIABCMDgAEMwEAAAwAA//8DAKkCBf4AAAAASUVORK5CYII=")); var matcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, contentTypeMatcher, contentDispositionMatcher, contentTransferEncodingMatcher, contentMatcher); var result = matcher.IsMatch(part); diff --git a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageBodyMatcherTests.cs b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageBodyMatcherTests.cs index ab53577a..27934771 100644 --- a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageBodyMatcherTests.cs +++ b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageBodyMatcherTests.cs @@ -29,7 +29,7 @@ public class RequestMessageBodyMatcherTests DetectedBodyType = BodyType.String }; var stringMatcherMock = new Mock(); - stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(1d); + stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), 1d)); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); @@ -61,10 +61,10 @@ public class RequestMessageBodyMatcherTests DetectedBodyType = BodyType.String }; var stringMatcherMock1 = new Mock(); - stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(one); + stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), one)); var stringMatcherMock2 = new Mock(); - stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(two); + stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), two)); var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object }; @@ -102,10 +102,10 @@ public class RequestMessageBodyMatcherTests DetectedBodyType = BodyType.String }; var stringMatcherMock1 = new Mock(); - stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(one); + stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), one)); var stringMatcherMock2 = new Mock(); - stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(two); + stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), two)); var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object }; @@ -143,10 +143,10 @@ public class RequestMessageBodyMatcherTests DetectedBodyType = BodyType.String }; var stringMatcherMock1 = new Mock(); - stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(one); + stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), one)); var stringMatcherMock2 = new Mock(); - stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(two); + stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), two)); var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object }; @@ -175,11 +175,11 @@ public class RequestMessageBodyMatcherTests // Assign var body = new BodyData { - BodyAsBytes = new byte[] { 1 }, + BodyAsBytes = [1], DetectedBodyType = BodyType.Bytes }; var stringMatcherMock = new Mock(); - stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(0.5d); + stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), 0.5d)); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); @@ -207,7 +207,7 @@ public class RequestMessageBodyMatcherTests DetectedBodyType = BodyType.Json }; var stringMatcherMock = new Mock(); - stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(1.0d); + stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), 1.0d)); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); @@ -235,7 +235,7 @@ public class RequestMessageBodyMatcherTests DetectedBodyType = BodyType.Json }; var stringMatcherMock = new Mock(); - stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(1d); + stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), 1d)); stringMatcherMock.SetupGet(m => m.MatchOperator).Returns(MatchOperator.Or); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); @@ -263,7 +263,7 @@ public class RequestMessageBodyMatcherTests DetectedBodyType = BodyType.Json }; var objectMatcherMock = new Mock(); - objectMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(1d); + objectMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), 1d)); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); @@ -387,7 +387,7 @@ public class RequestMessageBodyMatcherTests DetectedBodyType = BodyType.Bytes }; var objectMatcherMock = new Mock(); - objectMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(1.0d); + objectMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), 1.0d)); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); diff --git a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageGraphQLMatcherTests.cs b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageGraphQLMatcherTests.cs index e882ef23..88a8137c 100644 --- a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageGraphQLMatcherTests.cs +++ b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageGraphQLMatcherTests.cs @@ -25,7 +25,7 @@ public class RequestMessageGraphQLMatcherTests DetectedBodyType = BodyType.String }; var stringMatcherMock = new Mock(); - stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(1d); + stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), 1d)); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); @@ -57,10 +57,10 @@ public class RequestMessageGraphQLMatcherTests DetectedBodyType = BodyType.String }; var stringMatcherMock1 = new Mock(); - stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(one); + stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), one)); var stringMatcherMock2 = new Mock(); - stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(two); + stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), two)); var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object }; @@ -98,10 +98,10 @@ public class RequestMessageGraphQLMatcherTests DetectedBodyType = BodyType.String }; var stringMatcherMock1 = new Mock(); - stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(one); + stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), one)); var stringMatcherMock2 = new Mock(); - stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(two); + stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), two)); var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object }; @@ -139,10 +139,10 @@ public class RequestMessageGraphQLMatcherTests DetectedBodyType = BodyType.String }; var stringMatcherMock1 = new Mock(); - stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(one); + stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), one)); var stringMatcherMock2 = new Mock(); - stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(two); + stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), two)); var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object }; @@ -175,7 +175,7 @@ public class RequestMessageGraphQLMatcherTests DetectedBodyType = BodyType.Bytes }; var stringMatcherMock = new Mock(); - stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(0.5d); + stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(MatchResult.From(nameof(IStringMatcher), 0.5d)); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); diff --git a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageMultiPartMatcher.cs b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageMultiPartMatcher.cs index 36e2bda2..f883aaf3 100644 --- a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageMultiPartMatcher.cs +++ b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageMultiPartMatcher.cs @@ -153,6 +153,44 @@ AAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC score.Should().Be(MatchScores.Perfect); } + [Fact] + public void RequestMessageBodyMatcher_GetMatchingScore_BodyAsMultiPart_Issue1371() + { + var body = new BodyData + { + BodyAsString = + "--------------------------woli8b80pw4vBJtNpAMOKS\r\nContent-Disposition: form-data; name=\"metadata\"\r\nContent-Type: application/json\r\n\r\n{\"ID\": \"9858013b-e020-4ef9-b8a8-0bebc740e6a7\", \"DATE\": \"2025-08-15T13:45:30.0000000Z\", \"NAME\": \"32c9a8dd-e214-4afb-9611-9cde81f827c6\", \"NUMBER\": 10}\r\n--------------------------woli8b80pw4vBJtNpAMOKS--\r\n", + DetectedBodyType = BodyType.MultiPart + }; + + var headers = new Dictionary + { + { "Content-Type", [@"multipart/form-data; boundary=------------------------woli8b80pw4vBJtNpAMOKS"] } + }; + var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body, headers); + + var bodyMatcher = new MimePartMatcher( + MatchBehaviour.AcceptOnMatch, + new ContentTypeMatcher("application/json"), + null, // Content-Disposition + null, // Content-Transfer-Encoding + new JsonPartialMatcher(new { id = "9858013b-e020-4ef9-b8a8-0bebc740e6a7" }, true) + ); + + var matchers = new[] { bodyMatcher } + .OfType() + .ToArray(); + + var matcher = new RequestMessageMultiPartMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, matchers!); + + // Act + var result = new RequestMatchResult(); + var score = matcher.GetMatchingScore(requestMessage, result); + + // Assert + score.Should().Be(MatchScores.Perfect); + } + private static double GetScore(IMatcher? matcher1, IMatcher? matcher2, IMatcher? matcher3, MatchOperator matchOperator = MatchOperator.And) { // Assign diff --git a/test/WireMock.Net.Tests/Serialization/CustomPathParamMatcher.cs b/test/WireMock.Net.Tests/Serialization/CustomPathParamMatcher.cs index 9cf3c3ad..5885c974 100644 --- a/test/WireMock.Net.Tests/Serialization/CustomPathParamMatcher.cs +++ b/test/WireMock.Net.Tests/Serialization/CustomPathParamMatcher.cs @@ -46,7 +46,7 @@ public class CustomPathParamMatcher : IStringMatcher var inputParts = GetPathParts(input); if (inputParts.Length != _pathParts.Length) { - return MatchScores.Mismatch; + return MatchResult.From(Name); } try @@ -60,29 +60,29 @@ public class CustomPathParamMatcher : IStringMatcher var pathParamName = pathPart.Trim('{').Trim('}'); if (!_pathParams.ContainsKey(pathParamName)) { - return MatchScores.Mismatch; + return MatchResult.From(Name); } if (!Regex.IsMatch(inputPart, _pathParams[pathParamName], RegexOptions.IgnoreCase)) { - return MatchScores.Mismatch; + return MatchResult.From(Name); } } else { if (!inputPart.Equals(pathPart, StringComparison.InvariantCultureIgnoreCase)) { - return MatchScores.Mismatch; + return MatchResult.From(Name); } } } } catch { - return MatchScores.Mismatch; + return MatchResult.From(Name); } - return MatchScores.Perfect; + return MatchResult.From(Name, MatchScores.Perfect); } public AnyOf[] GetPatterns()