Fix MimePartMatcher and add more tests (#1389)

* mp

* .

* --return

* Fixed

* --

* ...

* fix

* ...

* .
This commit is contained in:
Stef Heyenrath
2026-01-24 09:15:43 +01:00
committed by GitHub
parent 317fcb1b30
commit 702e156ddc
56 changed files with 665 additions and 263 deletions

View File

@@ -152,6 +152,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.OpenTelemetry"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.OpenTelemetryDemo", "examples\WireMock.Net.OpenTelemetryDemo\WireMock.Net.OpenTelemetryDemo.csproj", "{9957038D-F9C3-CA5D-E8AE-BE188E512635}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.OpenTelemetryDemo", "examples\WireMock.Net.OpenTelemetryDemo\WireMock.Net.OpenTelemetryDemo.csproj", "{9957038D-F9C3-CA5D-E8AE-BE188E512635}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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|x64.Build.0 = Release|Any CPU
{9957038D-F9C3-CA5D-E8AE-BE188E512635}.Release|x86.ActiveCfg = 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 {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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -871,6 +885,7 @@ Global
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} {2DBBD70D-8051-441F-92BB-FF9B8B4B4982} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{C8F4E6D2-9A3B-4F1C-8D5E-7A2B3C4D5E6F} = {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} {9957038D-F9C3-CA5D-E8AE-BE188E512635} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{4005E20C-D42B-138A-79BE-B3F5420C563F} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458} SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<DefineConstants>$(DefineConstants);GRAPHQL;MIMEKIT;PROTOBUF</DefineConstants>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Content Include="__admin\mappings\*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="log4net.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -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"
}
}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="System.Configuration.IgnoreSectionHandler" />
</configSections>
<appSettings>
<add key="log4net.Internal.Debug" value="true"/>
</appSettings>
<log4net>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger{1} - %message%newline" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="ConsoleAppender" />
</root>
</log4net>
</configuration>

View File

@@ -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); 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 using var httpAndHttpsWithPort = WireMockServer.Start(new WireMockServerSettings
{ {
HostingScheme = HostingScheme.HttpAndHttps, HostingScheme = HostingScheme.HttpAndHttps,
@@ -556,39 +556,6 @@ namespace WireMock.Net.ConsoleApplication
); );
#endif #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 // 400 ms
server server
.Given(Request.Create() .Given(Request.Create()

View File

@@ -8,6 +8,12 @@ namespace WireMock.Admin.Mappings;
[FluentBuilder.AutoGenerateBuilder] [FluentBuilder.AutoGenerateBuilder]
public class BodyModel public class BodyModel
{ {
/// <summary>
/// The name of the body matcher.
/// Currently only "MultiPartMatcher" is supported.
/// </summary>
public string? MatcherName { get; set; }
/// <summary> /// <summary>
/// Gets or sets the matcher. /// Gets or sets the matcher.
/// </summary> /// </summary>

View File

@@ -1,6 +1,7 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Collections.Generic; using System.Collections.Generic;
using WireMock.Matchers.Request;
namespace WireMock.Admin.Requests; namespace WireMock.Admin.Requests;
@@ -47,5 +48,5 @@ public class LogRequestMatchModel
/// <value> /// <value>
/// The match details. /// The match details.
/// </value> /// </value>
public IList<object> MatchDetails { get; set; } public IList<MatchDetail> MatchDetails { get; set; } = [];
} }

View File

@@ -55,4 +55,11 @@ public interface IRequestMatchResult : IComparable
/// <param name="exception">The exception [Optional].</param> /// <param name="exception">The exception [Optional].</param>
/// <returns>The score.</returns> /// <returns>The score.</returns>
double AddScore(Type matcherType, double score, Exception? exception); double AddScore(Type matcherType, double score, Exception? exception);
/// <summary>
/// Adds the score.
/// </summary>
/// <param name="matchDetail">The matchDetail.</param>
/// <returns>The score.</returns>
double AddMatchDetail(MatchDetail matchDetail);
} }

View File

@@ -12,7 +12,12 @@ public class MatchDetail
/// <summary> /// <summary>
/// Gets or sets the type of the matcher. /// Gets or sets the type of the matcher.
/// </summary> /// </summary>
public Type MatcherType { get; set; } = null!; public required Type MatcherType { get; set; }
/// <summary>
/// Gets or sets the type of the matcher.
/// </summary>
public required string Name { get; set; }
/// <summary> /// <summary>
/// Gets or sets the score between 0.0 and 1.0 /// Gets or sets the score between 0.0 and 1.0
@@ -24,4 +29,9 @@ public class MatchDetail
/// [Optional] /// [Optional]
/// </summary> /// </summary>
public Exception? Exception { get; set; } public Exception? Exception { get; set; }
/// <summary>
/// The child MatchResults in case of multiple matchers.
/// </summary>
public MatchDetail[]? MatchDetails { get; set; }
} }

View File

@@ -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);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -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) private bool IsMatch(dynamic input, string pattern)

View File

