Compare commits

..

12 Commits

Author SHA1 Message Date
Stef Heyenrath
29e149a7fa 1.5.40 2023-11-07 18:03:34 +01:00
Stef Heyenrath
7160dbdd19 FluentAssertions - WithBody and WithBodyAsJson and WithBodyAsBytes (#1014)
* WithBody

* .

* fix

* .

* .
2023-11-04 16:17:23 +01:00
Stef Heyenrath
2f29d80336 Add more tests for JmesPathMatchers and StringUtils.ParseMatchOperator (#1009)
* Add more tests for JmesPathMatchers and StringUtils.ParseMatchOperator

* .

* prio
2023-10-16 17:13:52 +02:00
Stef Heyenrath
f7cd4b100e GraphQL - add support for standard scalar types in the schema (#1011)
* GraphQL: register BuiltInTypes

* GraphQLMatcher_For_ValidSchema_And_CorrectGraphQL_Mutation_IsMatch
2023-10-16 16:13:53 +02:00
Stef Heyenrath
62fa4666b5 Add unit tests for HttpClient with WebProxy (#1010)
* Add test for SSL / Https

* Add unit tests for HttpClient with WebProxy

* cert.pem

* x

* skip

* example google

* revert

* host tests

* sonar
2023-10-14 17:55:29 +02:00
Stef Heyenrath
30372a9348 1.5.39 2023-10-09 18:30:19 +02:00
Stef Heyenrath
bc75db8c8c Fix RequestMessageParamMatcher : RejectOnMatch (#1006) 2023-10-09 18:28:22 +02:00
Stef Heyenrath
6254ee3950 1.5.38 2023-10-02 19:33:32 +02:00
Carsten Alder
60bf12e2a9 Support for xml namespaces in XPathMatcher (#1005)
* Support for xml namespaces in XPathMatcher

* Review findings of Stef implemented.

* Fix of build error

* New review findings by Stef

---------

Co-authored-by: Carsten Alder <carsten.alder@schleupen.de>
2023-10-02 19:29:31 +02:00
Stef Heyenrath
a25a8cabf8 1.5.37 2023-09-27 21:43:50 +02:00
Stef Heyenrath
9f9fc85a64 JmesPathMatcherTests (#998) 2023-09-27 21:42:34 +02:00
Stef Heyenrath
b63076a9ac Fix MappingModel to map IgnoreCase and RejectOnMatch for Headers, Cookies and Parameters (#1004) 2023-09-26 21:01:33 +02:00
50 changed files with 1567 additions and 214 deletions

View File

@@ -1,3 +1,21 @@
# 1.5.40 (07 November 2023)
- [#1009](https://github.com/WireMock-Net/WireMock.Net/pull/1009) - Add more tests for JmesPathMatchers and StringUtils.ParseMatchOperator [test] contributed by [StefH](https://github.com/StefH)
- [#1010](https://github.com/WireMock-Net/WireMock.Net/pull/1010) - Add unit tests for HttpClient with WebProxy [test] contributed by [StefH](https://github.com/StefH)
- [#1011](https://github.com/WireMock-Net/WireMock.Net/pull/1011) - GraphQL - add support for standard scalar types in the schema [feature] contributed by [StefH](https://github.com/StefH)
- [#1014](https://github.com/WireMock-Net/WireMock.Net/pull/1014) - FluentAssertions - WithBody and WithBodyAsJson and WithBodyAsBytes contributed by [StefH](https://github.com/StefH)
# 1.5.39 (09 October 2023)
- [#1006](https://github.com/WireMock-Net/WireMock.Net/pull/1006) - Fix RequestMessageParamMatcher : RejectOnMatch [bug] contributed by [StefH](https://github.com/StefH)
- [#997](https://github.com/WireMock-Net/WireMock.Net/issues/997) - JmesPathMatcher or and MatchOperator working in version 1.5.34 but not 1.5.35 [bug]
# 1.5.38 (02 October 2023)
- [#1005](https://github.com/WireMock-Net/WireMock.Net/pull/1005) - Support for xml namespaces in XPathMatcher [feature] contributed by [cal-schleupen](https://github.com/cal-schleupen)
# 1.5.37 (27 September 2023)
- [#998](https://github.com/WireMock-Net/WireMock.Net/pull/998) - Add JmesPathMatcher UnitTests [test] contributed by [StefH](https://github.com/StefH)
- [#1004](https://github.com/WireMock-Net/WireMock.Net/pull/1004) - Fix MappingModel to map IgnoreCase and RejectOnMatch for Headers, Cookies and Parameters [bug] contributed by [StefH](https://github.com/StefH)
- [#1003](https://github.com/WireMock-Net/WireMock.Net/issues/1003) - Store Mapping per POST request ignores &quot;IgnoreCase&quot; property of HeaderModel [bug]
# 1.5.36 (21 September 2023)
- [#986](https://github.com/WireMock-Net/WireMock.Net/pull/986) - Write logging in case a Matcher throws an exception [feature] contributed by [StefH](https://github.com/StefH)
- [#996](https://github.com/WireMock-Net/WireMock.Net/pull/996) - Remove dependency on Microsoft.AspNet.WebApi.Client [feature] contributed by [StefH](https://github.com/StefH)

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>1.5.36</VersionPrefix>
<VersionPrefix>1.5.40</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/WireMock-Net/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>

View File

@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes
SET version=1.5.36
SET version=1.5.40
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels question invalid doc duplicate --version %version% --token %GH_TOKEN%

View File

@@ -1,8 +1,7 @@
# 1.5.36 (21 September 2023)
- #986 Write logging in case a Matcher throws an exception [feature]
- #996 Remove dependency on Microsoft.AspNet.WebApi.Client [feature]
- #1002 Fixed logic for SaveUnmatchedRequests [bug]
- #974 HttpClient extension methods causes ambiguous invocations in .NET 7 [bug]
- #1001 SaveUnmatchedRequests stopped working [bug]
# 1.5.40 (07 November 2023)
- #1009 Add more tests for JmesPathMatchers and StringUtils.ParseMatchOperator [test]
- #1010 Add unit tests for HttpClient with WebProxy [test]
- #1011 GraphQL - add support for standard scalar types in the schema [feature]
- #1014 FluentAssertions - WithBody and WithBodyAsJson and WithBodyAsBytes
The full release notes can be found here: https://github.com/WireMock-Net/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -1,4 +1,4 @@
using WireMock.Logging;
using WireMock.Logging;
using WireMock.Server;
using WireMock.Settings;
@@ -24,7 +24,6 @@ namespace WireMock.Net.Console.NETCoreApp3WithCertificate
// X509CertificateFilePath = "example.pfx",
// X509CertificatePassword = "wiremock"
}
});
System.Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls));

View File

@@ -73,4 +73,8 @@ public class MatcherModel
/// </summary>
public MatcherModel? ContentMatcher { get; set; }
#endregion
#region XPathMatcher
public XmlNamespace[]? XmlNamespaceMap { get; set; }
#endregion
}

View File

@@ -16,6 +16,11 @@ public class ParamModel
/// </summary>
public bool? IgnoreCase { get; set; }
/// <summary>
/// Gets or sets the Reject on match for the Param Name.
/// </summary>
public bool? RejectOnMatch { get; set; }
/// <summary>
/// Gets or sets the matchers.
/// </summary>

View File

@@ -0,0 +1,21 @@
namespace WireMock.Admin.Mappings;
/// <summary>
/// Defines an xml namespace consisting of prefix and uri.
/// <example>xmlns:i="http://www.w3.org/2001/XMLSchema-instance"</example>
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class XmlNamespace
{
/// <summary>
/// The prefix.
/// <example>i</example>
/// </summary>
public string Prefix { get; set; } = null!;
/// <summary>
/// The uri.
/// <example>http://www.w3.org/2001/XMLSchema-instance</example>
/// </summary>
public string Uri { get; set; } = null!;
}

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

@@ -137,9 +137,14 @@ public class GraphQLMatcher : IStringMatcher
}
}
private static ISchema BuildSchema(string schema)
private static ISchema BuildSchema(string typeDefinitions)
{
return Schema.For(schema);
var schema = Schema.For(typeDefinitions);
// #984
schema.RegisterTypes(schema.BuiltInTypeMappings.Select(x => x.graphType).ToArray());
return schema;
}
}
#endif

View File

@@ -118,8 +118,9 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
// The SelectToken method can accept a string path to a child token ( i.e. "Manufacturers[0].Products[0].Price").
// In that case it will return a JValue (some type) which does not implement the IEnumerable interface.
return MatchScores.ToScore(
_patterns.Select(pattern => array.SelectToken(pattern.GetPattern()) != null).ToArray(), MatchOperator);
var values = _patterns.Select(pattern => array.SelectToken(pattern.GetPattern()) != null).ToArray();
return MatchScores.ToScore(values, MatchOperator);
}
// https://github.com/WireMock-Net/WireMock.Net/issues/965

View File

@@ -53,7 +53,7 @@ public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false)
{
Guard.NotNull(value, nameof(value));
Guard.NotNull(value);
MatchBehaviour = matchBehaviour;
IgnoreCase = ignoreCase;

View File

@@ -11,9 +11,15 @@ namespace WireMock.Matchers.Request;
/// <inheritdoc cref="IRequestMatcher"/>
public class RequestMessageCookieMatcher : IRequestMatcher
{
private readonly MatchBehaviour _matchBehaviour;
/// <summary>
/// MatchBehaviour
/// </summary>
public MatchBehaviour MatchBehaviour { get; }
private readonly bool _ignoreCase;
/// <summary>
/// IgnoreCase
/// </summary>
public bool IgnoreCase { get; }
/// <summary>
/// The functions
@@ -39,8 +45,8 @@ public class RequestMessageCookieMatcher : IRequestMatcher
/// <param name="matchBehaviour">The match behaviour.</param>
public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, string name, string pattern, bool ignoreCase)
{
_matchBehaviour = matchBehaviour;
_ignoreCase = ignoreCase;
MatchBehaviour = matchBehaviour;
IgnoreCase = ignoreCase;
Name = Guard.NotNull(name);
Matchers = new IStringMatcher[] { new WildcardMatcher(matchBehaviour, Guard.NotNull(pattern), ignoreCase) };
}
@@ -67,10 +73,10 @@ public class RequestMessageCookieMatcher : IRequestMatcher
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, string name, bool ignoreCase, params IStringMatcher[] matchers)
{
_matchBehaviour = matchBehaviour;
MatchBehaviour = matchBehaviour;
Name = Guard.NotNull(name);
Matchers = Guard.NotNull(matchers);
_ignoreCase = ignoreCase;
IgnoreCase = ignoreCase;
}
/// <summary>
@@ -96,11 +102,11 @@ public class RequestMessageCookieMatcher : IRequestMatcher
{
if (requestMessage.Cookies == null)
{
return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch);
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
}
// Check if we want to use IgnoreCase to compare the Cookie-Name and Cookie-Value
var cookies = !_ignoreCase ? requestMessage.Cookies : new Dictionary<string, string>(requestMessage.Cookies, StringComparer.OrdinalIgnoreCase);
var cookies = !IgnoreCase ? requestMessage.Cookies : new Dictionary<string, string>(requestMessage.Cookies, StringComparer.OrdinalIgnoreCase);
if (Funcs != null)
{
@@ -114,7 +120,7 @@ public class RequestMessageCookieMatcher : IRequestMatcher
if (!cookies.ContainsKey(Name))
{
return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch);
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
}
return Matchers.Max(m => m.IsMatch(cookies[Name]));

View File

@@ -12,8 +12,15 @@ namespace WireMock.Matchers.Request;
/// <inheritdoc cref="IRequestMatcher"/>
public class RequestMessageHeaderMatcher : IRequestMatcher
{
private readonly MatchBehaviour _matchBehaviour;
private readonly bool _ignoreCase;
/// <summary>
/// MatchBehaviour
/// </summary>
public MatchBehaviour MatchBehaviour { get; }
/// <summary>
/// IgnoreCase
/// </summary>
public bool IgnoreCase { get; }
/// <summary>
/// The functions
@@ -47,8 +54,8 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
Guard.NotNull(name);
Guard.NotNull(pattern);
_matchBehaviour = matchBehaviour;
_ignoreCase = ignoreCase;
MatchBehaviour = matchBehaviour;
IgnoreCase = ignoreCase;
Name = name;
Matchers = new IStringMatcher[] { new WildcardMatcher(matchBehaviour, pattern, ignoreCase) };
}
@@ -80,11 +87,11 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
Guard.NotNull(name);
Guard.NotNull(matchers);
_matchBehaviour = matchBehaviour;
MatchBehaviour = matchBehaviour;
MatchOperator = matchOperator;
Name = name;
Matchers = matchers;
_ignoreCase = ignoreCase;
IgnoreCase = ignoreCase;
}
/// <summary>
@@ -108,11 +115,11 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
{
if (requestMessage.Headers == null)
{
return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch);
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
}
// Check if we want to use IgnoreCase to compare the Header-Name and Header-Value(s)
var headers = !_ignoreCase ? requestMessage.Headers : new Dictionary<string, WireMockList<string>>(requestMessage.Headers, StringComparer.OrdinalIgnoreCase);
var headers = !IgnoreCase ? requestMessage.Headers : new Dictionary<string, WireMockList<string>>(requestMessage.Headers, StringComparer.OrdinalIgnoreCase);
if (Funcs != null)
{
@@ -124,7 +131,7 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
{
if (!headers.ContainsKey(Name))
{
return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch);
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
}
var results = new List<MatchResult>();
@@ -138,6 +145,6 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
return MatchResult.From(results, MatchOperator);
}
return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch);
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
}
}

View File

@@ -29,7 +29,7 @@ public class RequestMessageParamMatcher : IRequestMatcher
/// <summary>
/// Defines if the key should be matched using case-ignore.
/// </summary>
public bool? IgnoreCase { get; }
public bool IgnoreCase { get; }
/// <summary>
/// The matchers.
@@ -85,22 +85,22 @@ public class RequestMessageParamMatcher : IRequestMatcher
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
var (score, exception) = GetMatchResult(requestMessage).Expand();
return requestMatchResult.AddScore(GetType(), score, exception);
var score = GetMatchScore(requestMessage);
return requestMatchResult.AddScore(GetType(), MatchBehaviourHelper.Convert(MatchBehaviour, score), null);
}
private MatchResult GetMatchResult(IRequestMessage requestMessage)
private double GetMatchScore(IRequestMessage requestMessage)
{
if (Funcs != null)
{
return MatchScores.ToScore(requestMessage.Query != null && Funcs.Any(f => f(requestMessage.Query)));
}
var valuesPresentInRequestMessage = ((RequestMessage)requestMessage).GetParameter(Key, IgnoreCase ?? false);
var valuesPresentInRequestMessage = ((RequestMessage)requestMessage).GetParameter(Key, IgnoreCase);
if (valuesPresentInRequestMessage == null)
{
// Key is not present at all, just return Mismatch
return default;
return MatchScores.Mismatch;
}
if (Matchers != null && Matchers.Any())
@@ -115,10 +115,10 @@ public class RequestMessageParamMatcher : IRequestMatcher
return MatchScores.Perfect;
}
return default;
return MatchScores.Mismatch;
}
private static MatchResult CalculateScore(IReadOnlyList<IStringMatcher> matchers, WireMockList<string> valuesPresentInRequestMessage)
private static double CalculateScore(IReadOnlyList<IStringMatcher> matchers, WireMockList<string> valuesPresentInRequestMessage)
{
var total = new List<double>();

View File

@@ -1,5 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.XPath;
@@ -7,6 +7,7 @@ using AnyOfTypes;
using WireMock.Extensions;
using WireMock.Models;
using Stef.Validation;
using WireMock.Admin.Mappings;
#if !NETSTANDARD1_3
using Wmhelp.XPath2;
#endif
@@ -24,11 +25,16 @@ public class XPathMatcher : IStringMatcher
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <summary>
/// Array of namespace prefix and uri.
/// </summary>
public XmlNamespace[]? XmlNamespaceMap { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="XPathMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
public XPathMatcher(params AnyOf<string, StringPattern>[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns)
public XPathMatcher(params AnyOf<string, StringPattern>[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, null, patterns)
{
}
@@ -37,13 +43,16 @@ public class XPathMatcher : IStringMatcher
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
/// <param name="xmlNamespaceMap">The xml namespaces of the xml document.</param>
/// <param name="patterns">The patterns.</param>
public XPathMatcher(
MatchBehaviour matchBehaviour,
MatchOperator matchOperator = MatchOperator.Or,
XmlNamespace[]? xmlNamespaceMap = null,
params AnyOf<string, StringPattern>[] patterns)
{
_patterns = Guard.NotNull(patterns);
XmlNamespaceMap = xmlNamespaceMap;
MatchBehaviour = matchBehaviour;
MatchOperator = matchOperator;
}
@@ -52,24 +61,34 @@ public class XPathMatcher : IStringMatcher
public MatchResult IsMatch(string? input)
{
var score = MatchScores.Mismatch;
Exception? exception = null;
if (input != null && TryGetXPathNavigator(input, out var nav))
if (input == null)
{
try
{
#if NETSTANDARD1_3
score = MatchScores.ToScore(_patterns.Select(p => true.Equals(nav.Evaluate($"boolean({p.GetPattern()})"))).ToArray(), MatchOperator);
#else
score = MatchScores.ToScore(_patterns.Select(p => true.Equals(nav.XPath2Evaluate($"boolean({p.GetPattern()})"))).ToArray(), MatchOperator);
#endif
}
catch (Exception ex)
{
exception = ex;
}
return CreateMatchResult(score);
}
try
{
var xPathEvaluator = new XPathEvaluator();
xPathEvaluator.Load(input);
if (!xPathEvaluator.IsXmlDocumentLoaded)
{
return CreateMatchResult(score);
}
score = MatchScores.ToScore(xPathEvaluator.Evaluate(_patterns, XmlNamespaceMap), MatchOperator);
}
catch (Exception exception)
{
return CreateMatchResult(score, exception);
}
return CreateMatchResult(score);
}
private MatchResult CreateMatchResult(double score, Exception? exception = null)
{
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
@@ -84,18 +103,54 @@ public class XPathMatcher : IStringMatcher
/// <inheritdoc />
public string Name => nameof(XPathMatcher);
private static bool TryGetXPathNavigator(string input, [NotNullWhen(true)] out XPathNavigator? nav)
private class XPathEvaluator
{
try
private XmlDocument? _xmlDocument;
private XPathNavigator? _xpathNavigator;
public bool IsXmlDocumentLoaded => _xmlDocument != null;
public void Load(string input)
{
nav = new XmlDocument { InnerXml = input }.CreateNavigator()!;
return true;
try
{
_xmlDocument = new XmlDocument { InnerXml = input };
_xpathNavigator = _xmlDocument.CreateNavigator();
}
catch
{
_xmlDocument = default;
}
}
catch
public bool[] Evaluate(AnyOf<string, StringPattern>[] patterns, IEnumerable<XmlNamespace>? xmlNamespaceMap)
{
nav = default;
return false;
XmlNamespaceManager? xmlNamespaceManager = GetXmlNamespaceManager(xmlNamespaceMap);
return patterns
.Select(p =>
#if NETSTANDARD1_3
true.Equals(_xpathNavigator.Evaluate($"boolean({p.GetPattern()})", xmlNamespaceManager)))
#else
true.Equals(_xpathNavigator.XPath2Evaluate($"boolean({p.GetPattern()})", xmlNamespaceManager)))
#endif
.ToArray();
}
private XmlNamespaceManager? GetXmlNamespaceManager(IEnumerable<XmlNamespace>? xmlNamespaceMap)
{
if (_xpathNavigator == null || xmlNamespaceMap == null)
{
return default;
}
var nsManager = new XmlNamespaceManager(_xpathNavigator.NameTable);
foreach (XmlNamespace xmlNamespace in xmlNamespaceMap)
{
nsManager.AddNamespace(xmlNamespace.Prefix, xmlNamespace.Uri);
}
return nsManager;
}
}
}

View File

@@ -12,8 +12,6 @@ internal class HostUrlOptions
public int? Port { get; set; }
public int? HttpsPort { get; set; }
public HostingScheme HostingScheme { get; set; }
public IReadOnlyList<HostUrlDetails> GetDetails()

View File

@@ -35,11 +35,7 @@ internal class MappingMatcher : IMappingMatcher
{
var nextState = GetNextState(mapping);
var mappingMatcherResult = new MappingMatcherResult
{
Mapping = mapping,
RequestMatchResult = mapping.GetRequestMatchResult(request, nextState)
};
var mappingMatcherResult = new MappingMatcherResult(mapping, mapping.GetRequestMatchResult(request, nextState));
var exceptions = mappingMatcherResult.RequestMatchResult.MatchDetails
.Where(md => md.Exception != null)
@@ -66,7 +62,10 @@ internal class MappingMatcher : IMappingMatcher
var partialMappings = possibleMappings
.Where(pm => (pm.Mapping.IsAdminInterface && pm.RequestMatchResult.IsPerfectMatch) || !pm.Mapping.IsAdminInterface)
.OrderBy(m => m.RequestMatchResult).ThenBy(m => m.Mapping.Priority).ThenByDescending(m => m.Mapping.UpdatedAt)
.OrderBy(m => m.RequestMatchResult)
.ThenBy(m => m.RequestMatchResult.TotalNumber)
.ThenBy(m => m.Mapping.Priority)
.ThenByDescending(m => m.Mapping.UpdatedAt)
.ToList();
var partialMatch = partialMappings.FirstOrDefault(pm => pm.RequestMatchResult.AverageTotalScore > 0.0);

View File

@@ -1,10 +1,17 @@
using Stef.Validation;
using WireMock.Matchers.Request;
namespace WireMock.Owin;
internal class MappingMatcherResult
{
public IMapping Mapping { get; set; }
public IMapping Mapping { get; }
public IRequestMatchResult RequestMatchResult { get; set; }
public IRequestMatchResult RequestMatchResult { get; }
public MappingMatcherResult(IMapping mapping, IRequestMatchResult requestMatchResult)
{
Mapping = Guard.NotNull(mapping);
RequestMatchResult = Guard.NotNull(requestMatchResult);
}
}

View File

@@ -259,19 +259,24 @@ internal class MappingConverter
Headers = headerMatchers.Any() ? headerMatchers.Select(hm => new HeaderModel
{
Name = hm.Name,
Matchers = _mapper.Map(hm.Matchers)
IgnoreCase = hm.IgnoreCase ? true : null,
RejectOnMatch = hm.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : null,
Matchers = _mapper.Map(hm.Matchers),
}).ToList() : null,
Cookies = cookieMatchers.Any() ? cookieMatchers.Select(cm => new CookieModel
{
Name = cm.Name,
IgnoreCase = cm.IgnoreCase ? true : null,
RejectOnMatch = cm.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : null,
Matchers = _mapper.Map(cm.Matchers)
}).ToList() : null,
Params = paramsMatchers.Any() ? paramsMatchers.Select(pm => new ParamModel
{
Name = pm.Key,
IgnoreCase = pm.IgnoreCase == true ? true : null,
IgnoreCase = pm.IgnoreCase ? true : null,
RejectOnMatch = pm.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : null,
Matchers = _mapper.Map(pm.Matchers)
}).ToList() : null
},

View File

@@ -101,7 +101,8 @@ internal class MatcherMapper
return new JmesPathMatcher(matchBehaviour, matchOperator, stringPatterns);
case nameof(XPathMatcher):
return new XPathMatcher(matchBehaviour, matchOperator, stringPatterns);
var xmlNamespaces = matcher.XmlNamespaceMap;
return new XPathMatcher(matchBehaviour, matchOperator, xmlNamespaces, stringPatterns);
case nameof(WildcardMatcher):
return new WildcardMatcher(matchBehaviour, stringPatterns, ignoreCase, matchOperator);
@@ -159,6 +160,10 @@ internal class MatcherMapper
case JsonPartialWildcardMatcher jsonPartialWildcardMatcher:
model.Regex = jsonPartialWildcardMatcher.Regex;
break;
case XPathMatcher xpathMatcher:
model.XmlNamespaceMap = xpathMatcher.XmlNamespaceMap;
break;
}
switch (matcher)

View File

@@ -61,7 +61,7 @@ public interface IRespondWithAProvider
/// <summary>
/// Define the priority for this mapping.
/// </summary>
/// <param name="priority">The priority.</param>
/// <param name="priority">The priority. (A lower value means a higher priority.)</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider AtPriority(int priority);

View File

@@ -141,6 +141,31 @@ public partial class WireMockServer : IWireMockServer
return client;
}
/// <summary>
/// Create a <see cref="HttpClient"/> which can be used to call this instance.
/// <param name="handlers">
/// <param name="innerHandler">The inner handler represents the destination of the HTTP message channel.</param>
/// An ordered list of System.Net.Http.DelegatingHandler instances to be invoked
/// as an System.Net.Http.HttpRequestMessage travels from the System.Net.Http.HttpClient
/// to the network and an System.Net.Http.HttpResponseMessage travels from the network
/// back to System.Net.Http.HttpClient. The handlers are invoked in a top-down fashion.
/// That is, the first entry is invoked first for an outbound request message but
/// last for an inbound response message.
/// </param>
/// </summary>
[PublicAPI]
public HttpClient CreateClient(HttpMessageHandler innerHandler, params DelegatingHandler[] handlers)
{
if (!IsStarted)
{
throw new InvalidOperationException("Unable to create HttpClient because the service is not started.");
}
var client = HttpClientFactory2.Create(innerHandler, handlers);
client.BaseAddress = new Uri(Url!);
return client;
}
/// <summary>
/// Create <see cref="HttpClient"/>s (one for each URL) which can be used to call this instance.
/// <param name="innerHandler">The inner handler represents the destination of the HTTP message channel.</param>

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;
@@ -215,10 +216,13 @@ public class WireMockAssertionsTests : IDisposable
{
// Arrange
using var server = WireMockServer.Start();
using var client = server.CreateClient();
using var client1 = server.CreateClient();
var handler = new HttpClientHandler();
using var client2 = server.CreateClient(handler);
// Act 1
await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/")
await client1.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/")
{
Headers =
{
@@ -227,7 +231,7 @@ public class WireMockAssertionsTests : IDisposable
});
// Act 2
await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/")
await client2.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/")
{
Headers =
{
@@ -630,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

@@ -11,7 +11,10 @@ namespace WireMock.Net.Tests.Matchers;
public class GraphQLMatcherTests
{
private const string TestSchema = @"
scalar DateTime
input MessageInput {
date: DateTime
content: String
author: String
}
@@ -24,20 +27,21 @@ public class GraphQLMatcherTests
type Mutation {
createMessage(input: MessageInput): Message
createAnotherMessage(date: DateTime, content: String, author: String): Message
updateMessage(id: ID!, input: MessageInput): Message
}
type Query {
greeting:String
students:[Student]
studentById(id:ID!):Student
greeting: String
students: [Student]
studentById(id: ID!):Student
}
type Student {
id:ID!
firstName:String
lastName:String
fullName:String
id: ID!
firstName: String
lastName: String
fullName: String
}";
[Fact]
@@ -57,7 +61,7 @@ public class GraphQLMatcherTests
}
[Fact]
public void GraphQLMatcher_For_ValidSchema_And_CorrectGraphQLQuery_IsMatch()
public void GraphQLMatcher_For_ValidSchema_And_CorrectGraphQL_Query_IsMatch()
{
// Arrange
var input = @"{
@@ -76,6 +80,25 @@ public class GraphQLMatcherTests
matcher.GetPatterns().Should().Contain(TestSchema);
}
[Fact]
public void GraphQLMatcher_For_ValidSchema_And_CorrectGraphQL_Mutation_IsMatch()
{
// Arrange
var input = @"{
""query"": ""mutation CreateAnotherMessage($date: DateTime!, $content: String!, $author: String!) { createAnotherMessage(date: $date, content: $content, author: $author) { id } }"",
""variables"": { ""date"": ""2007-12-03T10:15:30Z"", ""content"": ""--content--"", ""author"": ""--author--"" }
}";
// Act
var matcher = new GraphQLMatcher(TestSchema);
var result = matcher.IsMatch(input);
// Assert
result.Score.Should().Be(MatchScores.Perfect);
matcher.GetPatterns().Should().Contain(TestSchema);
}
[Fact]
public void GraphQLMatcher_For_ValidSchema_And_IncorrectQuery_IsMismatch()
{

View File

@@ -1,4 +1,5 @@
using System;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using NFluent;
using WireMock.Matchers;
@@ -163,4 +164,30 @@ public class JmesPathMatcherTests
// Assert
Check.That(match).IsEqualTo(0.0);
}
[Fact]
public void JmesPathMatcher_IsMatch_MultiplePatternsUsingMatchOperatorAnd()
{
// Assign
var matcher = new JmesPathMatcher(MatchOperator.And, "things.x == 'RequiredThing'", "things.x == 'abc'");
// Act
double score = matcher.IsMatch(JObject.Parse("{ \"things\": { \"x\": \"RequiredThing\" } }")).Score;
// Assert
score.Should().Be(0);
}
[Fact]
public void JmesPathMatcher_IsMatch_MultiplePatternsUsingMatchOperatorOr()
{
// Assign
var matcher = new JmesPathMatcher(MatchOperator.Or, "things.x == 'RequiredThing'", "things.x == 'abc'");
// Act
double score = matcher.IsMatch(JObject.Parse("{ \"things\": { \"x\": \"RequiredThing\" } }")).Score;
// Assert
score.Should().Be(1);
}
}

View File

@@ -1,4 +1,5 @@
using System;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using NFluent;
using WireMock.Matchers;
@@ -324,4 +325,52 @@ public class JsonPathMatcherTests
// Assert
Check.That(match).IsEqualTo(1.0);
}
[Fact]
public void JsonPathMatcher_IsMatch_MultiplePatternsUsingMatchOperatorAnd()
{
// Assign
var matcher = new JsonPathMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.And, "$.arr[0].sub[0].subline1", "$.arr[0].line2");
// Act
double match = matcher.IsMatch(JObject.Parse(@"{
""name"": ""PathSelectorTest"",
""test"": ""test"",
""test2"": ""test2"",
""arr"": [{
""line1"": ""line1"",
""sub"":[
{
""subline1"":""subline1""
}]
}]
}")).Score;
// Assert
match.Should().Be(0);
}
[Fact]
public void JsonPathMatcher_IsMatch_MultiplePatternsUsingMatchOperatorOr()
{
// Assign
var matcher = new JsonPathMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, "$.arr[0].sub[0].subline2", "$.arr[0].line1");
// Act
double match = matcher.IsMatch(JObject.Parse(@"{
""name"": ""PathSelectorTest"",
""test"": ""test"",
""test2"": ""test2"",
""arr"": [{
""line1"": ""line1"",
""sub"":[
{
""subline1"":""subline1""
}]
}]
}")).Score;
// Assert
match.Should().Be(1);
}
}

View File

@@ -1,4 +1,5 @@
using NFluent;
using WireMock.Admin.Mappings;
using WireMock.Matchers;
using Xunit;
@@ -49,6 +50,71 @@ public class XPathMatcherTests
Check.That(result).IsEqualTo(1.0);
}
[Fact]
public void XPathMatcher_IsMatch_WithNamespaces_AcceptOnMatch()
{
// Assign
string input =
@"<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
<s:Body>
<QueryRequest xmlns=""urn://MyWcfService"">
<MaxResults i:nil=""true"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""/>
<Restriction i:nil=""true"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""/>
<SearchMode i:nil=""true"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""/>
</QueryRequest>
</s:Body>
</s:Envelope>";
var xmlNamespaces = new[]
{
new XmlNamespace { Prefix = "s", Uri = "http://schemas.xmlsoap.org/soap/envelope/" },
new XmlNamespace { Prefix = "i", Uri = "http://www.w3.org/2001/XMLSchema-instance" }
};
var matcher = new XPathMatcher(
MatchBehaviour.AcceptOnMatch,
MatchOperator.Or,
xmlNamespaces,
"/s:Envelope/s:Body/*[local-name()='QueryRequest' and namespace-uri()='urn://MyWcfService']");
// Act
double result = matcher.IsMatch(input).Score;
// Assert
Check.That(result).IsEqualTo(1.0);
}
[Fact]
public void XPathMatcher_IsMatch_WithNamespaces_OneSelfDefined_AcceptOnMatch()
{
// Assign
string input =
@"<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
<s:Body>
<QueryRequest xmlns=""urn://MyWcfService"">
<MaxResults i:nil=""true"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""/>
<Restriction i:nil=""true"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""/>
<SearchMode i:nil=""true"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""/>
</QueryRequest>
</s:Body>
</s:Envelope>";
var xmlNamespaces = new[]
{
new XmlNamespace { Prefix = "s", Uri = "http://schemas.xmlsoap.org/soap/envelope/" },
new XmlNamespace { Prefix = "i", Uri = "http://www.w3.org/2001/XMLSchema-instance" },
new XmlNamespace { Prefix = "q", Uri = "urn://MyWcfService" }
};
var matcher = new XPathMatcher(
MatchBehaviour.AcceptOnMatch,
MatchOperator.Or,
xmlNamespaces,
"/s:Envelope/s:Body/q:QueryRequest");
// Act
double result = matcher.IsMatch(input).Score;
// Assert
Check.That(result).IsEqualTo(1.0);
}
[Fact]
public void XPathMatcher_IsMatch_RejectOnMatch()
{
@@ -57,7 +123,7 @@ public class XPathMatcherTests
<todo-list>
<todo-item id='a1'>abc</todo-item>
</todo-list>";
var matcher = new XPathMatcher(MatchBehaviour.RejectOnMatch, MatchOperator.Or, "/todo-list[count(todo-item) = 1]");
var matcher = new XPathMatcher(MatchBehaviour.RejectOnMatch, MatchOperator.Or, null, "/todo-list[count(todo-item) = 1]");
// Act
double result = matcher.IsMatch(xml).Score;

View File

@@ -0,0 +1,60 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using FluentAssertions;
using WireMock.Owin;
using WireMock.Types;
using Xunit;
namespace WireMock.Net.Tests.Owin;
[ExcludeFromCodeCoverage]
public class HostUrlOptionsTests
{
[Fact]
public void GetDetails_WithNoUrlsAndHttpScheme_ShouldReturnCorrectDetails()
{
// Arrange
var options = new HostUrlOptions
{
HostingScheme = HostingScheme.Http,
Port = 8080
};
// Act
var details = options.GetDetails();
// Assert
details.Should().HaveCount(1);
var detail = details.Single();
detail.Should().Match<HostUrlDetails>(d =>
d.Scheme == "http" &&
d.Host == "localhost" &&
d.Port == 8080 &&
d.IsHttps == false
);
}
[Fact]
public void GetDetails_WithNoUrlsAndHttpsScheme_ShouldReturnCorrectDetails()
{
// Arrange
var options = new HostUrlOptions
{
HostingScheme = HostingScheme.Https,
Port = 8081
};
// Act
var details = options.GetDetails();
// Assert
details.Should().HaveCount(1);
var detail = details.Single();
detail.Should().Match<HostUrlDetails>(d =>
d.Scheme == "https" &&
d.Host == "localhost" &&
d.Port == 8081 &&
d.IsHttps == true
);
}
}

View File

@@ -16,6 +16,7 @@ using WireMock.Admin.Requests;
using WireMock.Settings;
using FluentAssertions;
using WireMock.Handlers;
using WireMock.Matchers.Request;
using WireMock.ResponseBuilders;
using WireMock.RequestBuilders;
#if NET452
@@ -43,6 +44,7 @@ public class WireMockMiddlewareTests
private readonly Mock<IOwinResponseMapper> _responseMapperMock;
private readonly Mock<IMappingMatcher> _matcherMock;
private readonly Mock<IMapping> _mappingMock;
private readonly Mock<IRequestMatchResult> _requestMatchResultMock;
private readonly Mock<IContext> _contextMock;
private readonly WireMockMiddleware _sut;
@@ -72,12 +74,16 @@ public class WireMockMiddlewareTests
_matcherMock = new Mock<IMappingMatcher>();
_matcherMock.SetupAllProperties();
_matcherMock.Setup(m => m.FindBestMatch(It.IsAny<RequestMessage>())).Returns((new MappingMatcherResult(), new MappingMatcherResult()));
// _matcherMock.Setup(m => m.FindBestMatch(It.IsAny<RequestMessage>())).Returns((new MappingMatcherResult(), new MappingMatcherResult()));
_contextMock = new Mock<IContext>();
_mappingMock = new Mock<IMapping>();
_requestMatchResultMock = new Mock<IRequestMatchResult>();
_requestMatchResultMock.Setup(r => r.TotalNumber).Returns(1);
_requestMatchResultMock.Setup(r => r.MatchDetails).Returns(new List<MatchDetail>());
_sut = new WireMockMiddleware(
null,
_optionsMock.Object,
@@ -133,7 +139,7 @@ public class WireMockMiddlewareTests
_optionsMock.SetupGet(o => o.AuthenticationMatcher).Returns(new ExactMatcher());
_mappingMock.SetupGet(m => m.IsAdminInterface).Returns(true);
var result = new MappingMatcherResult { Mapping = _mappingMock.Object };
var result = new MappingMatcherResult(_mappingMock.Object, _requestMatchResultMock.Object);
_matcherMock.Setup(m => m.FindBestMatch(It.IsAny<RequestMessage>())).Returns((result, result));
// Act
@@ -156,7 +162,7 @@ public class WireMockMiddlewareTests
_optionsMock.SetupGet(o => o.AuthenticationMatcher).Returns(new ExactMatcher());
_mappingMock.SetupGet(m => m.IsAdminInterface).Returns(true);
var result = new MappingMatcherResult { Mapping = _mappingMock.Object };
var result = new MappingMatcherResult(_mappingMock.Object, _requestMatchResultMock.Object);
_matcherMock.Setup(m => m.FindBestMatch(It.IsAny<RequestMessage>())).Returns((result, result));
// Act
@@ -216,7 +222,7 @@ public class WireMockMiddlewareTests
var requestBuilder = Request.Create().UsingAnyMethod();
_mappingMock.SetupGet(m => m.RequestMatcher).Returns(requestBuilder);
var result = new MappingMatcherResult { Mapping = _mappingMock.Object };
var result = new MappingMatcherResult(_mappingMock.Object, _requestMatchResultMock.Object);
_matcherMock.Setup(m => m.FindBestMatch(It.IsAny<RequestMessage>())).Returns((result, result));
// Act
@@ -270,7 +276,7 @@ public class WireMockMiddlewareTests
var requestBuilder = Request.Create().UsingAnyMethod();
_mappingMock.SetupGet(m => m.RequestMatcher).Returns(requestBuilder);
var result = new MappingMatcherResult { Mapping = _mappingMock.Object };
var result = new MappingMatcherResult (_mappingMock.Object, _requestMatchResultMock.Object);
_matcherMock.Setup(m => m.FindBestMatch(It.IsAny<RequestMessage>())).Returns((result, result));
// Act

View File

@@ -300,6 +300,31 @@ public class RequestMessageBodyMatcherTests
Check.That(score).IsEqualTo(1.0d);
}
[Fact]
public void RequestMessageBodyMatcher_GetMatchingScore_BodyAsJson_JmesPathMatchers()
{
// Arrange
var body = new BodyData
{
BodyAsJson = new { requestId = "1", value = "A" },
DetectedBodyType = BodyType.Json
};
var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body);
var jmesMatcher1 = new JmesPathMatcher("requestId == '1'");
var jmesMatcher2 = new JmesPathMatcher("value == 'A'");
var bodyMatcher = new RequestMessageBodyMatcher(MatchOperator.And, jmesMatcher1, jmesMatcher2);
// Act
var result = new RequestMatchResult();
double score = bodyMatcher.GetMatchingScore(requestMessage, result);
// Assert
score.Should().Be(MatchScores.Perfect);
}
[Theory]
[InlineData(null, 0.0)]
[InlineData(new byte[0], 0.0)]

View File

@@ -0,0 +1,122 @@
{
Guid: Guid_1,
UpdatedAt: DateTime_1,
Request: {
Headers: [
{
Name: MatchBehaviour.RejectOnMatch,
Matchers: [
{
Name: WildcardMatcher,
Pattern: hv-1,
IgnoreCase: true,
RejectOnMatch: true
}
],
IgnoreCase: true,
RejectOnMatch: true
},
{
Name: MatchBehaviour.AcceptOnMatch,
Matchers: [
{
Name: WildcardMatcher,
Pattern: hv-2,
IgnoreCase: true
}
],
IgnoreCase: true
},
{
Name: IgnoreCase_false,
Matchers: [
{
Name: WildcardMatcher,
Pattern: hv-3,
IgnoreCase: false
}
]
},
{
Name: IgnoreCase_true,
Matchers: [
{
Name: WildcardMatcher,
Pattern: hv-4,
IgnoreCase: true
}
],
IgnoreCase: true
},
{
Name: ExactMatcher,
Matchers: [
{
Name: ExactMatcher,
Pattern: h-exact,
IgnoreCase: false
}
]
}
],
Cookies: [
{
Name: MatchBehaviour.RejectOnMatch,
Matchers: [
{
Name: WildcardMatcher,
Pattern: cv-1,
IgnoreCase: true,
RejectOnMatch: true
}
],
IgnoreCase: true,
RejectOnMatch: true
},
{
Name: MatchBehaviour.AcceptOnMatch,
Matchers: [
{
Name: WildcardMatcher,
Pattern: cv-2,
IgnoreCase: true
}
],
IgnoreCase: true
},
{
Name: IgnoreCase_false,
Matchers: [
{
Name: WildcardMatcher,
Pattern: cv-3,
IgnoreCase: false
}
]
},
{
Name: IgnoreCase_true,
Matchers: [
{
Name: WildcardMatcher,
Pattern: cv-4,
IgnoreCase: true
}
],
IgnoreCase: true
},
{
Name: ExactMatcher,
Matchers: [
{
Name: ExactMatcher,
Pattern: c-exact,
IgnoreCase: false
}
]
}
]
},
Response: {},
UseWebhooksFireAndForget: false
}

View File

@@ -0,0 +1,59 @@
{
Guid: Guid_1,
UpdatedAt: DateTime_1,
Request: {
Params: [
{
Name: MatchBehaviour.RejectOnMatch,
RejectOnMatch: true
},
{
Name: MatchBehaviour.RejectOnMatch|IgnoreCase_false,
RejectOnMatch: true
},
{
Name: IgnoreCase_false,
Matchers: [
{
Name: ExactMatcher,
Pattern: pv-3a,
IgnoreCase: false
},
{
Name: ExactMatcher,
Pattern: pv-3b,
IgnoreCase: false
}
]
},
{
Name: IgnoreCase_true,
IgnoreCase: true,
Matchers: [
{
Name: ExactMatcher,
Pattern: pv-3a,
IgnoreCase: true
},
{
Name: ExactMatcher,
Pattern: pv-3b,
IgnoreCase: true
}
]
},
{
Name: ExactMatcher,
Matchers: [
{
Name: ExactMatcher,
Pattern: exact,
IgnoreCase: false
}
]
}
]
},
Response: {},
UseWebhooksFireAndForget: false
}

View File

@@ -5,6 +5,7 @@ using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using VerifyXunit;
using WireMock.Matchers;
using WireMock.Models;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
@@ -362,6 +363,60 @@ public partial class MappingConverterTests
return Verifier.Verify(model);
}
[Fact]
public Task ToMappingModel_WithHeader_And_Cookie_ReturnsCorrectModel()
{
// Assign
var request = Request.Create()
.WithHeader("MatchBehaviour.RejectOnMatch", "hv-1", MatchBehaviour.RejectOnMatch)
.WithHeader("MatchBehaviour.AcceptOnMatch", "hv-2", MatchBehaviour.AcceptOnMatch)
.WithHeader("IgnoreCase_false", "hv-3", false)
.WithHeader("IgnoreCase_true", "hv-4")
.WithHeader("ExactMatcher", new ExactMatcher("h-exact"))
.WithCookie("MatchBehaviour.RejectOnMatch", "cv-1", MatchBehaviour.RejectOnMatch)
.WithCookie("MatchBehaviour.AcceptOnMatch", "cv-2", MatchBehaviour.AcceptOnMatch)
.WithCookie("IgnoreCase_false", "cv-3", false)
.WithCookie("IgnoreCase_true", "cv-4")
.WithCookie("ExactMatcher", new ExactMatcher("c-exact"))
;
var response = Response.Create();
var mapping = new Mapping(_guid, _updatedAt, null, null, null, _settings, request, response, 0, null, null, null, null, null, false, null, data: null, probability: null);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
// Verify
return Verifier.Verify(model);
}
[Fact]
public Task ToMappingModel_WithParam_ReturnsCorrectModel()
{
// Assign
var request = Request.Create()
.WithParam("MatchBehaviour.RejectOnMatch", MatchBehaviour.RejectOnMatch)
.WithParam("MatchBehaviour.RejectOnMatch|IgnoreCase_false", false, MatchBehaviour.RejectOnMatch)
.WithParam("IgnoreCase_false", false, "pv-3a", "pv-3b")
.WithParam("IgnoreCase_true", true, "pv-3a", "pv-3b")
.WithParam("ExactMatcher", new ExactMatcher("exact"))
;
var response = Response.Create();
var mapping = new Mapping(_guid, _updatedAt, null, null, null, _settings, request, response, 0, null, null, null, null, null, false, null, data: null, probability: null);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
// Verify
return Verifier.Verify(model);
}
#if GRAPHQL
[Fact]
public Task ToMappingModel_Request_WithBodyAsGraphQLSchema_ReturnsCorrectModel()

View File

@@ -145,6 +145,26 @@ public class MatcherMapperTests
model.IgnoreCase.Should().BeTrue();
}
[Fact]
public void MatcherMapper_Map_XPathMatcher()
{
// Assign
var xmlNamespaceMap = new[]
{
new XmlNamespace { Prefix = "s", Uri = "http://schemas.xmlsoap.org/soap/envelope/" },
new XmlNamespace { Prefix = "i", Uri = "http://www.w3.org/2001/XMLSchema-instance" },
new XmlNamespace { Prefix = "q", Uri = "urn://MyWcfService" }
};
var matcher = new XPathMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.And, xmlNamespaceMap);
// Act
var model = _sut.Map(matcher)!;
// Assert
model.XmlNamespaceMap.Should().NotBeNull();
model.XmlNamespaceMap.Should().BeEquivalentTo(xmlNamespaceMap);
}
[Fact]
public void MatcherMapper_Map_MatcherModel_Null()
{
@@ -522,4 +542,47 @@ public class MatcherMapperTests
matcher.ContentTypeMatcher.Should().BeAssignableTo<ContentTypeMatcher>().Which.GetPatterns().Should().ContainSingle("text/json");
}
#endif
[Fact]
public void MatcherMapper_Map_MatcherModel_XPathMatcher_WithXmlNamespaces_As_String()
{
// Assign
var pattern = "/s:Envelope/s:Body/*[local-name()='QueryRequest']";
var model = new MatcherModel
{
Name = "XPathMatcher",
Pattern = pattern,
XmlNamespaceMap = new[]
{
new XmlNamespace { Prefix = "s", Uri = "http://schemas.xmlsoap.org/soap/envelope/" }
}
};
// Act
var matcher = (XPathMatcher)_sut.Map(model)!;
// Assert
matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch);
matcher.XmlNamespaceMap.Should().NotBeNull();
matcher.XmlNamespaceMap.Should().HaveCount(1);
}
[Fact]
public void MatcherMapper_Map_MatcherModel_XPathMatcher_WithoutXmlNamespaces_As_String()
{
// Assign
var pattern = "/s:Envelope/s:Body/*[local-name()='QueryRequest']";
var model = new MatcherModel
{
Name = "XPathMatcher",
Pattern = pattern
};
// Act
var matcher = (XPathMatcher)_sut.Map(model)!;
// Assert
matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch);
matcher.XmlNamespaceMap.Should().BeNull();
}
}

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);

