diff --git a/src/WireMock.Net.Abstractions/Models/IBodyData.cs b/src/WireMock.Net.Abstractions/Models/IBodyData.cs
index 6eb4c5d8..e293689c 100644
--- a/src/WireMock.Net.Abstractions/Models/IBodyData.cs
+++ b/src/WireMock.Net.Abstractions/Models/IBodyData.cs
@@ -46,7 +46,7 @@ public interface IBodyData
///
/// The body as Form UrlEncoded dictionary.
///
- IDictionary? BodyAsFormUrlEncoded { get; set; }
+ IDictionary>? BodyAsFormUrlEncoded { get; set; }
///
/// The detected body type (detection based on body content).
diff --git a/src/WireMock.Net.Abstractions/Types/WireMockList.cs b/src/WireMock.Net.Abstractions/Types/WireMockList.cs
index de7d0f29..cdb4dedd 100644
--- a/src/WireMock.Net.Abstractions/Types/WireMockList.cs
+++ b/src/WireMock.Net.Abstractions/Types/WireMockList.cs
@@ -46,6 +46,98 @@ public class WireMockList : List
/// The values to set.
public static implicit operator WireMockList(T[] values) => new(values);
+ ///
+ /// Operator for equality comparison from WireMockList to T
+ ///
+ public static bool operator ==(WireMockList? left, T? right)
+ {
+ if (ReferenceEquals(left, right))
+ {
+ return true;
+ }
+
+ if (left?.Count == 1 && Equals(left[0], right))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Operator for equality comparison from T to WireMockList
+ ///
+ public static bool operator ==(T? left, WireMockList? right)
+ {
+ if (ReferenceEquals(left, right))
+ {
+ return true;
+ }
+
+ if (right?.Count == 1 && Equals(left, right[0]))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Operator for inequality comparison from WireMockList to T
+ ///
+ public static bool operator !=(WireMockList? left, T? right) => !(left == right);
+
+ ///
+ /// Operator for inequality comparison from T to WireMockList
+ ///
+ public static bool operator !=(T? left, WireMockList? right) => !(left == right);
+
+ ///
+ /// Determines whether the specified object is equal to the current instance.
+ /// Two instances are equal if they contain the same elements in the same order.
+ ///
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+
+ if (obj is WireMockList other)
+ {
+ if (Count != other.Count)
+ {
+ return false;
+ }
+
+ for (var i = 0; i < Count; i++)
+ {
+ if (!Equals(this[i], other[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Returns a hash code for this instance based on its elements.
+ ///
+ public override int GetHashCode()
+ {
+ var hashCode = 17;
+ foreach (var item in this)
+ {
+ hashCode = hashCode * 31 + (item?.GetHashCode() ?? 0);
+ }
+
+ return hashCode;
+ }
+
///
/// Returns a that represents this instance.
///
@@ -61,7 +153,7 @@ public class WireMockList : List
{
return strValue;
}
-
+
return this[0]?.ToString() ?? string.Empty;
default:
diff --git a/src/WireMock.Net.Minimal/Matchers/FormUrlEncodedMatcher.cs b/src/WireMock.Net.Minimal/Matchers/FormUrlEncodedMatcher.cs
index e83bafc4..02ef334e 100644
--- a/src/WireMock.Net.Minimal/Matchers/FormUrlEncodedMatcher.cs
+++ b/src/WireMock.Net.Minimal/Matchers/FormUrlEncodedMatcher.cs
@@ -1,10 +1,10 @@
// Copyright © WireMock.Net
-using System.Linq;
using AnyOfTypes;
using Stef.Validation;
using WireMock.Extensions;
using WireMock.Models;
+using WireMock.Types;
using WireMock.Util;
namespace WireMock.Matchers;
@@ -21,7 +21,7 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
///
public MatchBehaviour MatchBehaviour { get; }
- private readonly List<(WildcardMatcher Key, WildcardMatcher? Value)> _pairs = [];
+ private readonly List<(WildcardMatcher Key, WildcardMatcher[]? Values)> KeyValueMatchers = [];
///
/// Initializes a new instance of the class.
@@ -91,9 +91,10 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
{
foreach (var nameValue in nameValueCollection)
{
- var keyMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, [nameValue.Key], ignoreCase, MatchOperator);
- var valueMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, [nameValue.Value], ignoreCase, MatchOperator);
- _pairs.Add((keyMatcher, valueMatcher));
+ var keyMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, nameValue.Key, ignoreCase);
+ var valueMatchers = nameValue.Value.Select(value => new WildcardMatcher(MatchBehaviour.AcceptOnMatch, value, ignoreCase)).ToArray();
+
+ KeyValueMatchers.Add((keyMatcher, valueMatchers));
}
}
}
@@ -116,37 +117,51 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
var matches = GetMatches(inputNameValueCollection);
var score = MatchScores.ToScore(matches, MatchOperator);
- return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score));
+ return MatchResult.From(Name, score);
}
- private bool[] GetMatches(IDictionary inputNameValueCollection)
+ private List GetMatches(IDictionary> inputNameValueCollection)
{
- var matches = new List();
- if (_pairs.Count > inputNameValueCollection.Count)
+ var inputPairs = inputNameValueCollection.ToArray();
+ var rowCount = inputPairs.Length;
+ var columnCount = KeyValueMatchers.Count;
+
+ if (rowCount == 0 && columnCount == 0)
{
- matches.AddRange(Enumerable.Repeat(false, _pairs.Count - inputNameValueCollection.Count));
+ return [];
}
- foreach (var inputKeyValuePair in inputNameValueCollection)
+ var matrix = new double[rowCount][];
+
+ for (var row = 0; row < rowCount; row++)
{
- var match = false;
- foreach (var pair in _pairs)
+ matrix[row] = new double[columnCount];
+
+ var inputKeyValuePair = inputPairs[row];
+ var inputKey = inputKeyValuePair.Key;
+ var inputValues = inputKeyValuePair.Value;
+
+ for (var column = 0; column < columnCount; column++)
{
- var keyMatchResult = pair.Key.IsMatch(inputKeyValuePair.Key).IsPerfect();
- if (keyMatchResult)
- {
- match = pair.Value?.IsMatch(inputKeyValuePair.Value).IsPerfect() ?? false;
- if (match)
- {
- break;
- }
- }
- }
+ var (keyMatcher, valuesMatchers) = KeyValueMatchers[column];
- matches.Add(match);
+ var keyScore = keyMatcher.IsMatch(inputKey).Score;
+ var valueScore = valuesMatchers != null ? MatchScores.ToScore(inputValues, valuesMatchers) : MatchScores.Perfect;
+
+ matrix[row][column] = Math.Min(keyScore, valueScore);
+ }
}
- return matches.ToArray();
+ var rowScores = rowCount == 0 ? [] : matrix.Select(row => row.Length == 0 ? MatchScores.Mismatch : row.Max()).ToList();
+
+ var columnScores = new List();
+ for (var column = 0; column < columnCount; column++)
+ {
+ columnScores.Add(rowCount == 0 ? MatchScores.Mismatch : matrix.Max(row => row[column]));
+ }
+
+ rowScores.AddRange(columnScores);
+ return rowScores;
}
///
diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher.cs
index 71c01f8e..818cd494 100644
--- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher.cs
+++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher.cs
@@ -2,6 +2,7 @@
using Stef.Validation;
using WireMock.Matchers.Helpers;
+using WireMock.Types;
using WireMock.Util;
namespace WireMock.Matchers.Request;
@@ -34,7 +35,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
///
/// The body data function for FormUrlEncoded
///
- public Func?, bool>? MatchOnBodyAsFormUrlEncodedFunc { get; }
+ public Func>?, bool>? MatchOnBodyAsFormUrlEncodedFunc { get; }
///
/// The matchers.
@@ -116,7 +117,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// Initializes a new instance of the class.
///
/// The function.
- public RequestMessageBodyMatcher(Func?, bool> func)
+ public RequestMessageBodyMatcher(Func>?, bool> func)
{
MatchOnBodyAsFormUrlEncodedFunc = Guard.NotNull(func);
}
diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageParamMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageParamMatcher.cs
index 5ca2f6d8..70d9563a 100644
--- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageParamMatcher.cs
+++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageParamMatcher.cs
@@ -115,36 +115,6 @@ public class RequestMessageParamMatcher : IRequestMatcher
}
// Return the score based on Matchers and valuesPresentInRequestMessage
- return CalculateScore(Matchers, valuesPresentInRequestMessage);
- }
-
- private static double CalculateScore(IReadOnlyList matchers, WireMockList valuesPresentInRequestMessage)
- {
- var total = new List();
-
- // If the total patterns in all matchers > values in message, use the matcher as base
- if (matchers.Sum(m => m.GetPatterns().Length) > valuesPresentInRequestMessage.Count)
- {
- foreach (var matcher in matchers)
- {
- double score = 0d;
- foreach (string valuePresentInRequestMessage in valuesPresentInRequestMessage)
- {
- score += matcher.IsMatch(valuePresentInRequestMessage).Score / matcher.GetPatterns().Length;
- }
-
- total.Add(score);
- }
- }
- else
- {
- foreach (string valuePresentInRequestMessage in valuesPresentInRequestMessage)
- {
- var score = matchers.Max(m => m.IsMatch(valuePresentInRequestMessage).Score);
- total.Add(score);
- }
- }
-
- return total.Any() ? MatchScores.ToScore(total, MatchOperator.Average) : 0;
+ return MatchScores.ToScore(valuesPresentInRequestMessage, Matchers.ToArray());
}
}
\ No newline at end of file
diff --git a/src/WireMock.Net.Minimal/RequestBuilders/Request.WithBody.cs b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithBody.cs
index 20658d98..fbde536c 100644
--- a/src/WireMock.Net.Minimal/RequestBuilders/Request.WithBody.cs
+++ b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithBody.cs
@@ -5,6 +5,7 @@
using Stef.Validation;
using WireMock.Matchers;
using WireMock.Matchers.Request;
+using WireMock.Types;
using WireMock.Util;
namespace WireMock.RequestBuilders;
@@ -84,7 +85,7 @@ public partial class Request
}
///
- public IRequestBuilder WithBody(Func?, bool> func)
+ public IRequestBuilder WithBody(Func>?, bool> func)
{
_requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func)));
return this;
diff --git a/src/WireMock.Net.Shared/Extensions/AnyOfExtensions.cs b/src/WireMock.Net.Shared/Extensions/AnyOfExtensions.cs
index aefe5e1e..470b17ab 100644
--- a/src/WireMock.Net.Shared/Extensions/AnyOfExtensions.cs
+++ b/src/WireMock.Net.Shared/Extensions/AnyOfExtensions.cs
@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
-using System.Collections.Generic;
-using System.Linq;
using AnyOfTypes;
using WireMock.Models;
diff --git a/src/WireMock.Net.Shared/Matchers/MatchScores.cs b/src/WireMock.Net.Shared/Matchers/MatchScores.cs
index deeca2d8..83ee708a 100644
--- a/src/WireMock.Net.Shared/Matchers/MatchScores.cs
+++ b/src/WireMock.Net.Shared/Matchers/MatchScores.cs
@@ -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
/// The values.
/// The .
/// average score
- public static double ToScore(IReadOnlyCollection values, MatchOperator matchOperator)
+ public static double ToScore(IEnumerable values, MatchOperator matchOperator)
{
return ToScore(values.Select(ToScore).ToArray(), matchOperator);
}
@@ -68,7 +66,7 @@ public static class MatchScores
/// The values.
///
/// average score
- public static double ToScore(IReadOnlyCollection values, MatchOperator matchOperator)
+ public static double ToScore(IEnumerable values, MatchOperator matchOperator)
{
if (!values.Any())
{
@@ -82,4 +80,29 @@ public static class MatchScores
_ => values.Average()
};
}
+
+ internal static double ToScore(WireMockList 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);
+ }
}
\ No newline at end of file
diff --git a/src/WireMock.Net.Shared/Models/BodyData.cs b/src/WireMock.Net.Shared/Models/BodyData.cs
index d49fcbb4..65b5bc3d 100644
--- a/src/WireMock.Net.Shared/Models/BodyData.cs
+++ b/src/WireMock.Net.Shared/Models/BodyData.cs
@@ -22,7 +22,7 @@ public class BodyData : IBodyData
public string? BodyAsString { get; set; }
///
- public IDictionary? BodyAsFormUrlEncoded { get; set; }
+ public IDictionary>? BodyAsFormUrlEncoded { get; set; }
///
public object? BodyAsJson { get; set; }
diff --git a/src/WireMock.Net.Shared/RequestBuilders/IBodyRequestBuilder.cs b/src/WireMock.Net.Shared/RequestBuilders/IBodyRequestBuilder.cs
index 1e049b91..1c7d697b 100644
--- a/src/WireMock.Net.Shared/RequestBuilders/IBodyRequestBuilder.cs
+++ b/src/WireMock.Net.Shared/RequestBuilders/IBodyRequestBuilder.cs
@@ -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
///
/// The form-urlencoded values.
/// The .
- IRequestBuilder WithBody(Func?, bool> func);
+ IRequestBuilder WithBody(Func>?, bool> func);
}
\ No newline at end of file
diff --git a/src/WireMock.Net.Shared/Util/QueryStringParser.cs b/src/WireMock.Net.Shared/Util/QueryStringParser.cs
index 0a2c4630..17693a5f 100644
--- a/src/WireMock.Net.Shared/Util/QueryStringParser.cs
+++ b/src/WireMock.Net.Shared/Util/QueryStringParser.cs
@@ -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> Empty = new();
- public static bool TryParse(string? queryString, bool caseIgnore, [NotNullWhen(true)] out IDictionary? nameValueCollection)
+ public static bool TryParse(string? queryString, bool caseIgnore, [NotNullWhen(true)] out IDictionary>? nameValueCollection)
{
if (queryString == null)
{
@@ -29,12 +26,22 @@ internal static class QueryStringParser
.Select(parameter => parameter.Split('='))
.Distinct();
- nameValueCollection = caseIgnore ? new Dictionary(StringComparer.OrdinalIgnoreCase) : new Dictionary();
+ nameValueCollection = caseIgnore ? new Dictionary>(StringComparer.OrdinalIgnoreCase) : new Dictionary>();
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);
+ }
}
}
diff --git a/test/WireMock.Net.Tests/Matchers/FormUrlEncodedMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/FormUrlEncodedMatcherTests.cs
index 139e7534..9512b2f1 100644
--- a/test/WireMock.Net.Tests/Matchers/FormUrlEncodedMatcherTests.cs
+++ b/test/WireMock.Net.Tests/Matchers/FormUrlEncodedMatcherTests.cs
@@ -1,9 +1,8 @@
// Copyright © WireMock.Net
using System.Net.Http;
-using AnyOfTypes;
+using WireMock.Extensions;
using WireMock.Matchers;
-using WireMock.Models;
namespace WireMock.Net.Tests.Matchers;
@@ -12,18 +11,26 @@ public class FormUrlEncodedMatcherTest
private readonly CancellationToken _ct = TestContext.Current.CancellationToken;
[Theory]
- [InlineData("*=*")]
- [InlineData("name=John Doe")]
- [InlineData("name=*")]
- [InlineData("*=John Doe")]
- [InlineData("email=johndoe@example.com")]
- [InlineData("email=*")]
- [InlineData("*=johndoe@example.com")]
- [InlineData("name=John Doe", "email=johndoe@example.com")]
- [InlineData("name=John Doe", "email=*")]
- [InlineData("name=*", "email=*")]
- [InlineData("*=John Doe", "*=johndoe@example.com")]
- public async Task FormUrlEncodedMatcher_IsMatch(params string[] patterns)
+ [InlineData(true, "*=*")]
+ [InlineData(true, "name=John Doe")]
+ [InlineData(false, "name=Stef")]
+ [InlineData(false, "name=John Doe&name=Stef")]
+ [InlineData(true, "name=*")]
+ [InlineData(true, "*=John Doe")]
+ [InlineData(false, "*=Stef")]
+ [InlineData(false, "*=John Doe&*=Stef")]
+ [InlineData(true, "email=johndoe@example.com")]
+ [InlineData(true, "email=*")]
+ [InlineData(true, "*=johndoe@example.com")]
+ [InlineData(true, "name=John Doe", "email=johndoe@example.com")]
+ [InlineData(true, "name=John Doe", "email=*")]
+ [InlineData(true, "name=John Doe&name=Stef", "email=*")]
+ [InlineData(true, "name=Stef", "email=*")]
+ [InlineData(true, "name=*", "email=*")]
+ [InlineData(true, "*=John Doe", "*=johndoe@example.com")]
+ [InlineData(true, "*=Stef", "*=johndoe@example.com")]
+ [InlineData(true, "name=John Doe&name=Stef", "*=johndoe@example.com")]
+ public async Task FormUrlEncodedMatcher_IsMatch_Single_Or(bool expected, params string[] patterns)
{
// Arrange
var content = new FormUrlEncodedContent(
@@ -33,28 +40,76 @@ public class FormUrlEncodedMatcherTest
]);
var contentAsString = await content.ReadAsStringAsync(_ct);
- var matcher = new FormUrlEncodedMatcher(patterns.Select(p => new AnyOf(p)).ToArray());
+ var matcher = new FormUrlEncodedMatcher(patterns.ToAnyOfPatterns());
// Act
var score = matcher.IsMatch(contentAsString).IsPerfect();
// Assert
- score.Should().BeTrue();
+ score.Should().Be(expected);
}
[Theory]
+ [InlineData(true, "*=*")]
[InlineData(false, "name=John Doe")]
+ [InlineData(false, "name=Stef")]
+ [InlineData(true, "name=John Doe&name=Stef")]
+ [InlineData(true, "name=*")]
+ [InlineData(false, "*=John Doe")]
+ [InlineData(false, "*=Stef")]
+ [InlineData(true, "*=John Doe&*=Stef")]
+ [InlineData(true, "email=johndoe@example.com")]
+ [InlineData(true, "email=*")]
+ [InlineData(true, "*=johndoe@example.com")]
+ [InlineData(true, "name=John Doe", "email=johndoe@example.com")]
+ [InlineData(true, "name=John Doe", "email=*")]
+ [InlineData(true, "name=John Doe&name=Stef", "email=*")]
+ [InlineData(true, "name=Stef", "email=*")]
+ [InlineData(true, "name=*", "email=*")]
+ [InlineData(true, "*=John Doe", "*=johndoe@example.com")]
+ [InlineData(true, "*=Stef", "*=johndoe@example.com")]
+ [InlineData(true, "name=John Doe&name=Stef", "*=johndoe@example.com")]
+ public async Task FormUrlEncodedMatcher_IsMatch_Multiple_Or(bool expected, params string[] patterns)
+ {
+ // Arrange
+ var content = new FormUrlEncodedContent(
+ [
+ new KeyValuePair("name", "John Doe"),
+ new KeyValuePair("name", "Stef"),
+ new KeyValuePair("email", "johndoe@example.com")
+ ]);
+ var contentAsString = await content.ReadAsStringAsync(_ct);
+
+ var matcher = new FormUrlEncodedMatcher(patterns.ToAnyOfPatterns());
+
+ // Act
+ var score = matcher.IsMatch(contentAsString).IsPerfect();
+
+ // Assert
+ score.Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData(true, "*=*")]
+ [InlineData(false, "name=John Doe")]
+ [InlineData(false, "name=Stef")]
+ [InlineData(false, "name=John Doe&name=Stef")]
[InlineData(false, "name=*")]
[InlineData(false, "*=John Doe")]
+ [InlineData(false, "*=Stef")]
+ [InlineData(false, "*=John Doe&*=Stef")]
[InlineData(false, "email=johndoe@example.com")]
[InlineData(false, "email=*")]
[InlineData(false, "*=johndoe@example.com")]
[InlineData(true, "name=John Doe", "email=johndoe@example.com")]
[InlineData(true, "name=John Doe", "email=*")]
+ [InlineData(false, "name=John Doe&name=Stef", "email=*")]
+ [InlineData(false, "name=Stef", "email=*")]
[InlineData(true, "name=*", "email=*")]
[InlineData(true, "*=John Doe", "*=johndoe@example.com")]
- [InlineData(true, "*=*")]
- public async Task FormUrlEncodedMatcher_IsMatch_And(bool expected, params string[] patterns)
+ [InlineData(false, "*=Stef", "*=johndoe@example.com")]
+ [InlineData(false, "name=John Doe&name=Stef", "*=johndoe@example.com")]
+ public async Task FormUrlEncodedMatcher_IsMatch_Single_And(bool expected, params string[] patterns)
{
// Arrange
var content = new FormUrlEncodedContent(
@@ -64,7 +119,47 @@ public class FormUrlEncodedMatcherTest
]);
var contentAsString = await content.ReadAsStringAsync(_ct);
- var matcher = new FormUrlEncodedMatcher(patterns.Select(p => new AnyOf(p)).ToArray(), true, MatchOperator.And);
+ var matcher = new FormUrlEncodedMatcher(patterns.ToAnyOfPatterns(), true, MatchOperator.And);
+
+ // Act
+ var score = matcher.IsMatch(contentAsString).IsPerfect();
+
+ // Assert
+ score.Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData(true, "*=*")]
+ [InlineData(false, "name=John Doe")]
+ [InlineData(false, "name=Stef")]
+ [InlineData(false, "name=John Doe&name=Stef")]
+ [InlineData(false, "name=*")]
+ [InlineData(false, "*=John Doe")]
+ [InlineData(false, "*=Stef")]
+ [InlineData(false, "*=John Doe&*=Stef")]
+ [InlineData(false, "email=johndoe@example.com")]
+ [InlineData(false, "email=*")]
+ [InlineData(false, "*=johndoe@example.com")]
+ [InlineData(false, "name=John Doe", "email=johndoe@example.com")]
+ [InlineData(false, "name=John Doe", "email=*")]
+ [InlineData(true, "name=John Doe&name=Stef", "email=*")]
+ [InlineData(false, "name=Stef", "email=*")]
+ [InlineData(true, "name=*", "email=*")]
+ [InlineData(false, "*=John Doe", "*=johndoe@example.com")]
+ [InlineData(false, "*=Stef", "*=johndoe@example.com")]
+ [InlineData(true, "name=John Doe&name=Stef", "*=johndoe@example.com")]
+ public async Task FormUrlEncodedMatcher_IsMatch_Multiple_And(bool expected, params string[] patterns)
+ {
+ // Arrange
+ var content = new FormUrlEncodedContent(
+ [
+ new KeyValuePair("name", "John Doe"),
+ new KeyValuePair("name", "Stef"),
+ new KeyValuePair("email", "johndoe@example.com")
+ ]);
+ var contentAsString = await content.ReadAsStringAsync(_ct);
+
+ var matcher = new FormUrlEncodedMatcher(patterns.ToAnyOfPatterns(), true, MatchOperator.And);
// Act
var score = matcher.IsMatch(contentAsString).IsPerfect();
@@ -74,12 +169,13 @@ public class FormUrlEncodedMatcherTest
}
[Fact]
- public async Task FormUrlEncodedMatcher_IsMatch_And_MatchAllProperties()
+ public async Task FormUrlEncodedMatcher_IsMatch_And_MatchAllProperties_Test_1()
{
// Arrange
var content = new FormUrlEncodedContent(
[
new KeyValuePair("name", "John Doe"),
+ new KeyValuePair("name", "Stef"),
new KeyValuePair("email", "johndoe@example.com")
]);
var contentAsString = await content.ReadAsStringAsync(_ct);
@@ -93,4 +189,25 @@ public class FormUrlEncodedMatcherTest
// Assert
score.Should().BeFalse();
}
+
+ [Fact]
+ public async Task FormUrlEncodedMatcher_IsMatch_And_MatchAllProperties_Test_2()
+ {
+ // Arrange
+ var content = new FormUrlEncodedContent(
+ [
+ new KeyValuePair("name", "John Doe"),
+ new KeyValuePair("name", "Stef"),
+ new KeyValuePair("email", "johndoe@example.com")
+ ]);
+ var contentAsString = await content.ReadAsStringAsync(_ct);
+
+ var matcher = new FormUrlEncodedMatcher(["name=*", "email=*", "email=x"], matchOperator: MatchOperator.And);
+
+ // Act
+ var score = matcher.IsMatch(contentAsString).IsPerfect();
+
+ // Assert
+ score.Should().BeFalse();
+ }
}
\ No newline at end of file
diff --git a/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithBodyTests.cs b/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithBodyTests.cs
index 31767152..8c40eb69 100644
--- a/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithBodyTests.cs
+++ b/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithBodyTests.cs
@@ -142,12 +142,12 @@ public class RequestBuilderWithBodyTests
public void Request_WithBody_FuncFormUrlEncoded()
{
// Assign
- var requestBuilder = Request.Create().UsingAnyMethod().WithBody((IDictionary? values) => values != null);
+ var requestBuilder = Request.Create().UsingAnyMethod().WithBody((IDictionary>? values) => values != null);
// Act
var body = new BodyData
{
- BodyAsFormUrlEncoded = new Dictionary(),
+ BodyAsFormUrlEncoded = new Dictionary>(),
DetectedBodyTypeFromContentType = BodyType.FormUrlEncoded,
DetectedBodyType = BodyType.FormUrlEncoded
};
diff --git a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageParamMatcherTests.cs b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageParamMatcherTests.cs
index c5db79a1..4f57bb84 100644
--- a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageParamMatcherTests.cs
+++ b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageParamMatcherTests.cs
@@ -15,7 +15,7 @@ public class RequestMessageParamMatcherTests
{
// Assign
var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=test1"), "GET", "127.0.0.1");
- var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "KeY", true, new[] { "test1" });
+ var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "KeY", true, ["test1"]);
// Act
var result = new RequestMatchResult();
@@ -30,7 +30,7 @@ public class RequestMessageParamMatcherTests
{
// Assign
var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=test1"), "GET", "127.0.0.1");
- var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, new[] { "test1", "test2" });
+ var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, ["test1", "test2"]);
// Act
var result = new RequestMatchResult();
@@ -60,7 +60,7 @@ public class RequestMessageParamMatcherTests
{
// Assign
var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=test1,test2,test3"), "GET", "127.0.0.1");
- var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, new IStringMatcher[] { new ExactMatcher("test1", "test2") });
+ var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, [new ExactMatcher("test1", "test2")]);
// Act
var result = new RequestMatchResult();
@@ -71,18 +71,18 @@ public class RequestMessageParamMatcherTests
}
[Fact]
- public void RequestMessageParamMatcher_GetMatchingScore_KeyWith2ValuesPresentInUrl_And_With1ExactStringWith3Patterns_Returns0_66()
+ public void RequestMessageParamMatcher_GetMatchingScore_KeyWith2ValuesPresentInUrl_And_With1ExactStringWith3Patterns_Returns1_0()
{
// Assign
var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=test1,test2"), "GET", "127.0.0.1");
- var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, new IStringMatcher[] { new ExactMatcher("test1", "test2", "test3") });
+ var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, [new ExactMatcher("test1", "test2", "test3")]);
// Act
var result = new RequestMatchResult();
double score = matcher.GetMatchingScore(requestMessage, result);
// Assert
- score.Should().BeApproximately(0.66d, 0.1d);
+ score.Should().Be(1.0d);
}
[Fact]
@@ -90,7 +90,7 @@ public class RequestMessageParamMatcherTests
{
// Assign
var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=test1,test2"), "GET", "127.0.0.1");
- var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, new[] { "test1", "test2" });
+ var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, ["test1", "test2"]);
// Act
var result = new RequestMatchResult();
@@ -105,7 +105,7 @@ public class RequestMessageParamMatcherTests
{
// Assign
var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=test1,test2"), "GET", "127.0.0.1");
- var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, new IStringMatcher[] { new ExactMatcher("test1"), new ExactMatcher("test2") });
+ var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, [new ExactMatcher("test1"), new ExactMatcher("test2")]);
// Act
var result = new RequestMatchResult();
@@ -120,7 +120,7 @@ public class RequestMessageParamMatcherTests
{
// Assign
var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=test0,test2"), "GET", "127.0.0.1");
- var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, new[] { "test1", "test2" });
+ var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, ["test1", "test2"]);
// Act
var result = new RequestMatchResult();
@@ -135,7 +135,7 @@ public class RequestMessageParamMatcherTests
{
// Assign
var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key"), "GET", "127.0.0.1");
- var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, new[] { "test1", "test2" });
+ var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, ["test1", "test2"]);
// Act
var result = new RequestMatchResult();
@@ -215,7 +215,7 @@ public class RequestMessageParamMatcherTests
{
// Assign: the param value in the URL matches the reject pattern -> the mapping must be rejected (score 0.0).
var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=abc"), "GET", "127.0.0.1");
- var matcher = new RequestMessageParamMatcher(MatchBehaviour.RejectOnMatch, "key", false, new[] { "abc" });
+ var matcher = new RequestMessageParamMatcher(MatchBehaviour.RejectOnMatch, "key", false, ["abc"]);
// Act
var result = new RequestMatchResult();
@@ -230,7 +230,7 @@ public class RequestMessageParamMatcherTests
{
// Assign: the param value in the URL does NOT match the reject pattern -> the mapping is accepted (score 1.0).
var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=xyz"), "GET", "127.0.0.1");
- var matcher = new RequestMessageParamMatcher(MatchBehaviour.RejectOnMatch, "key", false, new[] { "abc" });
+ var matcher = new RequestMessageParamMatcher(MatchBehaviour.RejectOnMatch, "key", false, ["abc"]);
// Act
var result = new RequestMatchResult();
@@ -245,7 +245,7 @@ public class RequestMessageParamMatcherTests
{
// Assign: ignoreCase must still be honored on the inner matcher after the fix.
var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=ABC"), "GET", "127.0.0.1");
- var matcher = new RequestMessageParamMatcher(MatchBehaviour.RejectOnMatch, "key", true, new[] { "abc" });
+ var matcher = new RequestMessageParamMatcher(MatchBehaviour.RejectOnMatch, "key", true, ["abc"]);
// Act
var result = new RequestMatchResult();
@@ -260,7 +260,7 @@ public class RequestMessageParamMatcherTests
{
// Assign
var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=abc"), "GET", "127.0.0.1");
- var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, new[] { "abc" });
+ var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, ["abc"]);
// Act
var result = new RequestMatchResult();
@@ -275,7 +275,7 @@ public class RequestMessageParamMatcherTests
{
// Assign
var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=xyz"), "GET", "127.0.0.1");
- var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, new[] { "abc" });
+ var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, ["abc"]);
// Act
var result = new RequestMatchResult();
diff --git a/test/WireMock.Net.Tests/Util/QueryStringParserTests.cs b/test/WireMock.Net.Tests/Util/QueryStringParserTests.cs
index 9e23ec0e..75cba9eb 100644
--- a/test/WireMock.Net.Tests/Util/QueryStringParserTests.cs
+++ b/test/WireMock.Net.Tests/Util/QueryStringParserTests.cs
@@ -7,27 +7,27 @@ namespace WireMock.Net.Tests.Util;
public class QueryStringParserTests
{
- public static IEnumerable