FluentAssertions - WithBody and WithBodyAsJson and WithBodyAsBytes (#1014)

* WithBody

* .

* fix

* .

* .
This commit is contained in:
Stef Heyenrath
2023-11-04 16:17:23 +01:00
committed by GitHub
parent 2f29d80336
commit 7160dbdd19
11 changed files with 450 additions and 100 deletions

View File

@@ -94,23 +94,23 @@ public interface IRequestMessage
IBodyData? BodyData { get; }
/// <summary>
/// The original body as string. Convenience getter for Handlebars.
/// The original body as string. Convenience getter for Handlebars and WireMockAssertions.
/// </summary>
string? Body { get; }
/// <summary>
/// The body (as JSON object). Convenience getter for Handlebars.
/// The body (as JSON object). Convenience getter for Handlebars and WireMockAssertions.
/// </summary>
object? BodyAsJson { get; }
/// <summary>
/// The body (as bytearray). Convenience getter for Handlebars.
/// The body (as bytearray). Convenience getter for Handlebars and WireMockAssertions.
/// </summary>
byte[]? BodyAsBytes { get; }
#if MIMEKIT
/// <summary>
/// The original body as MimeMessage. Convenience getter for Handlebars.
/// The original body as MimeMessage. Convenience getter for Handlebars and WireMockAssertions.
/// </summary>
object? BodyAsMimeMessage { get; }
#endif

View File

@@ -1,3 +1,4 @@
using Stef.Validation;
using WireMock.Server;
// ReSharper disable once CheckNamespace
@@ -10,7 +11,7 @@ namespace WireMock.FluentAssertions
public WireMockANumberOfCallsAssertions(IWireMockServer server, int callsCount)
{
_server = server;
_server = Guard.NotNull(server);
_callsCount = callsCount;
}

View File

@@ -0,0 +1,79 @@
#pragma warning disable CS1591
using System;
using WireMock.Constants;
// ReSharper disable once CheckNamespace
namespace WireMock.FluentAssertions;
public partial class WireMockAssertions
{
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingConnect(string because = "", params object[] becauseArgs)
=> UsingMethod(HttpRequestMethod.CONNECT, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingDelete(string because = "", params object[] becauseArgs)
=> UsingMethod(HttpRequestMethod.DELETE, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingGet(string because = "", params object[] becauseArgs)
=> UsingMethod(HttpRequestMethod.GET, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingHead(string because = "", params object[] becauseArgs)
=> UsingMethod(HttpRequestMethod.HEAD, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingOptions(string because = "", params object[] becauseArgs)
=> UsingMethod(HttpRequestMethod.OPTIONS, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingPost(string because = "", params object[] becauseArgs)
=> UsingMethod(HttpRequestMethod.POST, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingPatch(string because = "", params object[] becauseArgs)
=> UsingMethod(HttpRequestMethod.PATCH, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingPut(string because = "", params object[] becauseArgs)
=> UsingMethod(HttpRequestMethod.PUT, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingTrace(string because = "", params object[] becauseArgs)
=> UsingMethod(HttpRequestMethod.TRACE, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingAnyMethod(string because = "", params object[] becauseArgs)
=> UsingMethod(Any, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingMethod(string method, string because = "", params object[] becauseArgs)
{
var any = method == Any;
Func<IRequestMessage, bool> predicate = request => (any && !string.IsNullOrEmpty(request.Method)) ||
string.Equals(request.Method, method, StringComparison.OrdinalIgnoreCase);
var (filter, condition) = BuildFilterAndCondition(predicate);
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => _requestMessages)
.ForCondition(requests => _callsCount == 0 || requests.Any())
.FailWith(
"Expected {context:wiremockserver} to have been called using method {0}{reason}, but no calls were made.",
method
)
.Then
.ForCondition(condition)
.FailWith(
"Expected {context:wiremockserver} to have been called using method {0}{reason}, but didn't find it among the methods {1}.",
_ => method,
requests => requests.Select(request => request.Method)
);
_requestMessages = filter(_requestMessages).ToList();
return new AndConstraint<WireMockAssertions>(this);
}
}

View File

@@ -0,0 +1,151 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using WireMock.Matchers;
// ReSharper disable once CheckNamespace
namespace WireMock.FluentAssertions;
public partial class WireMockAssertions
{
private const string MessageFormatNoCalls = "Expected {context:wiremockserver} to have been called using body {0}{reason}, but no calls were made.";
private const string MessageFormat = "Expected {context:wiremockserver} to have been called using body {0}{reason}, but didn't find it among the body {1}.";
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithBody(string body, string because = "", params object[] becauseArgs)
{
return WithBody(new WildcardMatcher(body), because, becauseArgs);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithBodyAsJson(object body, string because = "", params object[] becauseArgs)
{
return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithBodyAsJson(string body, string because = "", params object[] becauseArgs)
{
return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithBodyAsBytes(byte[] body, string because = "", params object[] becauseArgs)
{
return WithBodyAsBytes(new ExactObjectMatcher(body), because, becauseArgs);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithBody(IStringMatcher matcher, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(r => r.Body, matcher);
return ExecuteAssertionWithBodyStringMatcher(matcher, because, becauseArgs, condition, filter, r => r.Body);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithBodyAsJson(IValueMatcher matcher, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(r => r.BodyAsJson, matcher);
return ExecuteAssertionWithBodyAsJsonValueMatcher(matcher, because, becauseArgs, condition, filter, r => r.BodyAsJson);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithBodyAsBytes(ExactObjectMatcher matcher, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(r => r.BodyAsBytes, matcher);
return ExecuteAssertionWithBodyAsBytesExactObjectMatcher(matcher, because, becauseArgs, condition, filter, r => r.BodyAsBytes);
}
private AndConstraint<WireMockAssertions> ExecuteAssertionWithBodyStringMatcher(
IStringMatcher matcher,
string because,
object[] becauseArgs,
Func<IReadOnlyList<IRequestMessage>, bool> condition,
Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> filter,
Func<IRequestMessage, object?> expression
)
{
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => _requestMessages)
.ForCondition(requests => _callsCount == 0 || requests.Any())
.FailWith(
MessageFormatNoCalls,
matcher.GetPatterns()
)
.Then
.ForCondition(condition)
.FailWith(
MessageFormat,
_ => matcher.GetPatterns(),
requests => requests.Select(expression)
);
_requestMessages = filter(_requestMessages).ToList();
return new AndConstraint<WireMockAssertions>(this);
}
private AndConstraint<WireMockAssertions> ExecuteAssertionWithBodyAsJsonValueMatcher(
IValueMatcher matcher,
string because,
object[] becauseArgs,
Func<IReadOnlyList<IRequestMessage>, bool> condition,
Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> filter,
Func<IRequestMessage, object?> expression
)
{
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => _requestMessages)
.ForCondition(requests => _callsCount == 0 || requests.Any())
.FailWith(
MessageFormatNoCalls,
matcher.Value
)
.Then
.ForCondition(condition)
.FailWith(
MessageFormat,
_ => matcher.Value,
requests => requests.Select(expression)
);
_requestMessages = filter(_requestMessages).ToList();
return new AndConstraint<WireMockAssertions>(this);
}
private AndConstraint<WireMockAssertions> ExecuteAssertionWithBodyAsBytesExactObjectMatcher(
ExactObjectMatcher matcher,
string because,
object[] becauseArgs,
Func<IReadOnlyList<IRequestMessage>, bool> condition,
Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> filter,
Func<IRequestMessage, object?> expression
)
{
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => _requestMessages)
.ForCondition(requests => _callsCount == 0 || requests.Any())
.FailWith(
MessageFormatNoCalls,
matcher.ValueAsObject ?? matcher.ValueAsBytes
)
.Then
.ForCondition(condition)
.FailWith(
MessageFormat,
_ => matcher.ValueAsObject ?? matcher.ValueAsBytes,
requests => requests.Select(expression)
);
_requestMessages = filter(_requestMessages).ToList();
return new AndConstraint<WireMockAssertions>(this);
}
}

View File

@@ -1,17 +1,14 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using FluentAssertions.Execution;
using WireMock.Constants;
using WireMock.Matchers;
using WireMock.Server;
using WireMock.Types;
// ReSharper disable once CheckNamespace
namespace WireMock.FluentAssertions;
public class WireMockAssertions
public partial class WireMockAssertions
{
private const string Any = "*";
private readonly int? _callsCount;
@@ -28,9 +25,7 @@ public class WireMockAssertions
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> AtAbsoluteUrl(string absoluteUrl, string because = "", params object[] becauseArgs)
{
Func<IRequestMessage, bool> predicate = request => string.Equals(request.AbsoluteUrl, absoluteUrl, StringComparison.OrdinalIgnoreCase);
var (filter, condition) = BuildFilterAndCondition(predicate);
var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.AbsoluteUrl, absoluteUrl, StringComparison.OrdinalIgnoreCase));
Execute.Assertion
.BecauseOf(because, becauseArgs)
@@ -55,9 +50,7 @@ public class WireMockAssertions
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> AtUrl(string url, string because = "", params object[] becauseArgs)
{
Func<IRequestMessage, bool> predicate = request => string.Equals(request.Url, url, StringComparison.OrdinalIgnoreCase);
var (filter, condition) = BuildFilterAndCondition(predicate);
var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.Url, url, StringComparison.OrdinalIgnoreCase));
Execute.Assertion
.BecauseOf(because, becauseArgs)
@@ -83,9 +76,7 @@ public class WireMockAssertions
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> WithProxyUrl(string proxyUrl, string because = "", params object[] becauseArgs)
{
Func<IRequestMessage, bool> predicate = request => string.Equals(request.ProxyUrl, proxyUrl, StringComparison.OrdinalIgnoreCase);
var (filter, condition) = BuildFilterAndCondition(predicate);
var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.ProxyUrl, proxyUrl, StringComparison.OrdinalIgnoreCase));
Execute.Assertion
.BecauseOf(because, becauseArgs)
@@ -111,9 +102,7 @@ public class WireMockAssertions
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> FromClientIP(string clientIP, string because = "", params object[] becauseArgs)
{
Func<IRequestMessage, bool> predicate = request => string.Equals(request.ClientIP, clientIP, StringComparison.OrdinalIgnoreCase);
var (filter, condition) = BuildFilterAndCondition(predicate);
var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.ClientIP, clientIP, StringComparison.OrdinalIgnoreCase));
Execute.Assertion
.BecauseOf(because, becauseArgs)
@@ -168,80 +157,20 @@ public class WireMockAssertions
return new AndConstraint<WireMockAssertions>(this);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingConnect(string because = "", params object[] becauseArgs)
=> UsingMethod(HttpRequestMethod.CONNECT, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingDelete(string because = "", params object[] becauseArgs)
=> UsingMethod(HttpRequestMethod.DELETE, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingGet(string because = "", params object[] becauseArgs)
=> UsingMethod(HttpRequestMethod.GET, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingHead(string because = "", params object[] becauseArgs)
=> UsingMethod(HttpRequestMethod.HEAD, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingOptions(string because = "", params object[] becauseArgs)
=> UsingMethod(HttpRequestMethod.OPTIONS, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingPost(string because = "", params object[] becauseArgs)
=> UsingMethod(HttpRequestMethod.POST, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingPatch(string because = "", params object[] becauseArgs)
=> UsingMethod(HttpRequestMethod.PATCH, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingPut(string because = "", params object[] becauseArgs)
=> UsingMethod(HttpRequestMethod.PUT, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingTrace(string because = "", params object[] becauseArgs)
=> UsingMethod(HttpRequestMethod.TRACE, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingAnyMethod(string because = "", params object[] becauseArgs)
=> UsingMethod(Any, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> UsingMethod(string method, string because = "", params object[] becauseArgs)
{
var any = method == Any;
Func<IRequestMessage, bool> predicate = request => (any && !string.IsNullOrEmpty(request.Method)) ||
string.Equals(request.Method, method, StringComparison.OrdinalIgnoreCase);
var (filter, condition) = BuildFilterAndCondition(predicate);
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => _requestMessages)
.ForCondition(requests => _callsCount == 0 || requests.Any())
.FailWith(
"Expected {context:wiremockserver} to have been called using method {0}{reason}, but no calls were made.",
method
)
.Then
.ForCondition(condition)
.FailWith(
"Expected {context:wiremockserver} to have been called using method {0}{reason}, but didn't find it among the methods {1}.",
_ => method,
requests => requests.Select(request => request.Method)
);
_requestMessages = filter(_requestMessages).ToList();
return new AndConstraint<WireMockAssertions>(this);
}
private (Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> Filter, Func<IReadOnlyList<IRequestMessage>, bool> Condition) BuildFilterAndCondition(Func<IRequestMessage, bool> predicate)
{
Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> filter = requests => requests.Where(predicate).ToList();
return (filter, requests => (_callsCount is null && filter(requests).Any()) || _callsCount == filter(requests).Count);
}
private (Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> Filter, Func<IReadOnlyList<IRequestMessage>, bool> Condition) BuildFilterAndCondition(Func<IRequestMessage, string?> expression, IStringMatcher matcher)
{
return BuildFilterAndCondition(r => matcher.IsMatch(expression(r)).IsPerfect());
}
private (Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> Filter, Func<IReadOnlyList<IRequestMessage>, bool> Condition) BuildFilterAndCondition(Func<IRequestMessage, object?> expression, IObjectMatcher matcher)
{
return BuildFilterAndCondition(r => matcher.IsMatch(expression(r)).IsPerfect());
}
}

View File

@@ -45,7 +45,7 @@ namespace WireMock.FluentAssertions
return new WireMockANumberOfCallsAssertions(Subject, callsCount);
}
/// <inheritdoc cref="ReferenceTypeAssertions{IWireMockServer, WireMockReceivedAssertions}.Identifier"/>
/// <inheritdoc />
protected override string Identifier => "wiremockserver";
}
}

View File

@@ -0,0 +1,3 @@
global using System.Linq;
global using FluentAssertions;
global using FluentAssertions.Execution;

View File

@@ -22,7 +22,7 @@
<!--<DelaySign>true</DelaySign>-->
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<LangVersion>10</LangVersion>
<LangVersion>11</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@@ -43,7 +43,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
<ProjectReference Include="..\WireMock.Net\WireMock.Net.csproj" />
</ItemGroup>
</Project>

View File

@@ -36,7 +36,7 @@ internal static class RegexUtils
{
if (useRegexExtended)
{
var regexExtended = new RegexExtended(pattern, RegexOptions.None, RegexTimeOut);
var regexExtended = new RegexExtended(pattern!, RegexOptions.None, RegexTimeOut);
return (true, regexExtended.IsMatch(input));
}

View File

@@ -6,6 +6,7 @@ using System.Net.Http.Headers;
using System.Threading.Tasks;
using FluentAssertions;
using WireMock.FluentAssertions;
using WireMock.Matchers;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
@@ -633,6 +634,194 @@ public class WireMockAssertionsTests : IDisposable
.AtUrl(_server.Url ?? string.Empty);
}
[Fact]
public async Task HaveReceived1Call_WithBodyAsString()
{
// Arrange
var server = WireMockServer.Start();
server
.Given(Request.Create().WithPath("/a").UsingPost().WithBody("x"))
.RespondWith(Response.Create().WithBody("A response"));
// Act
var httpClient = new HttpClient();
await httpClient.PostAsync($"{server.Url}/a", new StringContent("x"));
// Assert
server
.Should()
.HaveReceived(1)
.Calls()
.WithBody("*")
.And
.UsingPost();
server
.Should()
.HaveReceived(1)
.Calls()
.WithBody("x")
.And
.UsingPost();
server
.Should()
.HaveReceived(0)
.Calls()
.WithBody("")
.And
.UsingPost();
server
.Should()
.HaveReceived(0)
.Calls()
.WithBody("y")
.And
.UsingPost();
server.Stop();
}
[Fact]
public async Task HaveReceived1Call_WithBodyAsJson()
{
// Arrange
var server = WireMockServer.Start();
server
.Given(Request.Create().WithPath("/a").UsingPost().WithBodyAsJson(new { x = "y" }))
.RespondWith(Response.Create().WithBody("A response"));
// Act
var httpClient = new HttpClient();
await httpClient.PostAsync($"{server.Url}/a", new StringContent(@"{ ""x"": ""y"" }"));
// Assert
server
.Should()
.HaveReceived(1)
.Calls()
.WithBodyAsJson(new { x = "y" })
.And
.UsingPost();
server
.Should()
.HaveReceived(1)
.Calls()
.WithBodyAsJson(@"{ ""x"": ""y"" }")
.And
.UsingPost();
server
.Should()
.HaveReceived(0)
.Calls()
.WithBodyAsJson(new { x = "?" })
.And
.UsingPost();
server
.Should()
.HaveReceived(0)
.Calls()
.WithBodyAsJson(@"{ ""x"": 1234 }")
.And
.UsingPost();
server.Stop();
}
[Fact]
public async Task HaveReceived1Call_WithBodyAsBytes()
{
// Arrange
var server = WireMockServer.Start();
server
.Given(Request.Create().WithPath("/a").UsingPut().WithBody(new byte[] { 100 }))
.RespondWith(Response.Create().WithBody("A response"));
// Act
var httpClient = new HttpClient();
await httpClient.PutAsync($"{server.Url}/a", new ByteArrayContent(new byte[] { 100 }));
// Assert
server
.Should()
.HaveReceived(1)
.Calls()
.WithBodyAsBytes(new byte[] { 100 })
.And
.UsingPut();
server
.Should()
.HaveReceived(0)
.Calls()
.WithBodyAsBytes(new byte[0])
.And
.UsingPut();
server
.Should()
.HaveReceived(0)
.Calls()
.WithBodyAsBytes(new byte[] { 42 })
.And
.UsingPut();
server.Stop();
}
[Fact]
public async Task HaveReceived1Call_WithBodyAsString_UsingStringMatcher()
{
// Arrange
var server = WireMockServer.Start();
server
.Given(Request.Create().WithPath("/a").UsingPost().WithBody("x"))
.RespondWith(Response.Create().WithBody("A response"));
// Act
var httpClient = new HttpClient();
await httpClient.PostAsync($"{server.Url}/a", new StringContent("x"));
// Assert
server
.Should()
.HaveReceived(1)
.Calls()
.WithBody(new ExactMatcher("x"))
.And
.UsingPost();
server
.Should()
.HaveReceived(0)
.Calls()
.WithBody(new ExactMatcher(""))
.And
.UsingPost();
server
.Should()
.HaveReceived(0)
.Calls()
.WithBody(new ExactMatcher("y"))
.And
.UsingPost();
server.Stop();
}
public void Dispose()
{
_server?.Stop();

View File

@@ -30,8 +30,7 @@ public class RegexUtilsTests
[InlineData(null, "test", false, false)]
[InlineData(".*", "test", true, true)]
[InlineData("invalid[", "test", false, false)]
public void MatchRegex_WithVariousPatterns_ReturnsExpectedResults(
string? pattern, string input, bool expectedIsValid, bool expectedResult)
public void MatchRegex_WithVariousPatterns_ReturnsExpectedResults(string? pattern, string input, bool expectedIsValid, bool expectedResult)
{
// Act
var (isValidResult, matchResult) = RegexUtils.MatchRegex(pattern, input);
@@ -46,8 +45,7 @@ public class RegexUtilsTests
[InlineData(null, "test", false, false)]
[InlineData(".*", "test", true, true)]
[InlineData("invalid[", "test", false, false)]
public void MatchRegex_WithVariousPatternsAndExtendedRegex_ReturnsExpectedResults(
string? pattern, string input, bool expectedIsValid, bool expectedResult)
public void MatchRegex_WithVariousPatternsAndExtendedRegex_ReturnsExpectedResults(string? pattern, string input, bool expectedIsValid, bool expectedResult)
{
// Act
var (isValidResult, matchResult) = RegexUtils.MatchRegex(pattern, input, useRegexExtended: true);