@@ -1,6 +1,7 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System; using System;
using System.Collections.Generic;
using WireMock.Matchers.Helpers; using WireMock.Matchers.Helpers;
using WireMock.Models.Mime; using WireMock.Models.Mime;
using WireMock.Util; using WireMock.Util;
@@ -12,7 +13,7 @@ namespace WireMock.Matchers;
/// </summary> /// </summary>
public class MimePartMatcher : IMimePartMatcher public class MimePartMatcher : IMimePartMatcher
{ {
private readonly Func<IMimePartData, MatchResult>[] _funcs; private readonly IList<(string Name, Func<IMimePartData, MatchResult> func)> _matcherFunctions;
/// <inheritdoc /> /// <inheritdoc />
public string Name => nameof(MimePartMatcher); public string Name => nameof(MimePartMatcher);
@@ -49,34 +50,47 @@ public class MimePartMatcher : IMimePartMatcher
ContentTransferEncodingMatcher = contentTransferEncodingMatcher; ContentTransferEncodingMatcher = contentTransferEncodingMatcher;
ContentMatcher = contentMatcher; ContentMatcher = contentMatcher;
_funcs = _matcherFunctions = [];
[ if (ContentTypeMatcher != null)
mp => ContentTypeMatcher?.IsMatch(GetContentTypeAsString(mp.ContentType)) ?? MatchScores.Perfect, {
mp => ContentDispositionMatcher?.IsMatch(mp.ContentDisposition?.ToString()?.Replace("Content-Disposition: ", string.Empty)) ?? MatchScores.Perfect, _matcherFunctions.Add((nameof(ContentTypeMatcher), mp => ContentTypeMatcher.IsMatch(GetContentTypeAsString(mp.ContentType))));
mp => ContentTransferEncodingMatcher?.IsMatch(mp.ContentTransferEncoding.ToLowerInvariant()) ?? MatchScores.Perfect, }
MatchOnContent
]; 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));
}
} }
/// <inheritdoc /> /// <inheritdoc />
public MatchResult IsMatch(IMimePartData value) public MatchResult IsMatch(IMimePartData value)
{ {
var score = MatchScores.Mismatch; var results = new List<MatchResult>();
Exception? exception = null;
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);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -87,11 +101,6 @@ public class MimePartMatcher : IMimePartMatcher
private MatchResult MatchOnContent(IMimePartData mimePart) private MatchResult MatchOnContent(IMimePartData mimePart)
{ {
if (ContentMatcher == null)
{
return MatchScores.Perfect;
}
var bodyParserSettings = new BodyParserSettings var bodyParserSettings = new BodyParserSettings
{ {
Stream = mimePart.Open(), Stream = mimePart.Open(),
@@ -102,7 +111,7 @@ public class MimePartMatcher : IMimePartMatcher
}; };
var bodyData = BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false).GetAwaiter().GetResult(); 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) private static string? GetContentTypeAsString(IContentTypeData? contentType)

View File

@@ -52,7 +52,7 @@ internal class MimeKitUtils : IMimeKitUtils
return false; return false;
} }
var fixedBytes = FixBytes(bytes, contentTypeHeader[0]); var fixedBytes = PrependContentTypeHeader(bytes, contentTypeHeader[0]);
mimeMessageData = LoadFromStream(new MemoryStream(fixedBytes)); mimeMessageData = LoadFromStream(new MemoryStream(fixedBytes));
return true; return true;
@@ -68,7 +68,10 @@ internal class MimeKitUtils : IMimeKitUtils
return contentTypeHeader.Any(ct => ct.TrimStart().StartsWith("multipart/", StringComparison.OrdinalIgnoreCase)); return contentTypeHeader.Any(ct => ct.TrimStart().StartsWith("multipart/", StringComparison.OrdinalIgnoreCase));
} }
private static byte[] FixBytes(byte[] bytes, WireMockList<string> contentType) /// <summary>
/// Prepends the Content-Type header to the byte array to make it a valid MIME message for MimeKit.
/// </summary>
private static byte[] PrependContentTypeHeader(byte[] bytes, WireMockList<string> contentType)
{ {
var contentTypeBytes = Encoding.UTF8.GetBytes($"{HttpKnownHeaderNames.ContentType}: {contentType}\r\n\r\n"); var contentTypeBytes = Encoding.UTF8.GetBytes($"{HttpKnownHeaderNames.ContentType}: {contentType}\r\n\r\n");

View File

@@ -54,7 +54,7 @@ internal class AzureADAuthenticationMatcher : IStringMatcher
{ {
if (string.IsNullOrEmpty(input)) if (string.IsNullOrEmpty(input))
{ {
return MatchScores.Mismatch; return MatchResult.From(Name);
} }
var token = Regex.Replace(input, BearerPrefix, string.Empty, RegexOptions.IgnoreCase, RegexConstants.DefaultTimeout); 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.) // Throws an Exception as the token is invalid (expired, invalid-formatted, tenant mismatch, etc.)
_jwtSecurityTokenHandler.ValidateToken(token, validationParameters, out _); _jwtSecurityTokenHandler.ValidateToken(token, validationParameters, out _);
return MatchScores.Perfect; return MatchResult.From(Name, MatchScores.Perfect);
} }
catch (Exception ex) catch (Exception ex)
{ {
return new MatchResult(MatchScores.Mismatch, ex); return MatchResult.From(Name, ex);
} }
} }

View File

@@ -69,7 +69,7 @@ public class WireMockConsoleLogger : IWireMockLogger
/// <see cref="IWireMockLogger.DebugRequestResponse"/> /// <see cref="IWireMockLogger.DebugRequestResponse"/>
public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest) 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)); Console.WriteLine(Format("DebugRequestResponse", "Admin[{0}] {1}", isAdminRequest, message));
} }

View File

@@ -0,0 +1,46 @@
// Copyright © WireMock.Net
using System;
namespace WireMock.Matchers;
/// <summary>
/// Represents a matcher that combines multiple matching strategies into a single composite operation.
/// </summary>
public class CompositeMatcher : IMatcher
{
/// <inheritdoc />
public string Name => nameof(CompositeMatcher);
/// <summary>
/// The logical operator used to combine the results of the matchers.
/// </summary>
public MatchOperator MatchOperator { get; }
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <summary>
/// All matchers.
/// </summary>
public IMatcher[] Matchers { get; }
/// <summary>
/// Initializes a new instance of the CompositeMatcher class with the specified matchers, operator, and match behaviour.
/// </summary>
/// <param name="matchers">An array of matchers to be combined. Cannot be null or contain null elements.</param>
/// <param name="matchOperator">The logical operator used to combine the results of the matchers.</param>
/// <param name="matchBehaviour">The behaviour that determines how the composite matcher interprets the combined results.</param>
public CompositeMatcher(IMatcher[] matchers, MatchOperator matchOperator, MatchBehaviour matchBehaviour)
{
Matchers = matchers;
MatchOperator = matchOperator;
MatchBehaviour = matchBehaviour;
}
/// <inheritdoc />
public string GetCSharpCodeArguments()
{
throw new NotImplementedException();
}
}

View File

@@ -62,7 +62,7 @@ public class ContentTypeMatcher : WildcardMatcher
{ {
if (string.IsNullOrEmpty(input) || !MediaTypeHeaderValue.TryParse(input, out var contentType)) 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); return base.IsMatch(contentType.MediaType);

View File

@@ -75,7 +75,7 @@ public class ExactMatcher : IStringMatcher, IIgnoreCaseMatcher
: pattern => pattern == input; : pattern => pattern == input;
var score = MatchScores.ToScore(_values.Select(v => equals(v)).ToArray(), MatchOperator); 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));
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -106,18 +106,18 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
// Input is null or empty and if no patterns defined, return Perfect match. // Input is null or empty and if no patterns defined, return Perfect match.
if (string.IsNullOrEmpty(input) && _patterns.Length == 0) 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)) if (!QueryStringParser.TryParse(input, IgnoreCase, out var inputNameValueCollection))
{ {
return new MatchResult(MatchScores.Mismatch); return MatchResult.From(Name, MatchScores.Mismatch);
} }
var matches = GetMatches(inputNameValueCollection); var matches = GetMatches(inputNameValueCollection);
var score = MatchScores.ToScore(matches, MatchOperator); 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<string, string> inputNameValueCollection) private bool[] GetMatches(IDictionary<string, string> inputNameValueCollection)

View File

