Fix FormUrlEncodedMatcher

This commit is contained in:
Stef Heyenrath
2026-08-18 22:25:15 +02:00
parent 42353f035e
commit a31f4768a8
16 changed files with 382 additions and 132 deletions
@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System.Linq;
using AnyOfTypes;
using WireMock.Models;
@@ -1,8 +1,6 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using WireMock.Types;
namespace WireMock.Matchers;
@@ -57,7 +55,7 @@ public static class MatchScores
/// <param name="values">The values.</param>
/// <param name="matchOperator">The <see cref="MatchOperator"/>.</param>
/// <returns>average score</returns>
public static double ToScore(IReadOnlyCollection<bool> values, MatchOperator matchOperator)
public static double ToScore(IEnumerable<bool> values, MatchOperator matchOperator)
{
return ToScore(values.Select(ToScore).ToArray(), matchOperator);
}
@@ -68,7 +66,7 @@ public static class MatchScores
/// <param name="values">The values.</param>
/// <param name="matchOperator"></param>
/// <returns>average score</returns>
public static double ToScore(IReadOnlyCollection<double> values, MatchOperator matchOperator)
public static double ToScore(IEnumerable<double> values, MatchOperator matchOperator)
{
if (!values.Any())
{
@@ -82,4 +80,29 @@ public static class MatchScores
_ => values.Average()
};
}
internal static double ToScore(WireMockList<string> values, IStringMatcher[] matchers, MatchOperator matchOperator = MatchOperator.And)
{
// Create a matrix of scores where each row corresponds to a value and each column corresponds to a matcher.
var matrix = values
.Select(value => matchers
.Select(matcher => matcher.IsMatch(value).Score).ToArray()
)
.ToArray();
if (matrix.Length == 0 || matchers.Length == 0)
{
return Mismatch;
}
// For each value, how well was it matched by its best matcher?
var rowRange = Enumerable.Range(0, matchers.Length);
var rowScore = matchOperator == MatchOperator.And ? matrix.Average(row => row.Max()) : matrix.Max(row => row.Max());
// For each matcher, how well was it satisfied by its best value?
var columnRange = Enumerable.Range(0, matchers.Length);
var columnScore = matchOperator == MatchOperator.And ? columnRange.Average(column => matrix.Max(row => row[column])) : columnRange.Max(column => matrix.Max(row => row[column]));
return matchOperator == MatchOperator.And ? Math.Min(rowScore, columnScore) : Math.Max(rowScore, columnScore);
}
}
+1 -1
View File
@@ -22,7 +22,7 @@ public class BodyData : IBodyData
public string? BodyAsString { get; set; }
/// <inheritdoc />
public IDictionary<string, string>? BodyAsFormUrlEncoded { get; set; }
public IDictionary<string, WireMockList<string>>? BodyAsFormUrlEncoded { get; set; }
/// <inheritdoc />
public object? BodyAsJson { get; set; }
@@ -1,8 +1,7 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using WireMock.Matchers;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.RequestBuilders;
@@ -100,5 +99,5 @@ public interface IBodyRequestBuilder : IMultiPartRequestBuilder
/// </summary>
/// <param name="func">The form-urlencoded values.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(Func<IDictionary<string, string>?, bool> func);
IRequestBuilder WithBody(Func<IDictionary<string, WireMockList<string>>?, bool> func);
}
@@ -1,9 +1,6 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using WireMock.Types;
@@ -16,7 +13,7 @@ internal static class QueryStringParser
{
private static readonly Dictionary<string, WireMockList<string>> Empty = new();
public static bool TryParse(string? queryString, bool caseIgnore, [NotNullWhen(true)] out IDictionary<string, string>? nameValueCollection)
public static bool TryParse(string? queryString, bool caseIgnore, [NotNullWhen(true)] out IDictionary<string, WireMockList<string>>? nameValueCollection)
{
if (queryString == null)
{
@@ -29,12 +26,22 @@ internal static class QueryStringParser
.Select(parameter => parameter.Split('='))
.Distinct();
nameValueCollection = caseIgnore ? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) : new Dictionary<string, string>();
nameValueCollection = caseIgnore ? new Dictionary<string, WireMockList<string>>(StringComparer.OrdinalIgnoreCase) : new Dictionary<string, WireMockList<string>>();
foreach (var part in parts)
{
if (part.Length == 2)
{
nameValueCollection.Add(part[0], WebUtility.UrlDecode(part[1]));
var key = part[0];
var value = WebUtility.UrlDecode(part[1]);
if (!nameValueCollection.TryGetValue(key, out var stringList))
{
nameValueCollection.Add(key, value);
}
else
{
stringList.Add(value);
}
}
}