This commit is contained in:
Stef Heyenrath
2026-02-11 23:36:44 +01:00
parent deb9777f88
commit d6c5ede20b
6 changed files with 22863 additions and 21669 deletions

View File

@@ -0,0 +1,106 @@
// Copyright © WireMock.Net
using Stef.Validation;
using WireMock.Extensions;
namespace WireMock.Matchers;
/// <summary>
/// FuncMatcher - matches using a custom function
/// </summary>
/// <inheritdoc cref="IFuncMatcher"/>
public class FuncMatcher : IFuncMatcher
{
private readonly Func<string?, bool>? _stringFunc;
private readonly Func<byte[]?, bool>? _bytesFunc;
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <summary>
/// Initializes a new instance of the <see cref="FuncMatcher"/> class for string matching.
/// </summary>
/// <param name="func">The function to check if a string is a match.</param>
public FuncMatcher(Func<string?, bool> func) : this(MatchBehaviour.AcceptOnMatch, func)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncMatcher"/> class for string matching.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="func">The function to check if a string is a match.</param>
public FuncMatcher(MatchBehaviour matchBehaviour, Func<string?, bool> func)
{
_stringFunc = Guard.NotNull(func);
MatchBehaviour = matchBehaviour;
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncMatcher"/> class for byte array matching.
/// </summary>
/// <param name="func">The function to check if a byte[] is a match.</param>
public FuncMatcher(Func<byte[]?, bool> func) : this(MatchBehaviour.AcceptOnMatch, func)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncMatcher"/> class for byte array matching.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="func">The function to check if a byte[] is a match.</param>
public FuncMatcher(MatchBehaviour matchBehaviour, Func<byte[]?, bool> func)
{
_bytesFunc = Guard.NotNull(func);
MatchBehaviour = matchBehaviour;
}
/// <inheritdoc />
public MatchResult IsMatch(object? value)
{
if (value is string stringValue && _stringFunc != null)
{
try
{
return CreateMatchResult(_stringFunc(stringValue));
}
catch (Exception ex)
{
return MatchResult.From(Name, ex);
}
}
if (value is byte[] bytesValue && _bytesFunc != null)
{
try
{
return CreateMatchResult(_bytesFunc(bytesValue));
}
catch (Exception ex)
{
return MatchResult.From(Name, ex);
}
}
return MatchResult.From(Name, MatchScores.Mismatch);
}
private MatchResult CreateMatchResult(bool isMatch)
{
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(isMatch)));
}
/// <inheritdoc />
public string Name => nameof(FuncMatcher);
/// <inheritdoc />
public string GetCSharpCodeArguments()
{
var funcType = _stringFunc != null ? "Func<string?, bool>" : "Func<byte[]?, bool>";
return $"new {Name}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"/* {funcType} function */" +
$")";
}
}

View File