@@ -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);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -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);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -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);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -102,7 +102,7 @@ public class JmesPathMatcher : IStringMatcher, IObjectMatcher
return IsMatch(inputAsString); return IsMatch(inputAsString);
} }
return MatchBehaviourHelper.Convert(MatchBehaviour, score); return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score));
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -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);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -24,14 +24,26 @@ public class RequestMatchResult : IRequestMatchResult
public double AverageTotalScore => TotalNumber == 0 ? MatchScores.Mismatch : TotalScore / TotalNumber; public double AverageTotalScore => TotalNumber == 0 ? MatchScores.Mismatch : TotalScore / TotalNumber;
/// <inheritdoc /> /// <inheritdoc />
public IList<MatchDetail> MatchDetails { get; } = new List<MatchDetail>(); public IList<MatchDetail> MatchDetails { get; } = [];
/// <inheritdoc /> /// <inheritdoc />
public double AddScore(Type matcherType, double score, Exception? exception) 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; /// <inheritdoc />
public double AddMatchDetail(MatchDetail matchDetail)
{
MatchDetails.Add(matchDetail);
return matchDetail.Score;
} }
/// <summary> /// <summary>

View File

@@ -17,27 +17,27 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// <summary> /// <summary>
/// The body function /// The body function
/// </summary> /// </summary>
public Func<string?, bool>? Func { get; } public Func<string?, bool>? MatchOnBodyAsStringFunc { get; }
/// <summary> /// <summary>
/// The body data function for byte[] /// The body data function for byte[]
/// </summary> /// </summary>
public Func<byte[]?, bool>? DataFunc { get; } public Func<byte[]?, bool>? MatchOnBodyAsBytesFunc { get; }
/// <summary> /// <summary>
/// The body data function for json /// The body data function for json
/// </summary> /// </summary>
public Func<object?, bool>? JsonFunc { get; } public Func<object?, bool>? MatchOnBodyAsJsonFunc { get; }
/// <summary> /// <summary>
/// The body data function for BodyData /// The body data function for BodyData
/// </summary> /// </summary>
public Func<IBodyData?, bool>? BodyDataFunc { get; } public Func<IBodyData?, bool>? MatchOnBodyAsBodyDataFunc { get; }
/// <summary> /// <summary>
/// The body data function for FormUrlEncoded /// The body data function for FormUrlEncoded
/// </summary> /// </summary>
public Func<IDictionary<string, string>?, bool>? FormUrlEncodedFunc { get; } public Func<IDictionary<string, string>?, bool>? MatchOnBodyAsFormUrlEncodedFunc { get; }
/// <summary> /// <summary>
/// The matchers. /// The matchers.
@@ -85,7 +85,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// <param name="func">The function.</param> /// <param name="func">The function.</param>
public RequestMessageBodyMatcher(Func<string?, bool> func) public RequestMessageBodyMatcher(Func<string?, bool> func)
{ {
Func = Guard.NotNull(func); MatchOnBodyAsStringFunc = Guard.NotNull(func);
} }
/// <summary> /// <summary>
@@ -94,7 +94,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// <param name="func">The function.</param> /// <param name="func">The function.</param>
public RequestMessageBodyMatcher(Func<byte[]?, bool> func) public RequestMessageBodyMatcher(Func<byte[]?, bool> func)
{ {
DataFunc = Guard.NotNull(func); MatchOnBodyAsBytesFunc = Guard.NotNull(func);
} }
/// <summary> /// <summary>
@@ -103,7 +103,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// <param name="func">The function.</param> /// <param name="func">The function.</param>
public RequestMessageBodyMatcher(Func<object?, bool> func) public RequestMessageBodyMatcher(Func<object?, bool> func)
{ {
JsonFunc = Guard.NotNull(func); MatchOnBodyAsJsonFunc = Guard.NotNull(func);
} }
/// <summary> /// <summary>
@@ -112,7 +112,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// <param name="func">The function.</param> /// <param name="func">The function.</param>
public RequestMessageBodyMatcher(Func<IBodyData?, bool> func) public RequestMessageBodyMatcher(Func<IBodyData?, bool> func)
{ {
BodyDataFunc = Guard.NotNull(func); MatchOnBodyAsBodyDataFunc = Guard.NotNull(func);
} }
/// <summary> /// <summary>
@@ -121,7 +121,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// <param name="func">The function.</param> /// <param name="func">The function.</param>
public RequestMessageBodyMatcher(Func<IDictionary<string, string>?, bool> func) public RequestMessageBodyMatcher(Func<IDictionary<string, string>?, bool> func)
{ {
FormUrlEncodedFunc = Guard.NotNull(func); MatchOnBodyAsFormUrlEncodedFunc = Guard.NotNull(func);
} }
/// <summary> /// <summary>
@@ -147,43 +147,43 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// <inheritdoc /> /// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) 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); return requestMatchResult.AddScore(GetType(), score, exception);
} }
private MatchResult CalculateMatchScore(IRequestMessage requestMessage) private MatchResult CalculateMatchResult(IRequestMessage requestMessage)
{ {
if (Matchers != null && Matchers.Any()) if (Matchers != null && Matchers.Any())
{ {
var results = Matchers.Select(matcher => BodyDataMatchScoreCalculator.CalculateMatchScore(requestMessage.BodyData, matcher)).ToArray(); 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));
} }
} }

View File

@@ -11,6 +11,8 @@ namespace WireMock.Matchers.Request;
/// </summary> /// </summary>
public class RequestMessageBodyMatcher<T> : IRequestMatcher public class RequestMessageBodyMatcher<T> : IRequestMatcher
{ {
private const string _name = nameof(RequestMessageBodyMatcher<T>);
/// <summary> /// <summary>
/// The body data function for type T /// The body data function for type T
/// </summary> /// </summary>
@@ -46,15 +48,15 @@ public class RequestMessageBodyMatcher<T> : IRequestMatcher
try try
{ {
var bodyAsT = jsonObject.ToObject<T>(); var bodyAsT = jsonObject.ToObject<T>();
return MatchScores.ToScore(Func(bodyAsT)); return MatchResult.From(_name, MatchScores.ToScore(Func(bodyAsT)));
} }
catch (Exception ex) catch (Exception ex)
{ {
return new MatchResult(ex); return MatchResult.From(_name, ex);
} }
} }
} }
return default; return MatchResult.From(_name);
} }
} }

View File

@@ -14,6 +14,8 @@ namespace WireMock.Matchers.Request;
/// </summary> /// </summary>
public class RequestMessageClientIPMatcher : IRequestMatcher public class RequestMessageClientIPMatcher : IRequestMatcher
{ {
private const string _name = nameof(RequestMessageClientIPMatcher);
/// <summary> /// <summary>
/// The matchers /// The matchers
/// </summary> /// </summary>
@@ -77,8 +79,8 @@ public class RequestMessageClientIPMatcher : IRequestMatcher
/// <inheritdoc /> /// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{ {
var (score, exception) = GetMatchResult(requestMessage).Expand(); var matchDetail = GetMatchResult(requestMessage).ToMatchDetail();
return requestMatchResult.AddScore(GetType(), score, exception); return requestMatchResult.AddMatchDetail(matchDetail);
} }
private MatchResult GetMatchResult(IRequestMessage requestMessage) private MatchResult GetMatchResult(IRequestMessage requestMessage)
@@ -86,15 +88,15 @@ public class RequestMessageClientIPMatcher : IRequestMatcher
if (Matchers != null) if (Matchers != null)
{ {
var results = Matchers.Select(m => m.IsMatch(requestMessage.ClientIP)).ToArray(); var results = Matchers.Select(m => m.IsMatch(requestMessage.ClientIP)).ToArray();
return MatchResult.From(results, MatchOperator); return MatchResult.From(_name, results, MatchOperator);
} }
if (Funcs != null) if (Funcs != null)
{ {
var results = Funcs.Select(func => func(requestMessage.ClientIP)).ToArray(); 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);
} }
} }