View File

@@ -1,4 +1,5 @@
using FluentAssertions;
using WireMock.Matchers;
using WireMock.Util;
using Xunit;
@@ -6,13 +7,39 @@ namespace WireMock.Net.Tests.Util;
public class StringUtilsTests
{
[Theory]
[InlineData("And", MatchOperator.And)]
[InlineData("Or", MatchOperator.Or)]
public void ParseMatchOperator_ShouldReturnCorrectEnumValue_WhenValidStringIsProvided(string value, MatchOperator expected)
{
// Arrange & Act
var result = StringUtils.ParseMatchOperator(value);
// Assert
result.Should().Be(expected);
}
[Theory]
[InlineData(null, MatchOperator.Or)]
[InlineData("", MatchOperator.Or)]
[InlineData("and", MatchOperator.Or)]
[InlineData("InvalidValue", MatchOperator.Or)]
public void ParseMatchOperator_ShouldReturnDefaultEnumValue_WhenInvalidOrNullStringIsProvided(string? value, MatchOperator expected)
{
// Arrange & Act
var result = StringUtils.ParseMatchOperator(value);
// Assert
result.Should().Be(expected);
}
[Theory]
[InlineData("'s")]
[InlineData("\"s")]
public void StringUtils_TryParseQuotedString_With_UnexpectedUnclosedString_Returns_False(string input)
{
// Act
bool valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
var valid = StringUtils.TryParseQuotedString(input, out _, out _);
// Assert
valid.Should().BeFalse();
@@ -25,7 +52,7 @@ public class StringUtilsTests
public void StringUtils_TryParseQuotedString_With_InvalidStringLength_Returns_False(string input)
{
// Act
bool valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
var valid = StringUtils.TryParseQuotedString(input, out _, out _);
// Assert
valid.Should().BeFalse();
@@ -37,7 +64,7 @@ public class StringUtilsTests
public void StringUtils_TryParseQuotedString_With_InvalidStringQuoteCharacter_Returns_False(string input)
{
// Act
bool valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
var valid = StringUtils.TryParseQuotedString(input, out _, out _);
// Assert
valid.Should().BeFalse();
@@ -47,10 +74,10 @@ public class StringUtilsTests
public void StringUtils_TryParseQuotedString_With_UnexpectedUnrecognizedEscapeSequence_Returns_False()
{
// Arrange
string input = new string(new[] { '"', '\\', 'u', '?', '"' });
var input = new string(new[] { '"', '\\', 'u', '?', '"' });
// Act
bool valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
var valid = StringUtils.TryParseQuotedString(input, out _, out _);
// Assert
valid.Should().BeFalse();
@@ -64,7 +91,7 @@ public class StringUtilsTests
public void StringUtils_TryParseQuotedString_SingleQuotedString(string input, string expectedResult)
{
// Act
bool valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
var valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
// Assert
valid.Should().BeTrue();
@@ -93,7 +120,7 @@ public class StringUtilsTests
public void StringUtils_TryParseQuotedString_DoubleQuotedString(string input, string expectedResult)
{
// Act
bool valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
var valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
// Assert
valid.Should().BeTrue();

View File

@@ -3,6 +3,8 @@
<PropertyGroup>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>net452;net461;netcoreapp3.1;net6.0;net7.0</TargetFrameworks>
<LangVersion>11</LangVersion>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<DebugType>full</DebugType>
<AssemblyName>WireMock.Net.Tests</AssemblyName>
@@ -110,6 +112,9 @@
<None Update="OpenApiParser\*.yml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="cert.pem">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="responsebody.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

View File

@@ -1,9 +1,8 @@
#if !NET452
//using System;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
//using System.Net.Http.Json;
using System.Threading.Tasks;
using FluentAssertions;
using WireMock.Matchers;
@@ -21,6 +20,103 @@ public partial class WireMockServerTests
public string? Hi { get; set; }
}
[Fact]
public async Task WireMockServer_WithBodyAsJson_Using_PostAsJsonAsync_And_MultipleJmesPathMatchers_ShouldMatch()
{
// Arrange
var server = WireMockServer.Start();
server.Given(
Request.Create()
.WithPath("/a")
.WithBody(
new IMatcher[]
{
new JmesPathMatcher("requestId == '1'"),
new JmesPathMatcher("value == 'A'")
},
MatchOperator.And
)
.UsingPost()
)
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK));
server.Given(
Request.Create()
.WithPath("/a")
.WithBody(
new IMatcher[]
{
new JmesPathMatcher("requestId == '2'"),
new JmesPathMatcher("value == 'A'")
},
MatchOperator.And
)
.UsingPost()
)
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.Moved));
// Act
var requestUri = new Uri($"http://localhost:{server.Port}/a");
var json = new { requestId = "1", value = "A" };
var response = await server.CreateClient().PostAsJsonAsync(requestUri, json).ConfigureAwait(false);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
server.Stop();
}
[Fact]
public async Task WireMockServer_WithBodyAsJson_Using_PostAsJsonAsync_And_MultipleJmesPathMatchers_ShouldMatch_BestMatching()
{
// Arrange
var server = WireMockServer.Start();
server.Given(
Request.Create()
.WithPath("/a")
.WithBody(
new IMatcher[]
{
new JmesPathMatcher("extra == 'X'"),
new JmesPathMatcher("requestId == '1'"),
new JmesPathMatcher("value == 'A'")
},
MatchOperator.And
)
.UsingPost()
)
.AtPriority(1) // Higher priority
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK));
server.Given(
Request.Create()
.WithPath("/a")
.WithBody(
new IMatcher[]
{
new JmesPathMatcher("requestId == '1'"),
new JmesPathMatcher("value == 'A'")
},
MatchOperator.And
)
.UsingPost()
)
.AtPriority(2)
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.Moved));
// Act
var requestUri = new Uri($"http://localhost:{server.Port}/a");
var json = new { extra = "X", requestId = "1", value = "A" };
var response = await server.CreateClient().PostAsJsonAsync(requestUri, json).ConfigureAwait(false);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
server.Stop();
}
[Fact]
public async Task WireMockServer_WithBodyAsJson_Using_PostAsJsonAsync_And_WildcardMatcher_ShouldMatch()
{

View File

@@ -2,6 +2,7 @@ using System;
using System.Net;
using System.Threading.Tasks;
using FluentAssertions;
using WireMock.Matchers;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
@@ -26,10 +27,10 @@ public partial class WireMockServerTests
};
var server = WireMockServer.Start(settings);
server.Given(
Request.Create()
.UsingGet()
.WithPath("/foo")
.WithParam("query", queryValue)
Request.Create()
.UsingGet()
.WithPath("/foo")
.WithParam("query", queryValue)
)
.RespondWith(
Response.Create().WithStatusCode(200)
@@ -52,10 +53,10 @@ public partial class WireMockServerTests
var queryValue = "1,2,3";
var server = WireMockServer.Start();
server.Given(
Request.Create()
.UsingGet()
.WithPath("/foo")
.WithParam("query", "1", "2", "3")
Request.Create()
.UsingGet()
.WithPath("/foo")
.WithParam("query", "1", "2", "3")
)
.RespondWith(
Response.Create().WithStatusCode(200)
@@ -70,4 +71,54 @@ public partial class WireMockServerTests
server.Stop();
}
[Fact]
public async Task WireMockServer_WithParam_RejectOnMatch_OnNonMatchingParam_ShouldReturnMappingOk()
{
// Arrange
var server = WireMockServer.Start();
server.Given(
Request.Create()
.WithPath("/v1/person/workers")
.WithParam("delta_from", MatchBehaviour.RejectOnMatch)
.UsingGet()
)
.RespondWith(
Response.Create()
);
// Act
var requestUri = new Uri($"http://localhost:{server.Port}/v1/person/workers?showsourcesystem=true&count=700&page=1&sections=personal%2Corganizations%2Cemployment");
var response = await server.CreateClient().GetAsync(requestUri).ConfigureAwait(false);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
server.Stop();
}
[Fact]
public async Task WireMockServer_WithParam_AcceptOnMatch_OnNonMatchingParam_ShouldReturnMappingOk()
{
// Arrange
var server = WireMockServer.Start();
server.Given(
Request.Create()
.WithPath("/v1/person/workers")
.WithParam("delta_from")
.UsingGet()
)
.RespondWith(
Response.Create()
);
// Act
var requestUri = new Uri($"http://localhost:{server.Port}/v1/person/workers?showsourcesystem=true&count=700&page=1&sections=personal%2Corganizations%2Cemployment");
var response = await server.CreateClient().GetAsync(requestUri).ConfigureAwait(false);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
server.Stop();
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
@@ -100,6 +101,99 @@ public partial class WireMockServerTests
server.Stop();
}
#if NET461_OR_GREATER || NET6_0_OR_GREATER
[Fact]
public async Task WireMockServer_Should_Support_Https()
{
// Arrange
const string body = "example";
var path = $"/foo_{Guid.NewGuid()}";
var settings = new WireMockServerSettings
{
UseSSL = true
};
var server = WireMockServer.Start(settings);
server
.Given(Request.Create()
.WithPath(path)
.UsingGet()
)
.RespondWith(Response.Create()
.WithBody(body)
);
// Configure the HttpClient to trust self-signed certificates
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
};
using var client = new HttpClient(handler);
// Act
var result = await client.GetStringAsync($"{server.Url}{path}").ConfigureAwait(false);
// Assert
result.Should().Be(body);
server.Stop();
}
#endif
#if NET6_0_OR_GREATER
[Fact]
public async Task WireMockServer_When_HttpClientWithWebProxyCallsHttp_Should_Work_Correct()
{
// Arrange
const string body = "example";
var settings = new WireMockServerSettings
{
HostingScheme = HostingScheme.Http
};
var server = WireMockServer.Start(settings);
// The response to an HTTP CONNECT method, which is used to establish a tunnel with a proxy, should typically be a 200 OK status code if the connection is successful.
// This indicates that a tunnel has been established successfully between the client and the server via the proxy.
server
.Given(Request.Create()
.UsingConnect()
)
.RespondWith(Response.Create()
.WithBody("Connection established")
);
server
.Given(Request.Create()
.UsingGet()
)
.RespondWith(Response.Create()
.WithBody(body)
);
var httpUrl = server.Urls.First();
// Act
string result;
var currentProxy = HttpClient.DefaultProxy;
try
{
HttpClient.DefaultProxy = new WebProxy(httpUrl, false);
result = await new HttpClient().GetStringAsync(httpUrl).ConfigureAwait(false);
}
finally
{
// Revert
HttpClient.DefaultProxy = currentProxy;
}
// Assert
result.Should().Be(body);
server.Stop();
}
#endif
[Fact]
public async Task WireMockServer_Should_respond_a_redirect_without_body()
{

View File

@@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB9TCCAZugAwIBAgIUYH7UM/DAXzosxsT+ea2jdYvhqqMwCgYIKoZIzj0EAwIw
UDELMAkGA1UEBhMCTkwxEzARBgNVBAgMClNvbWUtU3RhdGUxFTATBgNVBAoMDFdp
cmVNb2NrLk5ldDEVMBMGA1UEAwwMV2lyZU1vY2suTmV0MB4XDTIyMDgxMTE2MjE0
NFoXDTMyMDYxOTE2MjE0NFowUDELMAkGA1UEBhMCTkwxEzARBgNVBAgMClNvbWUt
U3RhdGUxFTATBgNVBAoMDFdpcmVNb2NrLk5ldDEVMBMGA1UEAwwMV2lyZU1vY2su
TmV0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE39VoI268uDuIeKmRzr9e9jgM
SGeuJTvTG7+cSXmeDymrVgIGXQgmqKA8TDXpJNrRhWMd/fpsnWu1JwJUjBmspaNT
MFEwHQYDVR0OBBYEFILL8V+fAtMnccWKGAdkx2Dh/v/TMB8GA1UdIwQYMBaAFILL
8V+fAtMnccWKGAdkx2Dh/v/TMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwID
SAAwRQIgKDLAG8OWK6GF5HV4kmWz3kp2V3yVsNK2V9Lw3dSE+YsCIQCK1EEBvuqc
0ncZV4ETVnOY23PWFOMk1VwN2aoTi5n++Q==
-----END CERTIFICATE-----