@@ -100,21 +100,17 @@ internal class WebSocketBuilder : IWebSocketBuilder
});
}
public IWebSocketMessageConditionBuilder WhenMessage(string condition)
public IWebSocketMessageConditionBuilder WhenMessage(string wildcardPattern)
{
Guard.NotNull(condition);
// Use RegexMatcher for substring matching - escape special chars and wrap with wildcards
// Convert the string to a wildcard pattern that matches if it contains the condition
var pattern = $"*{condition}*";
var matcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, pattern);
Guard.NotNull(wildcardPattern);
var matcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, wildcardPattern);
return new WebSocketMessageConditionBuilder(this, matcher);
}
public IWebSocketMessageConditionBuilder WhenMessage(byte[] condition)
public IWebSocketMessageConditionBuilder WhenMessage(byte[] exactPattern)
{
Guard.NotNull(condition);
// Use ExactObjectMatcher for byte matching
var matcher = new ExactObjectMatcher(MatchBehaviour.AcceptOnMatch, condition);
Guard.NotNull(exactPattern);
var matcher = new ExactObjectMatcher(MatchBehaviour.AcceptOnMatch, exactPattern);
return new WebSocketMessageConditionBuilder(this, matcher);
}
@@ -241,12 +237,22 @@ internal class WebSocketBuilder : IWebSocketBuilder
private static async Task<bool> MatchMessageAsync(WebSocketMessage message, IMatcher matcher)
{
if (message.MessageType == WebSocketMessageType.Text && matcher is IStringMatcher stringMatcher)
if (message.MessageType == WebSocketMessageType.Text)
{
var result = stringMatcher.IsMatch(message.Text);
return result.IsPerfect();
if (matcher is IStringMatcher stringMatcher)
{
var result = stringMatcher.IsMatch(message.Text);
return result.IsPerfect();
}
if (matcher is IFuncMatcher funcMatcher)
{
var result = funcMatcher.IsMatch(message.Text);
return result.IsPerfect();
}
}
if (message.MessageType == WebSocketMessageType.Binary && matcher is IBytesMatcher bytesMatcher && message.Bytes != null)
{
var result = await bytesMatcher.IsMatchAsync(message.Bytes);

View File

@@ -0,0 +1,17 @@
// Copyright © WireMock.Net
namespace WireMock.Matchers;
/// <summary>
/// IFuncMatcher
/// </summary>
/// <inheritdoc cref="IMatcher"/>
public interface IFuncMatcher : IMatcher
{
/// <summary>
/// Determines whether the specified function is match.
/// </summary>
/// <param name="value">The value to check for a match.</param>
/// <returns>MatchResult</returns>
MatchResult IsMatch(object? value);
}

View File

@@ -0,0 +1,435 @@
// Copyright © WireMock.Net
using System;
using FluentAssertions;
using WireMock.Matchers;
using Xunit;
namespace WireMock.Net.Tests.Matchers;
public class FuncMatcherTests
{
[Fact]
public void FuncMatcher_For_String_IsMatch_Should_Return_Perfect_When_Function_Returns_True()
{
// Arrange
Func<string?, bool> func = s => s == "test";
var matcher = new FuncMatcher(func);
// Act
var result = matcher.IsMatch("test");
// Assert
result.IsPerfect().Should().BeTrue();
}
[Fact]
public void FuncMatcher_For_String_IsMatch_Should_Return_Mismatch_When_Function_Returns_False()
{
// Arrange
Func<string?, bool> func = s => s == "test";
var matcher = new FuncMatcher(func);
// Act
var result = matcher.IsMatch("other");
// Assert
result.IsPerfect().Should().BeFalse();
}
[Fact]
public void FuncMatcher_For_String_IsMatch_Should_Handle_Null_String()
{
// Arrange
Func<string?, bool> func = s => s == null;
var matcher = new FuncMatcher(func);
// Act - passing null as object, not as string
var result = matcher.IsMatch((object?)null);
// Assert - null object doesn't match, returns mismatch
result.IsPerfect().Should().BeFalse();
}
[Fact]
public void FuncMatcher_For_String_IsMatch_With_ByteArray_Input_Should_Return_Mismatch()
{
// Arrange
Func<string?, bool> func = s => s == "test";
var matcher = new FuncMatcher(func);
// Act
var result = matcher.IsMatch(new byte[] { 1, 2, 3 });
// Assert
result.IsPerfect().Should().BeFalse();
}
[Fact]
public void FuncMatcher_For_String_IsMatch_With_Null_Object_Should_Return_Mismatch()
{
// Arrange
Func<string?, bool> func = s => s == "test";
var matcher = new FuncMatcher(func);
// Act
var result = matcher.IsMatch((object?)null);
// Assert
result.IsPerfect().Should().BeFalse();
}
[Fact]
public void FuncMatcher_For_String_IsMatch_Should_Handle_Exception()
{
// Arrange
Func<string?, bool> func = s => throw new InvalidOperationException("Test exception");
var matcher = new FuncMatcher(func);
// Act
var result = matcher.IsMatch("test");
// Assert
result.IsPerfect().Should().BeFalse();
result.Exception.Should().NotBeNull();
result.Exception.Should().BeOfType<InvalidOperationException>();
result.Exception!.Message.Should().Be("Test exception");
}
[Fact]
public void FuncMatcher_For_Bytes_IsMatch_Should_Return_Perfect_When_Function_Returns_True()
{
// Arrange
Func<byte[]?, bool> func = b => b != null && b.Length == 3;
var matcher = new FuncMatcher(func);
// Act
var result = matcher.IsMatch(new byte[] { 1, 2, 3 });
// Assert
result.IsPerfect().Should().BeTrue();
}
[Fact]
public void FuncMatcher_For_Bytes_IsMatch_Should_Return_Mismatch_When_Function_Returns_False()
{
// Arrange
Func<byte[]?, bool> func = b => b != null && b.Length == 3;
var matcher = new FuncMatcher(func);
// Act
var result = matcher.IsMatch(new byte[] { 1, 2, 3, 4, 5 });
// Assert
result.IsPerfect().Should().BeFalse();
}
[Fact]
public void FuncMatcher_For_Bytes_IsMatch_Should_Handle_Null_ByteArray()
{
// Arrange
Func<byte[]?, bool> func = b => b == null;
var matcher = new FuncMatcher(func);
// Act - passing null as object, not as byte[]
var result = matcher.IsMatch((object?)null);
// Assert - null object doesn't match, returns mismatch
result.IsPerfect().Should().BeFalse();
}
[Fact]
public void FuncMatcher_For_Bytes_IsMatch_With_String_Input_Should_Return_Mismatch()
{
// Arrange
Func<byte[]?, bool> func = b => b != null && b.Length > 0;
var matcher = new FuncMatcher(func);
// Act
var result = matcher.IsMatch("test");
// Assert
result.IsPerfect().Should().BeFalse();
}
[Fact]
public void FuncMatcher_For_Bytes_IsMatch_Should_Handle_Exception()
{
// Arrange
Func<byte[]?, bool> func = b => throw new InvalidOperationException("Bytes exception");
var matcher = new FuncMatcher(func);
// Act
var result = matcher.IsMatch(new byte[] { 1, 2, 3 });
// Assert
result.IsPerfect().Should().BeFalse();
result.Exception.Should().NotBeNull();
result.Exception.Should().BeOfType<InvalidOperationException>();
result.Exception!.Message.Should().Be("Bytes exception");
}
[Fact]
public void FuncMatcher_For_String_With_Contains_Logic_Should_Work()
{
// Arrange
Func<string?, bool> func = s => s?.Contains("foo") == true;
var matcher = new FuncMatcher(func);
// Act
var result1 = matcher.IsMatch("foo");
var result2 = matcher.IsMatch("foobar");
var result3 = matcher.IsMatch("bar");
// Assert
result1.IsPerfect().Should().BeTrue();
result2.IsPerfect().Should().BeTrue();
result3.IsPerfect().Should().BeFalse();
}
[Fact]
public void FuncMatcher_For_Bytes_With_Length_Logic_Should_Work()
{
// Arrange
Func<byte[]?, bool> func = b => b != null && b.Length > 2;
var matcher = new FuncMatcher(func);
// Act
var result1 = matcher.IsMatch(new byte[] { 1 });
var result2 = matcher.IsMatch(new byte[] { 1, 2 });
var result3 = matcher.IsMatch(new byte[] { 1, 2, 3 });
// Assert
result1.IsPerfect().Should().BeFalse();
result2.IsPerfect().Should().BeFalse();
result3.IsPerfect().Should().BeTrue();
}
[Fact]
public void FuncMatcher_Name_Should_Return_FuncMatcher()
{
// Arrange
Func<string?, bool> func = s => s == "test";
var matcher = new FuncMatcher(func);
// Act & Assert
matcher.Name.Should().Be("FuncMatcher");
}
[Fact]
public void FuncMatcher_MatchBehaviour_Should_Return_AcceptOnMatch_By_Default()
{
// Arrange
Func<string?, bool> func = s => s == "test";
var matcher = new FuncMatcher(func);
// Act & Assert
matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch);
}
[Fact]
public void FuncMatcher_MatchBehaviour_Should_Return_Custom_Value_For_String()
{
// Arrange
Func<string?, bool> func = s => s == "test";
var matcher = new FuncMatcher(MatchBehaviour.RejectOnMatch, func);
// Act & Assert
matcher.MatchBehaviour.Should().Be(MatchBehaviour.RejectOnMatch);
}
[Fact]
public void FuncMatcher_MatchBehaviour_Should_Return_Custom_Value_For_Bytes()
{
// Arrange
Func<byte[]?, bool> func = b => b != null;
var matcher = new FuncMatcher(MatchBehaviour.RejectOnMatch, func);
// Act & Assert
matcher.MatchBehaviour.Should().Be(MatchBehaviour.RejectOnMatch);
}
[Fact]
public void FuncMatcher_GetCSharpCodeArguments_For_String_Should_Return_Correct_Code()
{
// Arrange
Func<string?, bool> func = s => s == "test";
var matcher = new FuncMatcher(func);
// Act
var code = matcher.GetCSharpCodeArguments();
// Assert
code.Should().Contain("FuncMatcher");
code.Should().Contain("Func<string?, bool>");
code.Should().Contain("AcceptOnMatch");
}
[Fact]
public void FuncMatcher_GetCSharpCodeArguments_For_Bytes_Should_Return_Correct_Code()
{
// Arrange
Func<byte[]?, bool> func = b => b != null;
var matcher = new FuncMatcher(func);
// Act
var code = matcher.GetCSharpCodeArguments();
// Assert
code.Should().Contain("FuncMatcher");
code.Should().Contain("Func<byte[]?, bool>");
code.Should().Contain("AcceptOnMatch");
}
[Fact]
public void FuncMatcher_With_RejectOnMatch_For_String_Should_Invert_Result_When_True()
{
// Arrange
Func<string?, bool> func = s => s == "test";
var matcher = new FuncMatcher(MatchBehaviour.RejectOnMatch, func);
// Act
var result = matcher.IsMatch("test");
// Assert
result.IsPerfect().Should().BeFalse();
}
[Fact]
public void FuncMatcher_With_RejectOnMatch_For_String_Should_Invert_Result_When_False()
{
// Arrange
Func<string?, bool> func = s => s == "test";
var matcher = new FuncMatcher(MatchBehaviour.RejectOnMatch, func);
// Act
var result = matcher.IsMatch("other");
// Assert
result.IsPerfect().Should().BeTrue();
}
[Fact]
public void FuncMatcher_With_RejectOnMatch_For_Bytes_Should_Invert_Result_When_True()
{
// Arrange
Func<byte[]?, bool> func = b => b != null && b.Length > 0;
var matcher = new FuncMatcher(MatchBehaviour.RejectOnMatch, func);
// Act
var result = matcher.IsMatch(new byte[] { 1, 2, 3 });
// Assert
result.IsPerfect().Should().BeFalse();
}
[Fact]
public void FuncMatcher_With_RejectOnMatch_For_Bytes_Should_Invert_Result_When_False()
{
// Arrange
Func<byte[]?, bool> func = b => b != null && b.Length > 0;
var matcher = new FuncMatcher(MatchBehaviour.RejectOnMatch, func);
// Act
var result = matcher.IsMatch(new byte[0]);
// Assert
result.IsPerfect().Should().BeTrue();
}
[Fact]
public void FuncMatcher_For_String_IsMatch_With_Integer_Input_Should_Return_Mismatch()
{
// Arrange
Func<string?, bool> func = s => s == "test";
var matcher = new FuncMatcher(func);
// Act
var result = matcher.IsMatch(42);
// Assert
result.IsPerfect().Should().BeFalse();
}
[Fact]
public void FuncMatcher_For_Bytes_IsMatch_With_Integer_Input_Should_Return_Mismatch()
{
// Arrange
Func<byte[]?, bool> func = b => b != null;
var matcher = new FuncMatcher(func);
// Act
var result = matcher.IsMatch(42);
// Assert
result.IsPerfect().Should().BeFalse();
}
[Fact]
public void FuncMatcher_For_String_With_Empty_String_Should_Work()
{
// Arrange
Func<string?, bool> func = s => string.IsNullOrEmpty(s);
var matcher = new FuncMatcher(func);
// Act
var result1 = matcher.IsMatch("");
var result2 = matcher.IsMatch("test");
// Assert
result1.IsPerfect().Should().BeTrue();
result2.IsPerfect().Should().BeFalse();
}
[Fact]
public void FuncMatcher_For_Bytes_With_Empty_Array_Should_Work()
{
// Arrange
Func<byte[]?, bool> func = b => b != null && b.Length == 0;
var matcher = new FuncMatcher(func);
// Act
var result1 = matcher.IsMatch(new byte[0]);
var result2 = matcher.IsMatch(new byte[] { 1 });
// Assert
result1.IsPerfect().Should().BeTrue();
result2.IsPerfect().Should().BeFalse();
}
[Fact]
public void FuncMatcher_For_String_With_Complex_Logic_Should_Work()
{
// Arrange
Func<string?, bool> func = s => s != null && s.Length > 3 && s.StartsWith("t") && s.EndsWith("t");
var matcher = new FuncMatcher(func);
// Act
var result1 = matcher.IsMatch("test");
var result2 = matcher.IsMatch("tart");
var result3 = matcher.IsMatch("tes");
var result4 = matcher.IsMatch("best");
// Assert
result1.IsPerfect().Should().BeTrue();
result2.IsPerfect().Should().BeTrue();
result3.IsPerfect().Should().BeFalse();
result4.IsPerfect().Should().BeFalse();
}
[Fact]
public void FuncMatcher_For_Bytes_With_Specific_Byte_Check_Should_Work()
{
// Arrange
Func<byte[]?, bool> func = b => b != null && b.Length > 0 && b[0] == 0xFF;
var matcher = new FuncMatcher(func);
// Act
var result1 = matcher.IsMatch(new byte[] { 0xFF, 0x00 });
var result2 = matcher.IsMatch(new byte[] { 0x00, 0xFF });
// Assert
result1.IsPerfect().Should().BeTrue();
result2.IsPerfect().Should().BeFalse();
}
}

View File

@@ -6,6 +6,7 @@ using System.Net.WebSockets;
using System.Text;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using WireMock.Matchers;
using WireMock.Net.Xunit;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
@@ -573,9 +574,11 @@ public class WebSocketIntegrationTests(ITestOutputHelper output)
.RespondWith(Response.Create()
.WithWebSocket(ws => ws
.WithCloseTimeout(TimeSpan.FromSeconds(3))
.WhenMessage("/help").SendMessage(m => m.WithText("Available commands: /help, /time, /echo <text>"))
.WhenMessage("/help").SendMessage(m => m.WithText("Available commands"))
.WhenMessage("/time").SendMessage(m => m.WithText($"Server time: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC"))
.WhenMessage("/echo ").SendMessage(m => m.WithText("echo response"))
.WhenMessage("/echo *").SendMessage(m => m.WithText("echo response"))
.WhenMessage(new ExactMatcher("/exact")).SendMessage(m => m.WithText("is exact"))
.WhenMessage(new FuncMatcher(s => s == "/func")).SendMessage(m => m.WithText("is func"))
)
);
@@ -587,7 +590,9 @@ public class WebSocketIntegrationTests(ITestOutputHelper output)
{
("/help", "Available commands"),
("/time", "Server time"),
("/echo test", "echo response")
("/echo test", "echo response"),
("/exact", "is exact"),
("/func", "is func")
};
// Act & Assert

File diff suppressed because it is too large Load Diff