View File

@@ -13,6 +13,8 @@ namespace WireMock.Matchers.Request;
/// <inheritdoc cref="IRequestMatcher"/> /// <inheritdoc cref="IRequestMatcher"/>
public class RequestMessageCookieMatcher : IRequestMatcher public class RequestMessageCookieMatcher : IRequestMatcher
{ {
private const string _name = nameof(RequestMessageCookieMatcher);
/// <summary> /// <summary>
/// MatchBehaviour /// MatchBehaviour
/// </summary> /// </summary>
@@ -104,7 +106,7 @@ public class RequestMessageCookieMatcher : IRequestMatcher
{ {
if (requestMessage.Cookies == null) 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 // 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) 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) if (Matchers == null)
{ {
return default; return MatchResult.From(_name);
} }
if (!cookies.ContainsKey(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);
} }
} }

View File

@@ -14,6 +14,8 @@ namespace WireMock.Matchers.Request;
/// <inheritdoc cref="IRequestMatcher"/> /// <inheritdoc cref="IRequestMatcher"/>
public class RequestMessageHeaderMatcher : IRequestMatcher public class RequestMessageHeaderMatcher : IRequestMatcher
{ {
private const string _name = nameof(RequestMessageCookieMatcher);
/// <summary> /// <summary>
/// MatchBehaviour /// MatchBehaviour
/// </summary> /// </summary>
@@ -117,7 +119,7 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
{ {
if (requestMessage.Headers == null) 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) // 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) if (Funcs != null)
{ {
var funcResults = Funcs.Select(f => f(headers.ToDictionary(entry => entry.Key, entry => entry.Value.ToArray()))).ToArray(); 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 (Matchers != null)
{ {
if (!headers.ContainsKey(Name)) if (!headers.ContainsKey(Name))
{ {
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch); return MatchResult.From(_name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch));
} }
var results = new List<MatchResult>(); var results = new List<MatchResult>();
@@ -141,12 +143,12 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
{ {
var resultsPerMatcher = headers[Name].Select(matcher.IsMatch).ToArray(); 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));
} }
} }

View File

@@ -11,6 +11,8 @@ namespace WireMock.Matchers.Request;
/// </summary> /// </summary>
public class RequestMessageHttpVersionMatcher : IRequestMatcher public class RequestMessageHttpVersionMatcher : IRequestMatcher
{ {
private const string _name = nameof(RequestMessageHttpVersionMatcher);
/// <summary> /// <summary>
/// The matcher. /// The matcher.
/// </summary> /// </summary>
@@ -19,7 +21,7 @@ public class RequestMessageHttpVersionMatcher : IRequestMatcher
/// <summary> /// <summary>
/// The func. /// The func.
/// </summary> /// </summary>
public Func<string, bool>? Func { get; } public Func<string, bool>? MatcherOnStringFunc { get; }
/// <summary> /// <summary>
/// The <see cref="MatchBehaviour"/> /// The <see cref="MatchBehaviour"/>
@@ -61,7 +63,7 @@ public class RequestMessageHttpVersionMatcher : IRequestMatcher
/// <param name="func">The function.</param> /// <param name="func">The function.</param>
public RequestMessageHttpVersionMatcher(Func<string, bool> func) public RequestMessageHttpVersionMatcher(Func<string, bool> func)
{ {
Func = Guard.NotNull(func); MatcherOnStringFunc = Guard.NotNull(func);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -78,11 +80,11 @@ public class RequestMessageHttpVersionMatcher : IRequestMatcher
return Matcher.IsMatch(requestMessage.HttpVersion); 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);
} }
} }

View File

@@ -12,6 +12,11 @@ namespace WireMock.Matchers.Request;
/// </summary> /// </summary>
public class RequestMessageMultiPartMatcher : IRequestMatcher public class RequestMessageMultiPartMatcher : IRequestMatcher
{ {
/// <summary>
/// The name of this matcher.
/// </summary>
public const string Name = "MultiPartMatcher";
private readonly IMimeKitUtils _mimeKitUtils = LoadMimeKitUtils(); private readonly IMimeKitUtils _mimeKitUtils = LoadMimeKitUtils();
/// <summary> /// <summary>
@@ -22,7 +27,7 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher
/// <summary> /// <summary>
/// The <see cref="MatchOperator"/> /// The <see cref="MatchOperator"/>
/// </summary> /// </summary>
public MatchOperator MatchOperator { get; } = MatchOperator.Or; public MatchOperator MatchOperator { get; } = MatchOperator.And;
/// <summary> /// <summary>
/// The <see cref="MatchBehaviour"/> /// The <see cref="MatchBehaviour"/>
@@ -54,19 +59,20 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher
/// <inheritdoc /> /// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{ {
var score = MatchScores.Mismatch; var matchDetail = MatchResult.From(Name).ToMatchDetail();
Exception? exception = null; 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)) if (!_mimeKitUtils.TryGetMimeMessage(requestMessage, out var message))
{ {
return requestMatchResult.AddScore(GetType(), score, null); return requestMatchResult.AddMatchDetail(matchDetail);
} }
double score = MatchScores.Mismatch;
try try
{ {
foreach (var mimePartMatcher in Matchers.OfType<IMimePartMatcher>().ToArray()) foreach (var mimePartMatcher in Matchers.OfType<IMimePartMatcher>().ToArray())
@@ -94,7 +100,7 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher
exception = ex; exception = ex;
} }
return requestMatchResult.AddScore(GetType(), score, exception); return requestMatchResult.AddMatchDetail(MatchResult.From(Name, score, exception).ToMatchDetail());
} }
private static IMimeKitUtils LoadMimeKitUtils() private static IMimeKitUtils LoadMimeKitUtils()

View File

@@ -77,8 +77,8 @@ public class RequestMessagePathMatcher : IRequestMatcher
/// <inheritdoc /> /// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{ {
var (score, exception) = GetMatchResult(requestMessage).Expand(); var matchDetail = GetMatchResult(requestMessage).ToMatchDetail();
return requestMatchResult.AddScore(GetType(), score, exception); return requestMatchResult.AddMatchDetail(matchDetail);
} }
private MatchResult GetMatchResult(IRequestMessage requestMessage) private MatchResult GetMatchResult(IRequestMessage requestMessage)
@@ -86,15 +86,16 @@ public class RequestMessagePathMatcher : IRequestMatcher
if (Matchers != null) if (Matchers != null)
{ {
var results = Matchers.Select(m => m.IsMatch(requestMessage.Path)).ToArray(); 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) if (Funcs != null)
{ {
var results = Funcs.Select(func => func(requestMessage.Path)).ToArray(); 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));
} }
} }

View File

@@ -86,15 +86,16 @@ public class RequestMessageUrlMatcher : IRequestMatcher
if (Matchers != null) if (Matchers != null)
{ {
var results = Matchers.Select(m => m.IsMatch(requestMessage.Url)).ToArray(); 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) if (Funcs != null)
{ {
var results = Funcs.Select(func => func(requestMessage.Url)).ToArray(); 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));
} }
} }

View File

@@ -86,7 +86,7 @@ public class SimMetricsMatcher : IStringMatcher
IStringMetric stringMetricType = GetStringMetricType(); IStringMetric stringMetricType = GetStringMetricType();
var score = MatchScores.ToScore(_patterns.Select(p => stringMetricType.GetSimilarity(p.GetPattern(), input)).ToArray(), MatchOperator); 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));
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -116,7 +116,7 @@ public class XPathMatcher : IStringMatcher
private MatchResult CreateMatchResult(double score, Exception? exception = null) 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 private sealed class XPathEvaluator

View File

@@ -15,7 +15,7 @@ public partial class Request
} }
/// <inheritdoc /> /// <inheritdoc />
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)); _requestMatchers.Add(new RequestMessageMultiPartMatcher(matchBehaviour, matchOperator, matchers));
return this; return this;

View File

@@ -166,11 +166,12 @@ internal class LogEntryMapper
TotalScore = matchResult.TotalScore, TotalScore = matchResult.TotalScore,
TotalNumber = matchResult.TotalNumber, TotalNumber = matchResult.TotalNumber,
AverageTotalScore = matchResult.AverageTotalScore, AverageTotalScore = matchResult.AverageTotalScore,
MatchDetails = matchResult.MatchDetails.Select(md => new MatchDetails = matchResult.MatchDetails
{ //MatchDetails = matchResult.MatchDetails.Select(md => new
Name = md.MatcherType.Name.Replace("RequestMessage", string.Empty), //{
md.Score // Name = md.MatcherType.Name.Replace("RequestMessage", string.Empty),
} as object).ToList() // md.Score
//} as object).ToList()
}; };
} }
} }

View File

@@ -25,9 +25,9 @@ internal class MatcherMapper
_settings = Guard.NotNull(settings); _settings = Guard.NotNull(settings);
} }
public IMatcher[]? Map(IEnumerable<MatcherModel>? matchers) public IMatcher[] Map(IEnumerable<MatcherModel>? matchers)
{ {
return matchers?.Select(Map).OfType<IMatcher>().ToArray(); return matchers?.Select(Map).OfType<IMatcher>().ToArray() ?? [];
} }
public IMatcher? Map(MatcherModel? matcherModel) public IMatcher? Map(MatcherModel? matcherModel)

View File

@@ -6,6 +6,7 @@ using System.Linq;
using Stef.Validation; using Stef.Validation;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.RequestBuilders; using WireMock.RequestBuilders;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
using WireMock.Serialization; using WireMock.Serialization;
@@ -253,7 +254,15 @@ public partial class WireMockServer
else if (requestModel.Body?.Matchers != null) else if (requestModel.Body?.Matchers != null)
{ {
var matchOperator = StringUtils.ParseMatchOperator(requestModel.Body.MatchOperator); 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; return requestBuilder;

View File

@@ -57,7 +57,7 @@ public class ProtoBufMatcher : IProtoBufMatcher
/// <inheritdoc /> /// <inheritdoc />
public async Task<MatchResult> IsMatchAsync(byte[]? input, CancellationToken cancellationToken = default) public async Task<MatchResult> IsMatchAsync(byte[]? input, CancellationToken cancellationToken = default)
{ {
var result = new MatchResult(); var result = MatchResult.From(Name);
if (input != null) if (input != null)
{ {
@@ -65,11 +65,11 @@ public class ProtoBufMatcher : IProtoBufMatcher
{ {
var instance = await DecodeAsync(input, true, cancellationToken).ConfigureAwait(false); 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);
} }
} }

View File

@@ -68,7 +68,7 @@ public class ExactObjectMatcher : IObjectMatcher
equals = Equals(Value, input); equals = Equals(Value, input);
} }
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(equals)); return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(equals)));
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -8,66 +8,68 @@ namespace WireMock.Matchers.Helpers;
internal static class BodyDataMatchScoreCalculator 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); Guard.NotNull(matcher);
if (requestMessage == null) if (bodyData == null)
{ {
return default; return MatchResult.From(_name);
} }
if (matcher is NotNullOrEmptyMatcher notNullOrEmptyMatcher) if (matcher is NotNullOrEmptyMatcher notNullOrEmptyMatcher)
{ {
switch (requestMessage.DetectedBodyType) switch (bodyData.DetectedBodyType)
{ {
case BodyType.Json: case BodyType.Json:
case BodyType.String: case BodyType.String:
case BodyType.FormUrlEncoded: case BodyType.FormUrlEncoded:
return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyAsString); return notNullOrEmptyMatcher.IsMatch(bodyData.BodyAsString);
case BodyType.Bytes: case BodyType.Bytes:
return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyAsBytes); return notNullOrEmptyMatcher.IsMatch(bodyData.BodyAsBytes);
default: default:
return default; return MatchResult.From(_name);
} }
} }
if (matcher is ExactObjectMatcher { Value: byte[] } exactObjectMatcher) if (matcher is ExactObjectMatcher { Value: byte[] } exactObjectMatcher)
{ {
// If the body is a byte array, try to match. // 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 // Check if the matcher is a IObjectMatcher
if (matcher is IObjectMatcher objectMatcher) if (matcher is IObjectMatcher objectMatcher)
{ {
// If the body is a JSON object, try to match. // 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 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. // 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) 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. // In case the matcher is a IProtoBufMatcher, use the BodyAsBytes to match on.
if (matcher is IProtoBufMatcher protoBufMatcher) 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);
} }
} }

View File

@@ -36,6 +36,6 @@ internal static class MatchBehaviourHelper
/// <returns>match result</returns> /// <returns>match result</returns>
internal static MatchResult Convert(MatchBehaviour matchBehaviour, MatchResult 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);
} }
} }

View File

@@ -5,13 +5,14 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Stef.Validation; using Stef.Validation;
using WireMock.Extensions; using WireMock.Extensions;
using WireMock.Matchers.Request;
namespace WireMock.Matchers; namespace WireMock.Matchers;
/// <summary> /// <summary>
/// The MatchResult which contains the score (value between 0.0 - 1.0 of the similarity) and an optional error message. /// The MatchResult which contains the score (value between 0.0 - 1.0 of the similarity) and an optional error message.
/// </summary> /// </summary>
public struct MatchResult public class MatchResult
{ {
/// <summary> /// <summary>
/// A value between 0.0 - 1.0 of the similarity. /// A value between 0.0 - 1.0 of the similarity.
@@ -25,46 +26,55 @@ public struct MatchResult
public Exception? Exception { get; set; } public Exception? Exception { get; set; }
/// <summary> /// <summary>
/// Create a MatchResult /// The name or description of the matcher.
/// </summary> /// </summary>
/// <param name="score">A value between 0.0 - 1.0 of the similarity.</param> public required string Name { get; set; }
/// <param name="exception">The exception in case the matching fails. [Optional]</param>
public MatchResult(double score, Exception? exception = null)
{
Score = score;
Exception = exception;
}
/// <summary> /// <summary>
/// Create a MatchResult /// The child MatchResults in case of multiple matchers.
/// </summary> /// </summary>
/// <param name="exception">The exception in case the matching fails.</param> public MatchResult[]? MatchResults { get; set; }
public MatchResult(Exception exception)
{
Exception = Guard.NotNull(exception);
}
/// <summary>
/// Implicitly converts a double to a MatchResult.
/// </summary>
/// <param name="score">The score</param>
public static implicit operator MatchResult(double score)
{
return new MatchResult(score);
}
/// <summary> /// <summary>
/// Is the value a perfect match? /// Is the value a perfect match?
/// </summary> /// </summary>
public bool IsPerfect() => MatchScores.IsPerfect(Score); public bool IsPerfect() => MatchScores.IsPerfect(Score);
/// <summary>
/// Create a MatchResult.
/// </summary>
/// <param name="name">The name or description of the matcher.</param>
/// <param name="score">A value between 0.0 - 1.0 of the similarity.</param>
/// <param name="exception">The exception in case the matching fails. [Optional]</param>
public static MatchResult From(string name, double score = 0, Exception? exception = null)
{
return new MatchResult
{
Name = name,
Score = score,
Exception = exception
};
}
/// <summary>
/// Create a MatchResult from exception.
/// </summary>
/// <param name="name">The name or description of the matcher.</param>
/// <param name="exception">The exception in case the matching fails.</param>
/// <returns>MatchResult</returns>
public static MatchResult From(string name, Exception exception)
{
return From(name, MatchScores.Mismatch, exception);
}
/// <summary> /// <summary>
/// Create a MatchResult from multiple MatchResults. /// Create a MatchResult from multiple MatchResults.
/// </summary> /// </summary>
/// <param name="name">The name or description of the matcher.</param>
/// <param name="matchResults">A list of MatchResults.</param> /// <param name="matchResults">A list of MatchResults.</param>
/// <param name="matchOperator">The MatchOperator</param> /// <param name="matchOperator">The MatchOperator</param>
/// <returns>MatchResult</returns> /// <returns>MatchResult</returns>
public static MatchResult From(IReadOnlyList<MatchResult> matchResults, MatchOperator matchOperator) public static MatchResult From(string name, IReadOnlyList<MatchResult> matchResults, MatchOperator matchOperator)
{ {
Guard.NotNullOrEmpty(matchResults); Guard.NotNullOrEmpty(matchResults);
@@ -75,6 +85,8 @@ public struct MatchResult
return new MatchResult return new MatchResult
{ {
Name = name,
MatchResults = matchResults.ToArray(),
Score = MatchScores.ToScore(matchResults.Select(r => r.Score).ToArray(), matchOperator), Score = MatchScores.ToScore(matchResults.Select(r => r.Score).ToArray(), matchOperator),
Exception = matchResults.Select(m => m.Exception).OfType<Exception>().ToArray().ToException() Exception = matchResults.Select(m => m.Exception).OfType<Exception>().ToArray().ToException()
}; };
@@ -88,4 +100,19 @@ public struct MatchResult
{ {
return (Score, Exception); return (Score, Exception);
} }
/// <summary>
/// Convert to <see cref="MatchResult"/>.
/// </summary>
public MatchDetail ToMatchDetail()
{
return new MatchDetail
{
Name = Name,
MatcherType = typeof(MatchResult),
Score = Score,
Exception = Exception,
MatchDetails = MatchResults?.Select(mr => mr.ToMatchDetail()).ToArray()
};
}
} }

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System;
using System.Linq; using System.Linq;
using AnyOfTypes; using AnyOfTypes;
using WireMock.Extensions; using WireMock.Extensions;
@@ -53,7 +52,7 @@ public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher
break; break;
} }
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)); return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)));
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -61,7 +60,7 @@ public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher
{ {
var match = !string.IsNullOrEmpty(input); var match = !string.IsNullOrEmpty(input);
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)); return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)));
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -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);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -73,7 +73,7 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{ {
var results = CalculateMatchResults(requestMessage); 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); return requestMatchResult.AddScore(GetType(), score, exception);
} }
@@ -86,12 +86,12 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher
return stringMatcher.IsMatch(requestMessage.BodyData.BodyAsString); return stringMatcher.IsMatch(requestMessage.BodyData.BodyAsString);
} }
return default; return MatchResult.From(nameof(RequestMessageGraphQLMatcher));
} }
private IReadOnlyList<MatchResult> CalculateMatchResults(IRequestMessage requestMessage) private IReadOnlyList<MatchResult> 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( private static IMatcher[] CreateMatcherArray(

View File

@@ -23,7 +23,7 @@ public interface IMultiPartRequestBuilder : IHttpVersionBuilder
/// <param name="matchBehaviour">The <see cref="MatchBehaviour"/> to use.</param> /// <param name="matchBehaviour">The <see cref="MatchBehaviour"/> to use.</param>
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use.</param> /// <param name="matchOperator">The <see cref="MatchOperator"/> to use.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns> /// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithMultiPart(IMatcher[] matchers, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.Or); IRequestBuilder WithMultiPart(IMatcher[] matchers, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.And);
/// <summary> /// <summary>
/// WithMultiPart: MatchBehaviour and IMatcher[] /// WithMultiPart: MatchBehaviour and IMatcher[]

View File

@@ -20,7 +20,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="WireMock.Net" Version="1.8.11" /> <PackageReference Include="WireMock.Net" Version="1.23.0" />
<PackageReference Include="xunit" Version="2.9.3" /> <PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.1"> <PackageReference Include="xunit.runner.visualstudio" Version="3.1.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>

View File

@@ -27,7 +27,7 @@ public partial class WireMockServerTests
var textPlainMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textPlainContentTypeMatcher, null, null, textPlainContentMatcher); var textPlainMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textPlainContentTypeMatcher, null, null, textPlainContentMatcher);
var textJson = "{ \"Key\" : \"Value\" }"; var textJson = "{ \"Key\" : \"Value\" }";
var textJsonContentType = "text/json"; var textJsonContentType = "applicatiom/json";
var textJsonContentTypeMatcher = new ContentTypeMatcher(textJsonContentType); var textJsonContentTypeMatcher = new ContentTypeMatcher(textJsonContentType);
var textJsonContentMatcher = new JsonMatcher(new { Key = "Value" }, true); var textJsonContentMatcher = new JsonMatcher(new { Key = "Value" }, true);
var jsonMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textJsonContentTypeMatcher, null, null, textJsonContentMatcher); var jsonMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textJsonContentTypeMatcher, null, null, textJsonContentMatcher);

View File

@@ -15,32 +15,31 @@ public class MimePartMatcherTests
private const string TestMultiPart = private const string TestMultiPart =
""" """
From: Content-Type: multipart/mixed; boundary=----MyBoundary123
Date: Sun, 23 Jul 2023 16:13:13 +0200
Subject:
Message-Id: <HZ3K1HEAJKU4.IO57XCVO4BWV@desktop-6dd5qi2>
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="=-5XgmpXt0XOfzdtcgNJc2ZQ=="
--=-5XgmpXt0XOfzdtcgNJc2ZQ== ------MyBoundary123
Content-Type: text/plain; charset=utf-8 Content-Type: text/plain
Content-Disposition: form-data; name="textPart"
This is some plain text This is some plain text.
--=-5XgmpXt0XOfzdtcgNJc2ZQ== ------MyBoundary123
Content-Type: text/json; charset=utf-8 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 ------MyBoundary123
Content-Disposition: attachment; filename=image.png Content-Type: image/png
Content-Disposition: form-data; name="imagePart"; filename="example.png"
Content-Transfer-Encoding: base64 Content-Transfer-Encoding: base64
iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjl iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4
AAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC //8wEzIABCMDgAEMwEAAAwAA//8DAKkCBf4AAAAASUVORK5CYII=
--=-5XgmpXt0XOfzdtcgNJc2ZQ==-- ------MyBoundary123--
"""; """;
[Fact] [Fact]
@@ -52,7 +51,7 @@ public class MimePartMatcherTests
// Act // Act
var contentTypeMatcher = new ContentTypeMatcher("text/plain"); 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 matcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, contentTypeMatcher, null, null, contentMatcher);
var result = matcher.IsMatch(part); var result = matcher.IsMatch(part);
@@ -70,8 +69,8 @@ public class MimePartMatcherTests
var part = message.BodyParts[1]; var part = message.BodyParts[1];
// Act // Act
var contentTypeMatcher = new ContentTypeMatcher("text/json"); var contentTypeMatcher = new ContentTypeMatcher("application/json");
var contentMatcher = new JsonMatcher(new { Key = "Value" }, true); var contentMatcher = new JsonPartialMatcher(new { id = 42 }, true);
var matcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, contentTypeMatcher, null, null, contentMatcher); var matcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, contentTypeMatcher, null, null, contentMatcher);
var result = matcher.IsMatch(part); var result = matcher.IsMatch(part);
@@ -89,9 +88,9 @@ public class MimePartMatcherTests
// Act // Act
var contentTypeMatcher = new ContentTypeMatcher("image/png"); 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 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 matcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, contentTypeMatcher, contentDispositionMatcher, contentTransferEncodingMatcher, contentMatcher);
var result = matcher.IsMatch(part); var result = matcher.IsMatch(part);

View File

@@ -29,7 +29,7 @@ public class RequestMessageBodyMatcherTests
DetectedBodyType = BodyType.String DetectedBodyType = BodyType.String
}; };
var stringMatcherMock = new Mock<IStringMatcher>(); var stringMatcherMock = new Mock<IStringMatcher>();
stringMatcherMock.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(1d); stringMatcherMock.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(MatchResult.From(nameof(IStringMatcher), 1d));
var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);
@@ -61,10 +61,10 @@ public class RequestMessageBodyMatcherTests
DetectedBodyType = BodyType.String DetectedBodyType = BodyType.String
}; };
var stringMatcherMock1 = new Mock<IStringMatcher>(); var stringMatcherMock1 = new Mock<IStringMatcher>();
stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(one); stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(MatchResult.From(nameof(IStringMatcher), one));
var stringMatcherMock2 = new Mock<IStringMatcher>(); var stringMatcherMock2 = new Mock<IStringMatcher>();
stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(two); stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(MatchResult.From(nameof(IStringMatcher), two));
var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object }; var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object };
@@ -102,10 +102,10 @@ public class RequestMessageBodyMatcherTests
DetectedBodyType = BodyType.String DetectedBodyType = BodyType.String
}; };
var stringMatcherMock1 = new Mock<IStringMatcher>(); var stringMatcherMock1 = new Mock<IStringMatcher>();
stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(one); stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(MatchResult.From(nameof(IStringMatcher), one));
var stringMatcherMock2 = new Mock<IStringMatcher>(); var stringMatcherMock2 = new Mock<IStringMatcher>();
stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(two); stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(MatchResult.From(nameof(IStringMatcher), two));
var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object }; var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object };
@@ -143,10 +143,10 @@ public class RequestMessageBodyMatcherTests
DetectedBodyType = BodyType.String DetectedBodyType = BodyType.String
}; };
var stringMatcherMock1 = new Mock<IStringMatcher>(); var stringMatcherMock1 = new Mock<IStringMatcher>();
stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(one); stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(MatchResult.From(nameof(IStringMatcher), one));
var stringMatcherMock2 = new Mock<IStringMatcher>(); var stringMatcherMock2 = new Mock<IStringMatcher>();
stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(two); stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(MatchResult.From(nameof(IStringMatcher), two));
var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object }; var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object };
@@ -175,11 +175,11 @@ public class RequestMessageBodyMatcherTests
// Assign // Assign
var body = new BodyData var body = new BodyData
{ {
BodyAsBytes = new byte[] { 1 }, BodyAsBytes = [1],
DetectedBodyType = BodyType.Bytes DetectedBodyType = BodyType.Bytes
}; };
var stringMatcherMock = new Mock<IStringMatcher>(); var stringMatcherMock = new Mock<IStringMatcher>();
stringMatcherMock.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(0.5d); stringMatcherMock.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(MatchResult.From(nameof(IStringMatcher), 0.5d));
var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);
@@ -207,7 +207,7 @@ public class RequestMessageBodyMatcherTests
DetectedBodyType = BodyType.Json DetectedBodyType = BodyType.Json
}; };
var stringMatcherMock = new Mock<IStringMatcher>(); var stringMatcherMock = new Mock<IStringMatcher>();
stringMatcherMock.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(1.0d); stringMatcherMock.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(MatchResult.From(nameof(IStringMatcher), 1.0d));
var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);
@@ -235,7 +235,7 @@ public class RequestMessageBodyMatcherTests
DetectedBodyType = BodyType.Json DetectedBodyType = BodyType.Json
}; };
var stringMatcherMock = new Mock<IStringMatcher>(); var stringMatcherMock = new Mock<IStringMatcher>();
stringMatcherMock.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(1d); stringMatcherMock.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(MatchResult.From(nameof(IStringMatcher), 1d));
stringMatcherMock.SetupGet(m => m.MatchOperator).Returns(MatchOperator.Or); stringMatcherMock.SetupGet(m => m.MatchOperator).Returns(MatchOperator.Or);
var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);
@@ -263,7 +263,7 @@ public class RequestMessageBodyMatcherTests
DetectedBodyType = BodyType.Json DetectedBodyType = BodyType.Json
}; };
var objectMatcherMock = new Mock<IObjectMatcher>(); var objectMatcherMock = new Mock<IObjectMatcher>();
objectMatcherMock.Setup(m => m.IsMatch(It.IsAny<object>())).Returns(1d); objectMatcherMock.Setup(m => m.IsMatch(It.IsAny<object>())).Returns(MatchResult.From(nameof(IStringMatcher), 1d));
var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);
@@ -387,7 +387,7 @@ public class RequestMessageBodyMatcherTests
DetectedBodyType = BodyType.Bytes DetectedBodyType = BodyType.Bytes
}; };
var objectMatcherMock = new Mock<IObjectMatcher>(); var objectMatcherMock = new Mock<IObjectMatcher>();
objectMatcherMock.Setup(m => m.IsMatch(It.IsAny<object>())).Returns(1.0d); objectMatcherMock.Setup(m => m.IsMatch(It.IsAny<object>())).Returns(MatchResult.From(nameof(IStringMatcher), 1.0d));
var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);

View File

@@ -25,7 +25,7 @@ public class RequestMessageGraphQLMatcherTests
DetectedBodyType = BodyType.String DetectedBodyType = BodyType.String
}; };
var stringMatcherMock = new Mock<IStringMatcher>(); var stringMatcherMock = new Mock<IStringMatcher>();
stringMatcherMock.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(1d); stringMatcherMock.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(MatchResult.From(nameof(IStringMatcher), 1d));
var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);
@@ -57,10 +57,10 @@ public class RequestMessageGraphQLMatcherTests
DetectedBodyType = BodyType.String DetectedBodyType = BodyType.String
}; };
var stringMatcherMock1 = new Mock<IStringMatcher>(); var stringMatcherMock1 = new Mock<IStringMatcher>();
stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(one); stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(MatchResult.From(nameof(IStringMatcher), one));
var stringMatcherMock2 = new Mock<IStringMatcher>(); var stringMatcherMock2 = new Mock<IStringMatcher>();
stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(two); stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(MatchResult.From(nameof(IStringMatcher), two));
var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object }; var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object };
@@ -98,10 +98,10 @@ public class RequestMessageGraphQLMatcherTests
DetectedBodyType = BodyType.String DetectedBodyType = BodyType.String
}; };
var stringMatcherMock1 = new Mock<IStringMatcher>(); var stringMatcherMock1 = new Mock<IStringMatcher>();
stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(one); stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(MatchResult.From(nameof(IStringMatcher), one));
var stringMatcherMock2 = new Mock<IStringMatcher>(); var stringMatcherMock2 = new Mock<IStringMatcher>();
stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(two); stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(MatchResult.From(nameof(IStringMatcher), two));
var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object }; var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object };
@@ -139,10 +139,10 @@ public class RequestMessageGraphQLMatcherTests
DetectedBodyType = BodyType.String DetectedBodyType = BodyType.String
}; };
var stringMatcherMock1 = new Mock<IStringMatcher>(); var stringMatcherMock1 = new Mock<IStringMatcher>();
stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(one); stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(MatchResult.From(nameof(IStringMatcher), one));
var stringMatcherMock2 = new Mock<IStringMatcher>(); var stringMatcherMock2 = new Mock<IStringMatcher>();
stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(two); stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(MatchResult.From(nameof(IStringMatcher), two));
var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object }; var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object };
@@ -175,7 +175,7 @@ public class RequestMessageGraphQLMatcherTests
DetectedBodyType = BodyType.Bytes DetectedBodyType = BodyType.Bytes
}; };
var stringMatcherMock = new Mock<IStringMatcher>(); var stringMatcherMock = new Mock<IStringMatcher>();
stringMatcherMock.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(0.5d); stringMatcherMock.Setup(m => m.IsMatch(It.IsAny<string>())).Returns(MatchResult.From(nameof(IStringMatcher), 0.5d));
var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);

View File

@@ -153,6 +153,44 @@ AAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC
score.Should().Be(MatchScores.Perfect); 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<string, string[]>
{
{ "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<IMatcher>()
.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) private static double GetScore(IMatcher? matcher1, IMatcher? matcher2, IMatcher? matcher3, MatchOperator matchOperator = MatchOperator.And)
{ {
// Assign // Assign

View File

@@ -46,7 +46,7 @@ public class CustomPathParamMatcher : IStringMatcher
var inputParts = GetPathParts(input); var inputParts = GetPathParts(input);
if (inputParts.Length != _pathParts.Length) if (inputParts.Length != _pathParts.Length)
{ {
return MatchScores.Mismatch; return MatchResult.From(Name);
} }
try try
@@ -60,29 +60,29 @@ public class CustomPathParamMatcher : IStringMatcher
var pathParamName = pathPart.Trim('{').Trim('}'); var pathParamName = pathPart.Trim('{').Trim('}');
if (!_pathParams.ContainsKey(pathParamName)) if (!_pathParams.ContainsKey(pathParamName))
{ {
return MatchScores.Mismatch; return MatchResult.From(Name);
} }
if (!Regex.IsMatch(inputPart, _pathParams[pathParamName], RegexOptions.IgnoreCase)) if (!Regex.IsMatch(inputPart, _pathParams[pathParamName], RegexOptions.IgnoreCase))
{ {
return MatchScores.Mismatch; return MatchResult.From(Name);
} }
} }
else else
{ {
if (!inputPart.Equals(pathPart, StringComparison.InvariantCultureIgnoreCase)) if (!inputPart.Equals(pathPart, StringComparison.InvariantCultureIgnoreCase))
{ {
return MatchScores.Mismatch; return MatchResult.From(Name);
} }
} }
} }
} }
catch catch
{ {
return MatchScores.Mismatch; return MatchResult.From(Name);
} }
return MatchScores.Perfect; return MatchResult.From(Name, MatchScores.Perfect);
} }
public AnyOf<string, StringPattern>[] GetPatterns() public AnyOf<string, StringPattern>[] GetPatterns()