Compare commits

...

20 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
Stef Heyenrath
05e2aa548b 1.5.36 2023-09-21 11:40:34 +02:00
Stef Heyenrath
b9a8ee4145 Fixed logic for SaveUnmatchedRequests (#1002)
* Fixed logic for SaveUnmatchedRequests

* fix
2023-09-21 11:38:01 +02:00
Stef Heyenrath
59aab9e1c3 Remove dependency on Microsoft.AspNet.WebApi.Client (#996)
* Using an alias for System.Net.Http.Formatting

* .

* fix

* space
2023-08-29 17:53:17 +02:00
Stef Heyenrath
93c87845c2 Write logging in case a Matcher throws an exception (#986)
* ThrowException

* ...

* .

* ...

* b

* fix test

* ...

* .

* sonar

* ft

* .

* fix tst
2023-08-21 20:07:46 +02:00
Stef Heyenrath
09a302baf2 1.5.35 2023-08-19 10:00:18 +02:00
Stef Heyenrath
f0139eb837 Update comment in JsonPathMatcher.cs 2023-08-17 17:22:05 +02:00
DayLightDancer
8adf34fb56 Update JSONPathMatcher.cs to cover the string path selection to a child (#993)
* Update JSONPathMatcher.cs to cover the string path selection to a child 

The .SelectToken method accept string path selection and JSONPath queries. The current code works only for the queries because the result is JObject. When the string path is selected the result is JValue and event with a valid result the code the code doesn't return valued result.
https://www.newtonsoft.com/json/help/html/SelectToken.htm

* Added unit tests

* Addressed the comments

* Addressed the comments

* Update JSONPathMatcher.cs
2023-08-17 17:18:32 +02:00
Stef Heyenrath
fd816f0952 Add extra unit test for WithParam multiple values comma (#992) 2023-08-12 21:05:13 +02:00
132 changed files with 3098 additions and 1454 deletions

View File

@@ -1,3 +1,32 @@
# 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)
- [#1002](https://github.com/WireMock-Net/WireMock.Net/pull/1002) - Fixed logic for SaveUnmatchedRequests [bug] contributed by [StefH](https://github.com/StefH)
- [#974](https://github.com/WireMock-Net/WireMock.Net/issues/974) - HttpClient extension methods causes ambiguous invocations in .NET 7 [bug]
- [#1001](https://github.com/WireMock-Net/WireMock.Net/issues/1001) - SaveUnmatchedRequests stopped working [bug]
# 1.5.35 (19 August 2023)
- [#992](https://github.com/WireMock-Net/WireMock.Net/pull/992) - Add extra unit test for WithParam multiple values comma [test] contributed by [StefH](https://github.com/StefH)
- [#993](https://github.com/WireMock-Net/WireMock.Net/pull/993) - Update JSONPathMatcher.cs to cover the string path selection to a child contributed by [DayLightDancer](https://github.com/DayLightDancer)
# 1.5.34 (04 August 2023)
- [#989](https://github.com/WireMock-Net/WireMock.Net/pull/989) - Fix MimeKitLite NuGet include [bug] contributed by [StefH](https://github.com/StefH)
- [#988](https://github.com/WireMock-Net/WireMock.Net/issues/988) - v1.5.33 Returns always StatusCode 500 [bug]

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>1.5.34</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.34
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

@@ -52,4 +52,4 @@ For more details see also [Docker](https://github.com/WireMock-Net/WireMock.Net-
More details on using HTTPS (SSL) can be found here [Wiki : HTTPS](https://github.com/WireMock-Net/WireMock.Net/wiki/Using-HTTPS-(SSL))
## :books: Documentation
For more info, see also this WIKI page: [What is WireMock.Net](https://github.com/WireMock-Net/WireMock.Net/wiki/What-Is-WireMock.Net).
For more info, see also this WIKI page: [What is WireMock.Net](https://github.com/WireMock-Net/WireMock.Net/wiki/What-Is-WireMock.Net).

View File

@@ -1,5 +1,7 @@
# 1.5.34 (04 August 2023)
- #989 Fix MimeKitLite NuGet include [bug]
- #988 v1.5.33 Returns always StatusCode 500 [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

@@ -22,10 +22,6 @@
<Compile Remove="__admin\mappings\1.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="__admin\mappings\array.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="log4net" Version="2.0.15" />
@@ -39,15 +35,6 @@
<None Update="nlog.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="__admin\mappings\791a3f31-6946-4ce7-8e6f-0237c7443275.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="__admin\mappings\791a3f31-6946-4ce7-8e6f-0237c7443275.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="__admin\mappings\MyXmlResponse.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,48 @@
[
{
"Guid": "12343f31-6946-4ce7-8e6f-0237c7001000",
"Title": "1",
"Request": {
"Path": {
"Matchers": [
{
"Name": "WildcardMatcher",
"Pattern": "/mappings_static_1"
}
]
},
"Methods": [
"get"
]
},
"Response": {
"BodyAsJson": { "result": "mappings static_1" },
"Headers": {
"Content-Type": "application/json"
}
}
},
{
"Guid": "12343f31-6946-4ce7-8e6f-0237c7002000",
"Title": "2",
"Request": {
"Path": {
"Matchers": [
{
"Name": "WildcardMatcher",
"Pattern": "/mappings_static_2"
}
]
},
"Methods": [
"get"
]
},
"Response": {
"BodyAsJson": { "result": "mappings static_2" },
"Headers": {
"Content-Type": "application/json"
}
}
}
]

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

@@ -137,6 +137,7 @@ namespace WireMock.Net.ConsoleApplication
Urls = new[] { url1, url2, url3 },
StartAdminInterface = true,
ReadStaticMappings = true,
SaveUnmatchedRequests = true,
WatchStaticMappings = true,
WatchStaticMappingsInSubdirectories = true,
//ProxyAndRecordSettings = new ProxyAndRecordSettings

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

@@ -1,21 +1,25 @@
using System;
using System;
namespace WireMock.Admin.Mappings
namespace WireMock.Admin.Mappings;
/// <summary>
/// Status
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class StatusModel
{
/// <summary>
/// Status
/// The optional guid.
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class StatusModel
{
/// <summary>
/// The optional guid.
/// </summary>
public Guid? Guid { get; set; }
public Guid? Guid { get; set; }
/// <summary>
/// The status (can also contain the error message).
/// </summary>
public string Status { get; set; }
}
/// <summary>
/// The status.
/// </summary>
public string? Status { get; set; }
/// <summary>
/// The error message.
/// </summary>
public string? Error { get; set; }
}

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

@@ -62,11 +62,6 @@ public class SettingsModel
/// </summary>
public bool? HandleRequestsSynchronously { get; set; }
/// <summary>
/// Throw an exception when the Matcher fails because of invalid input. (default set to <c>false</c>).
/// </summary>
public bool? ThrowExceptionWhenMatcherFails { get; set; }
/// <summary>
/// Use the RegexExtended instead of the default <see cref="Regex"/>. (default set to <c>true</c>).
/// </summary>

View File

@@ -1,121 +1,120 @@
using JetBrains.Annotations;
using System.Collections.Generic;
namespace WireMock.Handlers
namespace WireMock.Handlers;
/// <summary>
/// Handler to interact with the file system to handle folders and read and write (static mapping) files.
/// </summary>
public interface IFileSystemHandler
{
/// <summary>
/// Handler to interact with the file system to handle folders and read and write (static mapping) files.
/// Gets the folder where the static mappings are located. For local file system, this would be `{CurrentFolder}/__admin/mappings`.
/// </summary>
public interface IFileSystemHandler
{
/// <summary>
/// Gets the folder where the static mappings are located. For local file system, this would be `{CurrentFolder}/__admin/mappings`.
/// </summary>
/// <returns>The folder name.</returns>
string GetMappingFolder();
/// <returns>The folder name.</returns>
string GetMappingFolder();
/// <summary>
/// Determines whether the given path refers to an existing directory on disk.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>true if path refers to an existing directory; false if the directory does not exist or an error occurs when trying to determine if the specified directory exists.</returns>
bool FolderExists([NotNull] string path);
/// <summary>
/// Determines whether the given path refers to an existing directory on disk.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>true if path refers to an existing directory; false if the directory does not exist or an error occurs when trying to determine if the specified directory exists.</returns>
bool FolderExists([NotNull] string path);
/// <summary>
/// Creates all directories and subdirectories in the specified path unless they already exist.
/// </summary>
/// <param name="path">The path.</param>
void CreateFolder([NotNull] string path);
/// <summary>
/// Creates all directories and subdirectories in the specified path unless they already exist.
/// </summary>
/// <param name="path">The path.</param>
void CreateFolder([NotNull] string path);
/// <summary>
/// Returns an enumerable collection of file names in a specified path.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="includeSubdirectories">A value indicating whether subdirectories should also included when enumerating files.</param>
/// <returns>An enumerable collection of the full names (including paths) for the files in the directory (and optionally subdirectories) specified by path.</returns>
IEnumerable<string> EnumerateFiles([NotNull] string path, bool includeSubdirectories);
/// <summary>
/// Returns an enumerable collection of file names in a specified path.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="includeSubdirectories">A value indicating whether subdirectories should also included when enumerating files.</param>
/// <returns>An enumerable collection of the full names (including paths) for the files in the directory (and optionally subdirectories) specified by path.</returns>
IEnumerable<string> EnumerateFiles([NotNull] string path, bool includeSubdirectories);
/// <summary>
/// Read a static mapping file as text.
/// </summary>
/// <param name="path">The path (folder + filename with .json extension).</param>
/// <returns>The file content as text.</returns>
string ReadMappingFile([NotNull] string path);
/// <summary>
/// Read a static mapping file as text.
/// </summary>
/// <param name="path">The path (folder + filename with .json extension).</param>
/// <returns>The file content as text.</returns>
string ReadMappingFile([NotNull] string path);
/// <summary>
/// Write the static mapping file.
/// </summary>
/// <param name="path">The path (folder + filename with .json extension).</param>
/// <param name="text">The text.</param>
void WriteMappingFile([NotNull] string path, [NotNull] string text);
/// <summary>
/// Write the static mapping file.
/// </summary>
/// <param name="path">The path (folder + filename with .json extension).</param>
/// <param name="text">The text.</param>
void WriteMappingFile([NotNull] string path, [NotNull] string text);
/// <summary>
/// Read a response body file as byte[].
/// </summary>
/// <param name="path">The path or filename from the file to read.</param>
/// <returns>The file content as bytes.</returns>
byte[] ReadResponseBodyAsFile([NotNull] string path);
/// <summary>
/// Read a response body file as byte[].
/// </summary>
/// <param name="path">The path or filename from the file to read.</param>
/// <returns>The file content as bytes.</returns>
byte[] ReadResponseBodyAsFile([NotNull] string path);
/// <summary>
/// Read a response body file as text.
/// </summary>
/// <param name="path">The path or filename from the file to read.</param>
/// <returns>The file content as text.</returns>
string ReadResponseBodyAsString([NotNull] string path);
/// <summary>
/// Read a response body file as text.
/// </summary>
/// <param name="path">The path or filename from the file to read.</param>
/// <returns>The file content as text.</returns>
string ReadResponseBodyAsString([NotNull] string path);
/// <summary>
/// Delete a file.
/// </summary>
/// <param name="filename">The filename.</param>
void DeleteFile([NotNull] string filename);
/// <summary>
/// Delete a file.
/// </summary>
/// <param name="filename">The filename.</param>
void DeleteFile([NotNull] string filename);
/// <summary>
/// Determines whether the given path refers to an existing file on disk.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>true if path refers to an existing file; false if the file does not exist.</returns>
bool FileExists([NotNull] string filename);
/// <summary>
/// Determines whether the given path refers to an existing file on disk.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>true if path refers to an existing file; false if the file does not exist.</returns>
bool FileExists([NotNull] string filename);
/// <summary>
/// Write a file.
/// </summary>
/// <param name="filename">The filename.</param>
/// <param name="bytes">The bytes.</param>
void WriteFile([NotNull] string filename, [NotNull] byte[] bytes);
/// <summary>
/// Write a file.
/// </summary>
/// <param name="filename">The filename.</param>
/// <param name="bytes">The bytes.</param>
void WriteFile([NotNull] string filename, [NotNull] byte[] bytes);
/// <summary>
/// Write a file.
/// </summary>
/// <param name="folder">The folder.</param>
/// <param name="filename">The filename.</param>
/// <param name="bytes">The bytes.</param>
void WriteFile([NotNull] string folder, [NotNull] string filename, [NotNull] byte[] bytes);
/// <summary>
/// Write a file.
/// </summary>
/// <param name="folder">The folder.</param>
/// <param name="filename">The filename.</param>
/// <param name="bytes">The bytes.</param>
void WriteFile([NotNull] string folder, [NotNull] string filename, [NotNull] byte[] bytes);
/// <summary>
/// Read a file as bytes.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>The file content as bytes.</returns>
byte[] ReadFile([NotNull] string filename);
/// <summary>
/// Read a file as bytes.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>The file content as bytes.</returns>
byte[] ReadFile([NotNull] string filename);
/// <summary>
/// Read a file as string.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>The file content as a string.</returns>
string ReadFileAsString([NotNull] string filename);
/// <summary>
/// Read a file as string.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>The file content as a string.</returns>
string ReadFileAsString([NotNull] string filename);
/// <summary>
/// Gets the folder where the unmatched requests should be stored. For local file system, this would be `{CurrentFolder}/requests/unmatched`.
/// </summary>
/// <returns>The folder name.</returns>
string GetUnmatchedRequestsFolder();
/// <summary>
/// Gets the folder where the unmatched requests should be stored. For local file system, this would be `{CurrentFolder}/requests/unmatched`.
/// </summary>
/// <returns>The folder name.</returns>
string GetUnmatchedRequestsFolder();
/// <summary>
/// Write a unmatched request to the Unmatched RequestsFolder.
/// </summary>
/// <param name="filename">The filename.</param>
/// <param name="text">The text.</param>
void WriteUnmatchedRequest([NotNull] string filename, [NotNull] string text);
}
/// <summary>
/// Write a unmatched request to the Unmatched RequestsFolder.
/// </summary>
/// <param name="filename">The filename.</param>
/// <param name="text">The text.</param>
void WriteUnmatchedRequest([NotNull] string filename, [NotNull] string text);
}

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

@@ -2,65 +2,63 @@ using System;
using JetBrains.Annotations;
using WireMock.Admin.Requests;
namespace WireMock.Logging
namespace WireMock.Logging;
/// <summary>
/// IWireMockLogger interface
/// </summary>
[PublicAPI]
public interface IWireMockLogger
{
/// <summary>
/// IWireMockLogger interface
/// Writes the message at the Debug level using the specified parameters.
/// </summary>
/// <param name="formatString">The format string.</param>
/// <param name="args">The arguments.</param>
[PublicAPI]
public interface IWireMockLogger
{
/// <summary>
/// Writes the message at the Debug level using the specified parameters.
/// </summary>
/// <param name="formatString">The format string.</param>
/// <param name="args">The arguments.</param>
[PublicAPI]
[StringFormatMethod("formatString")]
void Debug(string formatString, params object[] args);
[StringFormatMethod("formatString")]
void Debug(string formatString, params object[] args);
/// <summary>
/// Writes the message at the Info level using the specified parameters.
/// </summary>
/// <param name="formatString">The format string.</param>
/// <param name="args">The arguments.</param>
[PublicAPI]
[StringFormatMethod("formatString")]
void Info(string formatString, params object[] args);
/// <summary>
/// Writes the message at the Info level using the specified parameters.
/// </summary>
/// <param name="formatString">The format string.</param>
/// <param name="args">The arguments.</param>
[PublicAPI]
[StringFormatMethod("formatString")]
void Info(string formatString, params object[] args);
/// <summary>
/// Writes the message at the Warning level using the specified parameters.
/// </summary>
/// <param name="formatString">The format string.</param>
/// <param name="args">The arguments.</param>
[PublicAPI]
[StringFormatMethod("formatString")]
void Warn(string formatString, params object[] args);
/// <summary>
/// Writes the message at the Warning level using the specified parameters.
/// </summary>
/// <param name="formatString">The format string.</param>
/// <param name="args">The arguments.</param>
[PublicAPI]
[StringFormatMethod("formatString")]
void Warn(string formatString, params object[] args);
/// <summary>
/// Writes the message at the Error level using the specified parameters.
/// </summary>
/// <param name="formatString">The format string.</param>
/// <param name="args">The arguments.</param>
[PublicAPI]
[StringFormatMethod("formatString")]
void Error(string formatString, params object[] args);
/// <summary>
/// Writes the message at the Error level using the specified parameters.
/// </summary>
/// <param name="formatString">The format string.</param>
/// <param name="args">The arguments.</param>
[PublicAPI]
[StringFormatMethod("formatString")]
void Error(string formatString, params object[] args);
/// <summary>
/// Writes the message at the Error level using the specified exception.
/// </summary>
/// <param name="formatString">The format string.</param>
/// <param name="exception">The exception.</param>
[PublicAPI]
[StringFormatMethod("formatString")]
void Error(string formatString, Exception exception);
/// <summary>
/// Writes the message at the Error level using the specified exception.
/// </summary>
/// <param name="formatString">The format string.</param>
/// <param name="exception">The exception.</param>
[PublicAPI]
void Error(string formatString, Exception exception);
/// <summary>
/// Writes the LogEntryModel (LogRequestModel, LogResponseModel and more).
/// </summary>
/// <param name="logEntryModel">The Request Log Model.</param>
/// <param name="isAdminRequest">Defines if this request is an admin request.</param>
[PublicAPI]
void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest);
}
/// <summary>
/// Writes the LogEntryModel (LogRequestModel, LogResponseModel and more).
/// </summary>
/// <param name="logEntryModel">The Request Log Model.</param>
/// <param name="isAdminRequest">Defines if this request is an admin request.</param>
[PublicAPI]
void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest);
}

View File

@@ -1,56 +1,56 @@
using System;
using System.Collections.Generic;
namespace WireMock.Matchers.Request
namespace WireMock.Matchers.Request;
/// <summary>
/// IRequestMatchResult
/// </summary>
public interface IRequestMatchResult : IComparable
{
/// <summary>
/// IRequestMatchResult
/// Gets the match percentage.
/// </summary>
public interface IRequestMatchResult : IComparable
{
/// <summary>
/// Gets the match percentage.
/// </summary>
/// <value>
/// The match percentage.
/// </value>
double AverageTotalScore { get; }
/// <value>
/// The match percentage.
/// </value>
double AverageTotalScore { get; }
/// <summary>
/// Gets or sets a value indicating whether this instance is perfect match.
/// </summary>
/// <value>
/// <c>true</c> if this instance is perfect match; otherwise, <c>false</c>.
/// </value>
bool IsPerfectMatch { get; }
/// <summary>
/// Gets or sets a value indicating whether this instance is perfect match.
/// </summary>
/// <value>
/// <c>true</c> if this instance is perfect match; otherwise, <c>false</c>.
/// </value>
bool IsPerfectMatch { get; }
/// <summary>
/// Gets the match details.
/// </summary>
IList<MatchDetail> MatchDetails { get; }
/// <summary>
/// Gets the match details.
/// </summary>
IList<MatchDetail> MatchDetails { get; }
/// <summary>
/// Gets or sets the total number of matches.
/// </summary>
/// <value>
/// The total number of matches.
/// </value>
int TotalNumber { get; }
/// <summary>
/// Gets or sets the total number of matches.
/// </summary>
/// <value>
/// The total number of matches.
/// </value>
int TotalNumber { get; }
/// <summary>
/// Gets or sets the match-score.
/// </summary>
/// <value>
/// The match-score.
/// </value>
double TotalScore { get; }
/// <summary>
/// Gets or sets the match-score.
/// </summary>
/// <value>
/// The match-score.
/// </value>
double TotalScore { get; }
/// <summary>
/// Adds the score.
/// </summary>
/// <param name="matcherType">The matcher Type.</param>
/// <param name="score">The score.</param>
/// <returns>The score.</returns>
double AddScore(Type matcherType, double score);
}
/// <summary>
/// Adds the score.
/// </summary>
/// <param name="matcherType">The matcher Type.</param>
/// <param name="score">The score.</param>
/// <param name="exception">The exception [Optional].</param>
/// <returns>The score.</returns>
double AddScore(Type matcherType, double score, Exception? exception);
}

View File

@@ -1,20 +1,25 @@
using System;
using System;
namespace WireMock.Matchers.Request
namespace WireMock.Matchers.Request;
/// <summary>
/// MatchDetail
/// </summary>
public class MatchDetail
{
/// <summary>
/// MatchDetail
/// Gets or sets the type of the matcher.
/// </summary>
public class MatchDetail
{
/// <summary>
/// Gets or sets the type of the matcher.
/// </summary>
public Type MatcherType { get; set; }
public Type MatcherType { get; set; } = null!;
/// <summary>
/// Gets or sets the score between 0.0 and 1.0
/// </summary>
public double Score { get; set; }
}
/// <summary>
/// Gets or sets the score between 0.0 and 1.0
/// </summary>
public double Score { get; set; }
/// <summary>
/// The exception in case the Matcher throws exception.
/// [Optional]
/// </summary>
public Exception? Exception { get; set; }
}

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

@@ -30,11 +30,9 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher
"Newtonsoft.Json.Linq"
};
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc cref="IMatcher.ThrowException"/>
public bool ThrowException { get; }
private readonly AnyOf<string, StringPattern>[] _patterns;
/// <summary>
@@ -55,37 +53,44 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher
{
_patterns = Guard.NotNull(patterns);
MatchBehaviour = matchBehaviour;
ThrowException = false;
MatchOperator = matchOperator;
}
public double IsMatch(string? input)
public MatchResult IsMatch(string? input)
{
return IsMatchInternal(input);
}
public double IsMatch(object? input)
public MatchResult IsMatch(object? input)
{
return IsMatchInternal(input);
}
public double IsMatchInternal(object? input)
public MatchResult IsMatchInternal(object? input)
{
double match = MatchScores.Mismatch;
var score = MatchScores.Mismatch;
Exception? exception = null;
if (input != null)
{
match = MatchScores.ToScore(_patterns.Select(pattern => IsMatch(input, pattern.GetPattern())).ToArray(), MatchOperator);
try
{
score = MatchScores.ToScore(_patterns.Select(pattern => IsMatch(input, pattern.GetPattern())).ToArray(), MatchOperator);
}
catch (Exception ex)
{
exception = ex;
}
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
private bool IsMatch(dynamic input, string pattern)
{
bool isMatchWithString = input is string;
var isMatchWithString = input is string;
var inputValue = isMatchWithString ? input : JObject.FromObject(input);
string source = GetSourceForIsMatchWithString(pattern, isMatchWithString);
var source = GetSourceForIsMatchWithString(pattern, isMatchWithString);
object? result;
@@ -155,7 +160,7 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher
}
#elif (NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP3_1 || NET5_0 || NET6_0 || NET7_0)
Assembly assembly;
Assembly assembly;
try
{
assembly = CSScriptLib.CSScript.Evaluator.CompileCode(source);
@@ -198,10 +203,10 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher
private string GetSourceForIsMatchWithString(string pattern, bool isMatchWithString)
{
string template = isMatchWithString ? TemplateForIsMatchWithString : TemplateForIsMatchWithDynamic;
var template = isMatchWithString ? TemplateForIsMatchWithString : TemplateForIsMatchWithDynamic;
var stringBuilder = new StringBuilder();
foreach (string @using in _usings)
foreach (var @using in _usings)
{
stringBuilder.AppendLine($"using {@using};");
}
@@ -211,7 +216,7 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher
return stringBuilder.ToString();
}
/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
/// <inheritdoc />
public AnyOf<string, StringPattern>[] GetPatterns()
{
return _patterns;
@@ -220,6 +225,6 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher
/// <inheritdoc />
public MatchOperator MatchOperator { get; }
/// <inheritdoc cref="IMatcher.Name"/>
public string Name => "CSharpCodeMatcher";
/// <inheritdoc />
public string Name => nameof(CSharpCodeMatcher);
}

View File

@@ -19,12 +19,15 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\WireMock.Net\Http\HttpClientFactory2.cs" Link="Http\HttpClientFactory2.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="IsExternalInit" Version="1.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
<PackageReference Include="Stef.Validation" Version="0.1.1" />
<PackageReference Include="Testcontainers" Version="3.2.0" />
<PackageReference Include="JetBrains.Annotations" VersionOverride="2022.3.1" PrivateAssets="All" Version="2022.3.1" />
@@ -33,4 +36,8 @@
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.RestClient\WireMock.Net.RestClient.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Http\" />
</ItemGroup>
</Project>

View File

@@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging;
using RestEase;
using Stef.Validation;
using WireMock.Client;
using WireMock.Http;
namespace WireMock.Net.Testcontainers;
@@ -71,7 +72,7 @@ public sealed class WireMockContainer : DockerContainer
{
ValidateIfRunning();
var client = HttpClientFactory.Create(handlers);
var client = HttpClientFactory2.Create(handlers);
client.BaseAddress = GetPublicUri();
return client;
}
@@ -93,7 +94,7 @@ public sealed class WireMockContainer : DockerContainer
{
ValidateIfRunning();
var client = HttpClientFactory.Create(innerHandler, handlers);
var client = HttpClientFactory2.Create(innerHandler, handlers);
client.BaseAddress = GetPublicUri();
return client;
}

View File

@@ -34,8 +34,6 @@ internal class AzureADAuthenticationMatcher : IStringMatcher
public MatchBehaviour MatchBehaviour => MatchBehaviour.AcceptOnMatch;
public bool ThrowException => false;
public AnyOf<string, StringPattern>[] GetPatterns()
{
return EmptyArray<AnyOf<string, StringPattern>>.Value;
@@ -43,7 +41,7 @@ internal class AzureADAuthenticationMatcher : IStringMatcher
public MatchOperator MatchOperator { get; } = MatchOperator.Or;
public double IsMatch(string? input)
public MatchResult IsMatch(string? input)
{
if (string.IsNullOrEmpty(input))
{
@@ -70,9 +68,9 @@ internal class AzureADAuthenticationMatcher : IStringMatcher
return MatchScores.Perfect;
}
catch
catch (Exception ex)
{
return MatchScores.Mismatch;
return new MatchResult(MatchScores.Mismatch, ex);
}
}
}

View File

@@ -8,4 +8,6 @@ internal static class WireMockConstants
public const string ContentTypeJson = "application/json";
public const string ContentTypeTextPlain = "text/plain";
public const string NoMatchingFound = "No matching mapping found";
}

View File

@@ -0,0 +1,16 @@
using System;
namespace WireMock.Extensions;
internal static class ExceptionExtensions
{
public static Exception? ToException(this Exception[] exceptions)
{
return exceptions.Length switch
{
1 => exceptions[0],
> 1 => new AggregateException(exceptions),
_ => null
};
}
}

View File

@@ -82,7 +82,7 @@ public class LocalFileSystemHandler : IFileSystemHandler
public virtual byte[] ReadResponseBodyAsFile(string path)
{
Guard.NotNullOrEmpty(path);
path = PathUtils.CleanPath(path);
path = PathUtils.CleanPath(path)!;
// If the file exists at the given path relative to the MappingsFolder, then return that.
// Else the path will just be as-is.
return File.ReadAllBytes(File.Exists(PathUtils.Combine(GetMappingFolder(), path)) ? PathUtils.Combine(GetMappingFolder(), path) : path);
@@ -92,7 +92,7 @@ public class LocalFileSystemHandler : IFileSystemHandler
public virtual string ReadResponseBodyAsString(string path)
{
Guard.NotNullOrEmpty(path);
path = PathUtils.CleanPath(path);
path = PathUtils.CleanPath(path)!;
// In case the path is a filename, the path will be adjusted to the MappingFolder.
// Else the path will just be as-is.
return File.ReadAllText(File.Exists(PathUtils.Combine(GetMappingFolder(), path)) ? PathUtils.Combine(GetMappingFolder(), path) : path);

View File

@@ -1,3 +1,4 @@
using System.Linq;
using System.Net.Http;
namespace WireMock.Http;
@@ -6,19 +7,31 @@ internal static class HttpClientFactory2
{
public static HttpClient Create(params DelegatingHandler[] handlers)
{
#if NETSTANDARD1_3
return new HttpClient();
#else
return HttpClientFactory.Create(handlers);
#endif
var handler = CreateHandlerPipeline(new HttpClientHandler(), handlers);
return new HttpClient(handler);
}
public static HttpClient Create(HttpMessageHandler innerHandler, params DelegatingHandler[] handlers)
{
#if NETSTANDARD1_3
return new HttpClient(innerHandler);
#else
return HttpClientFactory.Create(innerHandler, handlers);
#endif
var handler = CreateHandlerPipeline(innerHandler, handlers);
return new HttpClient(handler);
}
private static HttpMessageHandler CreateHandlerPipeline(HttpMessageHandler handler, params DelegatingHandler[] delegatingHandlers)
{
if (delegatingHandlers.Length == 0)
{
return handler;
}
var next = handler;
foreach (var delegatingHandler in delegatingHandlers.Reverse())
{
delegatingHandler.InnerHandler = next;
next = delegatingHandler;
}
return next;
}
}

View File

@@ -139,13 +139,13 @@ public class Mapping : IMapping
Probability = probability;
}
/// <inheritdoc cref="IMapping.ProvideResponseAsync" />
/// <inheritdoc />
public Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IRequestMessage requestMessage)
{
return Provider.ProvideResponseAsync(this, requestMessage, Settings);
}
/// <inheritdoc cref="IMapping.GetRequestMatchResult" />
/// <inheritdoc />
public IRequestMatchResult GetRequestMatchResult(IRequestMessage requestMessage, string? nextState)
{
var result = new RequestMatchResult();

View File

@@ -20,10 +20,8 @@ public abstract class AbstractJsonPartialMatcher : JsonMatcher
/// </summary>
/// <param name="value">The string value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="regex">Support Regex.</param>
protected AbstractJsonPartialMatcher(string value, bool ignoreCase = false, bool throwException = false, bool regex = false)
: base(value, ignoreCase, throwException)
protected AbstractJsonPartialMatcher(string value, bool ignoreCase = false, bool regex = false) : base(value, ignoreCase)
{
Regex = regex;
}
@@ -33,10 +31,8 @@ public abstract class AbstractJsonPartialMatcher : JsonMatcher
/// </summary>
/// <param name="value">The object value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="regex">Support Regex.</param>
protected AbstractJsonPartialMatcher(object value, bool ignoreCase = false, bool throwException = false, bool regex = false)
: base(value, ignoreCase, throwException)
protected AbstractJsonPartialMatcher(object value, bool ignoreCase = false, bool regex = false) : base(value, ignoreCase)
{
Regex = regex;
}
@@ -47,10 +43,8 @@ public abstract class AbstractJsonPartialMatcher : JsonMatcher
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="value">The value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="regex">Support Regex.</param>
protected AbstractJsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool throwException = false, bool regex = false)
: base(matchBehaviour, value, ignoreCase, throwException)
protected AbstractJsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false) : base(matchBehaviour, value, ignoreCase)
{
Regex = regex;
}
@@ -66,7 +60,7 @@ public abstract class AbstractJsonPartialMatcher : JsonMatcher
if (Regex && value.Type == JTokenType.String && input != null)
{
var valueAsString = value.ToString();
var (valid, result) = RegexUtils.MatchRegex(valueAsString, input.ToString());
if (valid)
{
@@ -75,10 +69,10 @@ public abstract class AbstractJsonPartialMatcher : JsonMatcher
}
if (input != null &&
((value.Type == JTokenType.Guid && input.Type == JTokenType.String) ||
(value.Type == JTokenType.String && input.Type == JTokenType.Guid)))
((value.Type == JTokenType.Guid && input.Type == JTokenType.String) ||
(value.Type == JTokenType.String && input.Type == JTokenType.Guid)))
{
return IsMatch(value.ToString(), input.ToString());
return IsMatch(value.ToString(), input.ToString());
}
if (input == null || value.Type != input.Type)

View File

@@ -46,15 +46,13 @@ public class ContentTypeMatcher : WildcardMatcher
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="patterns">The patterns.</param>
/// <param name="ignoreCase">IgnoreCase (default false)</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
public ContentTypeMatcher(MatchBehaviour matchBehaviour, AnyOf<string, StringPattern>[] patterns, bool ignoreCase = false, bool throwException = false) :
base(matchBehaviour, patterns, ignoreCase, throwException)
public ContentTypeMatcher(MatchBehaviour matchBehaviour, AnyOf<string, StringPattern>[] patterns, bool ignoreCase = false) : base(matchBehaviour, patterns, ignoreCase)
{
_patterns = patterns;
}
/// <inheritdoc cref="RegexMatcher.IsMatch"/>
public override double IsMatch(string? input)
/// <inheritdoc />
public override MatchResult IsMatch(string? input)
{
if (string.IsNullOrEmpty(input) || !MediaTypeHeaderValue.TryParse(input, out var contentType))
{
@@ -64,12 +62,12 @@ public class ContentTypeMatcher : WildcardMatcher
return base.IsMatch(contentType.MediaType);
}
/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
/// <inheritdoc />
public override AnyOf<string, StringPattern>[] GetPatterns()
{
return _patterns;
}
/// <inheritdoc cref="IMatcher.Name"/>
public override string Name => "ContentTypeMatcher";
/// <inheritdoc />
public override string Name => nameof(ContentTypeMatcher);
}

View File

@@ -14,17 +14,14 @@ public class ExactMatcher : IStringMatcher, IIgnoreCaseMatcher
{
private readonly AnyOf<string, StringPattern>[] _values;
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc cref="IMatcher.ThrowException"/>
public bool ThrowException { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ExactMatcher"/> class.
/// </summary>
/// <param name="values">The values.</param>
public ExactMatcher(params AnyOf<string, StringPattern>[] values) : this(MatchBehaviour.AcceptOnMatch, false, false, MatchOperator.Or, values)
public ExactMatcher(params AnyOf<string, StringPattern>[] values) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, values)
{
}
@@ -33,7 +30,7 @@ public class ExactMatcher : IStringMatcher, IIgnoreCaseMatcher
/// </summary>
/// <param name="ignoreCase">Ignore the case from the pattern(s).</param>
/// <param name="values">The values.</param>
public ExactMatcher(bool ignoreCase, params AnyOf<string, StringPattern>[] values) : this(MatchBehaviour.AcceptOnMatch, ignoreCase, false, MatchOperator.Or, values)
public ExactMatcher(bool ignoreCase, params AnyOf<string, StringPattern>[] values) : this(MatchBehaviour.AcceptOnMatch, ignoreCase, MatchOperator.Or, values)
{
}
@@ -42,36 +39,33 @@ public class ExactMatcher : IStringMatcher, IIgnoreCaseMatcher
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="ignoreCase">Ignore the case from the pattern(s).</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
/// <param name="values">The values.</param>
public ExactMatcher(
MatchBehaviour matchBehaviour,
bool ignoreCase = false,
bool throwException = false,
MatchOperator matchOperator = MatchOperator.Or,
params AnyOf<string, StringPattern>[] values)
{
_values = Guard.NotNull(values);
MatchBehaviour = matchBehaviour;
ThrowException = throwException;
IgnoreCase = ignoreCase;
MatchOperator = matchOperator;
}
/// <inheritdoc cref="IStringMatcher.IsMatch"/>
public double IsMatch(string? input)
/// <inheritdoc />
public MatchResult IsMatch(string? input)
{
Func<string?, bool> equals = IgnoreCase
? pattern => string.Equals(pattern, input, StringComparison.OrdinalIgnoreCase)
: pattern => pattern == input;
double score = MatchScores.ToScore(_values.Select(v => equals(v)).ToArray(), MatchOperator);
return MatchBehaviourHelper.Convert(MatchBehaviour, score);
var score = MatchScores.ToScore(_values.Select(v => equals(v)).ToArray(), MatchOperator);
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score));
}
/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
/// <inheritdoc />
public AnyOf<string, StringPattern>[] GetPatterns()
{
return _values;
@@ -80,7 +74,7 @@ public class ExactMatcher : IStringMatcher, IIgnoreCaseMatcher
/// <inheritdoc />
public MatchOperator MatchOperator { get; }
/// <inheritdoc cref="IMatcher.Name"/>
/// <inheritdoc />
public string Name => "ExactMatcher";
/// <inheritdoc />

View File

@@ -19,12 +19,9 @@ public class ExactObjectMatcher : IObjectMatcher
/// </summary>
public byte[]? ValueAsBytes { get; }
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc cref="IMatcher.ThrowException"/>
public bool ThrowException { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ExactObjectMatcher"/> class.
/// </summary>
@@ -56,17 +53,15 @@ public class ExactObjectMatcher : IObjectMatcher
/// Initializes a new instance of the <see cref="ExactObjectMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="value">The value.</param>
public ExactObjectMatcher(MatchBehaviour matchBehaviour, byte[] value, bool throwException = false)
public ExactObjectMatcher(MatchBehaviour matchBehaviour, byte[] value)
{
ValueAsBytes = Guard.NotNull(value);
MatchBehaviour = matchBehaviour;
ThrowException = throwException;
}
/// <inheritdoc cref="IObjectMatcher.IsMatch"/>
public double IsMatch(object? input)
/// <inheritdoc />
public MatchResult IsMatch(object? input)
{
bool equals = false;
if (ValueAsObject != null)
@@ -81,6 +76,6 @@ public class ExactObjectMatcher : IObjectMatcher
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(equals));
}
/// <inheritdoc cref="IMatcher.Name"/>
/// <inheritdoc />
public string Name => "ExactObjectMatcher";
}

View File

@@ -1,12 +1,14 @@
#if GRAPHQL
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using AnyOfTypes;
using GraphQL;
using GraphQL.Types;
using Newtonsoft.Json;
using Stef.Validation;
using WireMock.Extensions;
using WireMock.Models;
namespace WireMock.Matchers;
@@ -19,8 +21,10 @@ public class GraphQLMatcher : IStringMatcher
{
private sealed class GraphQLRequest
{
// ReSharper disable once UnusedAutoPropertyAccessor.Local
public string? Query { get; set; }
// ReSharper disable once UnusedAutoPropertyAccessor.Local
public Dictionary<string, object?>? Variables { get; set; }
}
@@ -31,21 +35,16 @@ public class GraphQLMatcher : IStringMatcher
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc />
public bool ThrowException { get; }
/// <summary>
/// Initializes a new instance of the <see cref="LinqMatcher"/> class.
/// </summary>
/// <param name="schema">The schema.</param>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
public GraphQLMatcher(AnyOf<string, StringPattern, ISchema> schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, bool throwException = false, MatchOperator matchOperator = MatchOperator.Or)
public GraphQLMatcher(AnyOf<string, StringPattern, ISchema> schema, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.Or)
{
Guard.NotNull(schema);
MatchBehaviour = matchBehaviour;
ThrowException = throwException;
MatchOperator = matchOperator;
var patterns = new List<AnyOf<string, StringPattern>>();
@@ -72,51 +71,44 @@ public class GraphQLMatcher : IStringMatcher
}
/// <inheritdoc />
public double IsMatch(string? input)
public MatchResult IsMatch(string? input)
{
var match = MatchScores.Mismatch;
var score = MatchScores.Mismatch;
Exception? exception = null;
try
if (input != null && TryGetGraphQLRequest(input, out var graphQLRequest))
{
var graphQLRequest = JsonConvert.DeserializeObject<GraphQLRequest>(input!)!;
var executionResult = new DocumentExecuter().ExecuteAsync(_ =>
try
{
_.ThrowOnUnhandledException = true;
_.Schema = _schema;
_.Query = graphQLRequest.Query;
if (graphQLRequest.Variables != null)
var executionResult = new DocumentExecuter().ExecuteAsync(_ =>
{
_.Variables = new Inputs(graphQLRequest.Variables);
}
}).GetAwaiter().GetResult();
_.ThrowOnUnhandledException = true;
if (executionResult.Errors == null || executionResult.Errors.Count == 0)
{
match = MatchScores.Perfect;
}
else
{
var exceptions = executionResult.Errors.OfType<Exception>().ToArray();
if (exceptions.Length == 1)
_.Schema = _schema;
_.Query = graphQLRequest.Query;
if (graphQLRequest.Variables != null)
{
_.Variables = new Inputs(graphQLRequest.Variables);
}
}).GetAwaiter().GetResult();
if (executionResult.Errors == null || executionResult.Errors.Count == 0)
{
throw exceptions[0];
score = MatchScores.Perfect;
}
else
{
exception = executionResult.Errors.OfType<Exception>().ToArray().ToException();
}
throw new AggregateException(exceptions);
}
}
catch
{
if (ThrowException)
catch (Exception ex)
{
throw;
exception = ex;
}
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
/// <inheritdoc />
@@ -131,9 +123,28 @@ public class GraphQLMatcher : IStringMatcher
/// <inheritdoc />
public string Name => nameof(GraphQLMatcher);
private static ISchema BuildSchema(string schema)
private static bool TryGetGraphQLRequest(string input, [NotNullWhen(true)] out GraphQLRequest? graphQLRequest)
{
return Schema.For(schema);
try
{
graphQLRequest = JsonConvert.DeserializeObject<GraphQLRequest>(input);
return graphQLRequest != null;
}
catch
{
graphQLRequest = default;
return false;
}
}
private static ISchema BuildSchema(string typeDefinitions)
{
var schema = Schema.For(typeDefinitions);
// #984
schema.RegisterTypes(schema.BuiltInTypeMappings.Select(x => x.graphType).ToArray());
return schema;
}
}
#endif

View File

@@ -6,7 +6,7 @@ namespace WireMock.Matchers.Helpers;
internal static class BodyDataMatchScoreCalculator
{
public static double CalculateMatchScore(IBodyData? requestMessage, IMatcher matcher)
public static MatchResult CalculateMatchScore(IBodyData? requestMessage, IMatcher matcher)
{
Guard.NotNull(matcher);
@@ -23,7 +23,7 @@ internal static class BodyDataMatchScoreCalculator
return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyAsBytes);
default:
return MatchScores.Mismatch;
return default;
}
}
@@ -63,16 +63,6 @@ internal static class BodyDataMatchScoreCalculator
}
}
#if MIMEKIT_XXX
if (matcher is MultiPartMatcher multiPartMatcher)
{
// If the body is a String or MultiPart, use the BodyAsString to match on.
if (requestMessage?.DetectedBodyType is BodyType.String or BodyType.MultiPart)
{
return multiPartMatcher.IsMatch(requestMessage.BodyAsString);
}
}
#endif
return MatchScores.Mismatch;
return default;
}
}

View File

@@ -14,9 +14,4 @@ public interface IMatcher
/// Gets the match behaviour.
/// </summary>
MatchBehaviour MatchBehaviour { get; }
/// <summary>
/// Should this matcher throw an exception?
/// </summary>
bool ThrowException { get; }
}

View File

@@ -9,6 +9,6 @@ public interface IObjectMatcher : IMatcher
/// Determines whether the specified input is match.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>A value between 0.0 - 1.0 of the similarity.</returns>
double IsMatch(object? input);
/// <returns>MatchResult</returns>
MatchResult IsMatch(object? input);
}

View File

@@ -13,8 +13,8 @@ public interface IStringMatcher : IMatcher
/// Determines whether the specified input is match.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>A value between 0.0 - 1.0 of the similarity.</returns>
double IsMatch(string? input);
/// <returns>MatchResult</returns>
MatchResult IsMatch(string? input);
/// <summary>
/// Gets the patterns.

View File

@@ -1,6 +1,6 @@
using System;
using System.Linq;
using AnyOfTypes;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Stef.Validation;
using WireMock.Extensions;
@@ -17,17 +17,15 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
{
private readonly AnyOf<string, StringPattern>[] _patterns;
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc cref="IMatcher.ThrowException"/>
public bool ThrowException { get; }
/// <summary>
/// Initializes a new instance of the <see cref="JsonPathMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
public JsonPathMatcher(params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns.ToAnyOfPatterns())
public JsonPathMatcher(params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or,
patterns.ToAnyOfPatterns())
{
}
@@ -35,7 +33,8 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
/// Initializes a new instance of the <see cref="JsonPathMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
public JsonPathMatcher(params AnyOf<string, StringPattern>[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns)
public JsonPathMatcher(params AnyOf<string, StringPattern>[] patterns) : this(MatchBehaviour.AcceptOnMatch,
MatchOperator.Or, patterns)
{
}
@@ -43,48 +42,45 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
/// Initializes a new instance of the <see cref="JsonPathMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
/// <param name="patterns">The patterns.</param>
public JsonPathMatcher(
MatchBehaviour matchBehaviour,
bool throwException = false,
MatchOperator matchOperator = MatchOperator.Or,
params AnyOf<string, StringPattern>[] patterns)
{
_patterns = Guard.NotNull(patterns);
MatchBehaviour = matchBehaviour;
ThrowException = throwException;
MatchOperator = matchOperator;
}
/// <inheritdoc cref="IStringMatcher.IsMatch"/>
public double IsMatch(string? input)
/// <inheritdoc />
public MatchResult IsMatch(string? input)
{
double match = MatchScores.Mismatch;
var score = MatchScores.Mismatch;
Exception? exception = null;
if (input != null)
{
try
{
var jToken = JToken.Parse(input);
match = IsMatch(jToken);
score = IsMatch(jToken);
}
catch (JsonException)
catch (Exception ex)
{
if (ThrowException)
{
throw;
}
exception = ex;
}
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
/// <inheritdoc cref="IObjectMatcher.IsMatch"/>
public double IsMatch(object? input)
/// <inheritdoc />
public MatchResult IsMatch(object? input)
{
double match = MatchScores.Mismatch;
var score = MatchScores.Mismatch;
Exception? exception = null;
// When input is null or byte[], return Mismatch.
if (input != null && !(input is byte[]))
@@ -93,18 +89,15 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
{
// Check if JToken or object
JToken jToken = input as JToken ?? JObject.FromObject(input);
match = IsMatch(jToken);
score = IsMatch(jToken);
}
catch (JsonException)
catch (Exception ex)
{
if (ThrowException)
{
throw;
}
exception = ex;
}
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
/// <inheritdoc />
@@ -116,14 +109,18 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
/// <inheritdoc />
public MatchOperator MatchOperator { get; }
/// <inheritdoc cref="IMatcher.Name"/>
/// <inheritdoc />
public string Name => "JsonPathMatcher";
private double IsMatch(JToken jToken)
{
var array = ConvertJTokenToJArrayIfNeeded(jToken);
return MatchScores.ToScore(_patterns.Select(pattern => array.SelectToken(pattern.GetPattern())?.Any() == true).ToArray(), MatchOperator);
// 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.
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
@@ -138,9 +135,9 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
var item = property.First();
if (item is JArray)
{
return jToken;
return jToken;
}
return new JObject
{
[property.Path] = new JArray(item)

View File

@@ -1,3 +1,4 @@
using System;
using System.Linq;
using AnyOfTypes;
using DevLab.JmesPath;
@@ -15,17 +16,14 @@ public class JmesPathMatcher : IStringMatcher, IObjectMatcher
{
private readonly AnyOf<string, StringPattern>[] _patterns;
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc cref="IMatcher.ThrowException"/>
public bool ThrowException { get; }
/// <summary>
/// Initializes a new instance of the <see cref="JmesPathMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
public JmesPathMatcher(params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns.ToAnyOfPatterns())
public JmesPathMatcher(params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns.ToAnyOfPatterns())
{
}
@@ -33,18 +31,17 @@ public class JmesPathMatcher : IStringMatcher, IObjectMatcher
/// Initializes a new instance of the <see cref="JmesPathMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
public JmesPathMatcher(params AnyOf<string, StringPattern>[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns)
public JmesPathMatcher(params AnyOf<string, StringPattern>[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="JmesPathMatcher"/> class.
/// </summary>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use.</param>
/// <param name="patterns">The patterns.</param>
public JmesPathMatcher(bool throwException = false, MatchOperator matchOperator = MatchOperator.Or, params AnyOf<string, StringPattern>[] patterns) :
this(MatchBehaviour.AcceptOnMatch, throwException, matchOperator, patterns)
public JmesPathMatcher(MatchOperator matchOperator = MatchOperator.Or, params AnyOf<string, StringPattern>[] patterns) :
this(MatchBehaviour.AcceptOnMatch, matchOperator, patterns)
{
}
@@ -52,60 +49,56 @@ public class JmesPathMatcher : IStringMatcher, IObjectMatcher
/// Initializes a new instance of the <see cref="JmesPathMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use.</param>
/// <param name="patterns">The patterns.</param>
public JmesPathMatcher(
MatchBehaviour matchBehaviour,
bool throwException = false,
MatchOperator matchOperator = MatchOperator.Or,
params AnyOf<string, StringPattern>[] patterns)
{
_patterns = Guard.NotNull(patterns);
MatchBehaviour = matchBehaviour;
ThrowException = throwException;
MatchOperator = matchOperator;
}
/// <inheritdoc cref="IStringMatcher.IsMatch"/>
public double IsMatch(string? input)
/// <inheritdoc />
public MatchResult IsMatch(string? input)
{
double match = MatchScores.Mismatch;
var score = MatchScores.Mismatch;
Exception? exception = null;
if (input != null)
{
try
{
var results = _patterns.Select(pattern => bool.Parse(new JmesPath().Transform(input, pattern.GetPattern()))).ToArray();
match = MatchScores.ToScore(results, MatchOperator);
score = MatchScores.ToScore(results, MatchOperator);
}
catch (JsonException)
catch (Exception ex)
{
if (ThrowException)
{
throw;
}
exception = ex;
}
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
/// <inheritdoc cref="IObjectMatcher.IsMatch"/>
public double IsMatch(object? input)
/// <inheritdoc />
public MatchResult IsMatch(object? input)
{
double match = MatchScores.Mismatch;
var score = MatchScores.Mismatch;
// When input is null or byte[], return Mismatch.
if (input != null && !(input is byte[]))
{
string inputAsString = JsonConvert.SerializeObject(input);
var inputAsString = JsonConvert.SerializeObject(input);
return IsMatch(inputAsString);
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
return MatchBehaviourHelper.Convert(MatchBehaviour, score);
}
/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
/// <inheritdoc />
public AnyOf<string, StringPattern>[] GetPatterns()
{
return _patterns;
@@ -114,6 +107,6 @@ public class JmesPathMatcher : IStringMatcher, IObjectMatcher
/// <inheritdoc />
public MatchOperator MatchOperator { get; }
/// <inheritdoc cref="IMatcher.Name"/>
public string Name => "JmesPathMatcher";
/// <inheritdoc />
public string Name => nameof(JmesPathMatcher);
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Stef.Validation;
using WireMock.Util;
@@ -13,21 +12,18 @@ namespace WireMock.Matchers;
/// </summary>
public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher
{
/// <inheritdoc cref="IMatcher.Name"/>
/// <inheritdoc />
public virtual string Name => "JsonMatcher";
/// <inheritdoc cref="IValueMatcher.Value"/>
public object Value { get; }
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc cref="IIgnoreCaseMatcher.IgnoreCase"/>
public bool IgnoreCase { get; }
/// <inheritdoc cref="IMatcher.ThrowException"/>
public bool ThrowException { get; }
private readonly JToken _valueAsJToken;
private readonly Func<JToken, JToken> _jTokenConverter;
@@ -36,8 +32,7 @@ public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher
/// </summary>
/// <param name="value">The string value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
public JsonMatcher(string value, bool ignoreCase = false, bool throwException = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, throwException)
public JsonMatcher(string value, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase)
{
}
@@ -46,8 +41,7 @@ public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher
/// </summary>
/// <param name="value">The object value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
public JsonMatcher(object value, bool ignoreCase = false, bool throwException = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, throwException)
public JsonMatcher(object value, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase)
{
}
@@ -57,24 +51,23 @@ public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="value">The value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool throwException = false)
public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false)
{
Guard.NotNull(value, nameof(value));
Guard.NotNull(value);
MatchBehaviour = matchBehaviour;
IgnoreCase = ignoreCase;
ThrowException = throwException;
Value = value;
_valueAsJToken = ConvertValueToJToken(value);
_jTokenConverter = ignoreCase ? Rename : jToken => jToken;
}
/// <inheritdoc cref="IObjectMatcher.IsMatch"/>
public double IsMatch(object? input)
/// <inheritdoc />
public MatchResult IsMatch(object? input)
{
bool match = false;
var score = MatchScores.Mismatch;
Exception? error = null;
// When input is null or byte[], return Mismatch.
if (input != null && input is not byte[])
@@ -83,20 +76,16 @@ public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher
{
var inputAsJToken = ConvertValueToJToken(input);
match = IsMatch(
_jTokenConverter(_valueAsJToken),
_jTokenConverter(inputAsJToken));
var match = IsMatch(_jTokenConverter(_valueAsJToken), _jTokenConverter(inputAsJToken));
score = MatchScores.ToScore(match);
}
catch (JsonException)
catch (Exception ex)
{
if (ThrowException)
{
throw;
}
error = ex;
}
}
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match));
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), error);
}
/// <summary>

View File

@@ -9,27 +9,27 @@ public class JsonPartialMatcher : AbstractJsonPartialMatcher
public override string Name => nameof(JsonPartialMatcher);
/// <inheritdoc />
public JsonPartialMatcher(string value, bool ignoreCase = false, bool throwException = false, bool regex = false)
: base(value, ignoreCase, throwException, regex)
public JsonPartialMatcher(string value, bool ignoreCase = false, bool regex = false)
: base(value, ignoreCase, regex)
{
}
/// <inheritdoc />
public JsonPartialMatcher(object value, bool ignoreCase = false, bool throwException = false, bool regex = false)
: base(value, ignoreCase, throwException, regex)
public JsonPartialMatcher(object value, bool ignoreCase = false, bool regex = false)
: base(value, ignoreCase, regex)
{
}
/// <inheritdoc />
public JsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool throwException = false, bool regex = false)
: base(matchBehaviour, value, ignoreCase, throwException, regex)
public JsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false)
: base(matchBehaviour, value, ignoreCase, regex)
{
}
/// <inheritdoc />
protected override bool IsMatch(string value, string input)
{
var exactStringMatcher = new ExactMatcher(MatchBehaviour.AcceptOnMatch, IgnoreCase, ThrowException, MatchOperator.Or, value);
return MatchScores.IsPerfect(exactStringMatcher.IsMatch(input));
var exactStringMatcher = new ExactMatcher(MatchBehaviour.AcceptOnMatch, IgnoreCase, MatchOperator.Or, value);
return exactStringMatcher.IsMatch(input).IsPerfect();
}
}

View File

@@ -9,20 +9,20 @@ public class JsonPartialWildcardMatcher : AbstractJsonPartialMatcher
public override string Name => nameof(JsonPartialWildcardMatcher);
/// <inheritdoc />
public JsonPartialWildcardMatcher(string value, bool ignoreCase = false, bool throwException = false, bool regex = false)
: base(value, ignoreCase, throwException, regex)
public JsonPartialWildcardMatcher(string value, bool ignoreCase = false, bool regex = false)
: base(value, ignoreCase, regex)
{
}
/// <inheritdoc />
public JsonPartialWildcardMatcher(object value, bool ignoreCase = false, bool throwException = false, bool regex = false)
: base(value, ignoreCase, throwException, regex)
public JsonPartialWildcardMatcher(object value, bool ignoreCase = false, bool regex = false)
: base(value, ignoreCase, regex)
{
}
/// <inheritdoc />
public JsonPartialWildcardMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool throwException = false, bool regex = false)
: base(matchBehaviour, value, ignoreCase, throwException, regex)
public JsonPartialWildcardMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false)
: base(matchBehaviour, value, ignoreCase, regex)
{
}
@@ -30,6 +30,6 @@ public class JsonPartialWildcardMatcher : AbstractJsonPartialMatcher
protected override bool IsMatch(string value, string input)
{
var wildcardStringMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, value, IgnoreCase);
return MatchScores.IsPerfect(wildcardStringMatcher.IsMatch(input));
return wildcardStringMatcher.IsMatch(input).IsPerfect();
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Linq;
using System.Linq.Dynamic.Core;
using AnyOfTypes;
@@ -18,12 +19,9 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
{
private readonly AnyOf<string, StringPattern>[] _patterns;
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc cref="IMatcher.ThrowException"/>
public bool ThrowException { get; }
/// <summary>
/// Initializes a new instance of the <see cref="LinqMatcher"/> class.
/// </summary>
@@ -36,7 +34,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
/// Initializes a new instance of the <see cref="LinqMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
public LinqMatcher(params AnyOf<string, StringPattern>[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns)
public LinqMatcher(params AnyOf<string, StringPattern>[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns)
{
}
@@ -45,7 +43,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="pattern">The pattern.</param>
public LinqMatcher(MatchBehaviour matchBehaviour, AnyOf<string, StringPattern> pattern) : this(matchBehaviour, false, MatchOperator.Or, pattern)
public LinqMatcher(MatchBehaviour matchBehaviour, AnyOf<string, StringPattern> pattern) : this(matchBehaviour, MatchOperator.Or, pattern)
{
}
@@ -53,25 +51,23 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
/// Initializes a new instance of the <see cref="LinqMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
/// <param name="patterns">The patterns.</param>
public LinqMatcher(
MatchBehaviour matchBehaviour,
bool throwException = false,
MatchOperator matchOperator = MatchOperator.Or,
params AnyOf<string, StringPattern>[] patterns)
{
_patterns = Guard.NotNull(patterns);
MatchBehaviour = matchBehaviour;
ThrowException = throwException;
MatchOperator = matchOperator;
}
/// <inheritdoc />
public double IsMatch(string? input)
public MatchResult IsMatch(string? input)
{
double match = MatchScores.Mismatch;
var score = MatchScores.Mismatch;
Exception? error = null;
// Convert a single input string to a Queryable string-list with 1 entry.
IQueryable queryable = new[] { input }.AsQueryable();
@@ -79,25 +75,21 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
try
{
// Use the Any(...) method to check if the result matches
match = MatchScores.ToScore(_patterns.Select(pattern => queryable.Any(pattern.GetPattern())).ToArray(), MatchOperator);
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
score = MatchScores.ToScore(_patterns.Select(pattern => queryable.Any(pattern.GetPattern())).ToArray(), MatchOperator);
}
catch
catch (Exception e)
{
if (ThrowException)
{
throw;
}
error = e;
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), error);
}
/// <inheritdoc />
public double IsMatch(object? input)
public MatchResult IsMatch(object? input)
{
double match = MatchScores.Mismatch;
var score = MatchScores.Mismatch;
Exception? error = null;
JArray jArray;
try
@@ -106,7 +98,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
}
catch
{
jArray = new JArray { JToken.FromObject(input) };
jArray = input == null ? new JArray() : new JArray { JToken.FromObject(input) };
}
// Convert a single object to a Queryable JObject-list with 1 entry.
@@ -117,19 +109,14 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
var patternsAsStringArray = _patterns.Select(p => p.GetPattern()).ToArray();
var scores = patternsAsStringArray.Select(p => queryable.Any(p)).ToArray();
match = MatchScores.ToScore(scores, MatchOperator);
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
score = MatchScores.ToScore(scores, MatchOperator);
}
catch
catch (Exception e)
{
if (ThrowException)
{
throw;
}
error = e;
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), error);
}
/// <inheritdoc />
@@ -142,5 +129,5 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
public MatchOperator MatchOperator { get; }
/// <inheritdoc />
public string Name => "LinqMatcher";
public string Name => nameof(LinqMatcher);
}

View File

@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Stef.Validation;
using WireMock.Extensions;
namespace WireMock.Matchers;
/// <summary>
/// The MatchResult which contains the score (value between 0.0 - 1.0 of the similarity) and an optional error message.
/// </summary>
public struct MatchResult
{
/// <summary>
/// A value between 0.0 - 1.0 of the similarity.
/// </summary>
public double Score { get; set; }
/// <summary>
/// The exception message) in case the matching fails.
/// [Optional]
/// </summary>
public Exception? Exception { get; set; }
/// <summary>
/// Create a MatchResult
/// </summary>
/// <param name="score">A value between 0.0 - 1.0 of the similarity.</param>
/// <param name="exception">The exception in case the matching fails. [Optional]</param>
public MatchResult(double score, Exception? exception = null)
{
Score = score;
Exception = exception;
}
/// <summary>
/// Create a MatchResult
/// </summary>
/// <param name="exception">The exception in case the matching fails.</param>
public MatchResult(Exception exception)
{
Exception = Guard.NotNull(exception);
}
/// <summary>
/// Implicitly converts a double to a MatchResult.
/// </summary>
/// <param name="score">The score</param>
public static implicit operator MatchResult(double score)
{
return new MatchResult(score);
}
/// <summary>
/// Is the value a perfect match?
/// </summary>
public bool IsPerfect() => MatchScores.IsPerfect(Score);
/// <summary>
/// Create a MatchResult from multiple MatchResults.
/// </summary>
/// <param name="matchResults">A list of MatchResults.</param>
/// <param name="matchOperator">The MatchOperator</param>
/// <returns>MatchResult</returns>
public static MatchResult From(IReadOnlyList<MatchResult> matchResults, MatchOperator matchOperator)
{
Guard.NotNullOrEmpty(matchResults);
if (matchResults.Count == 1)
{
return matchResults[0];
}
return new MatchResult
{
Score = MatchScores.ToScore(matchResults.Select(r => r.Score).ToArray(), matchOperator),
Exception = matchResults.Select(m => m.Exception).OfType<Exception>().ToArray().ToException()
};
}
/// <summary>
/// Expand to Tuple
/// </summary>
/// <returns>Tuple : Score and Exception</returns>
public (double Score, Exception? Exception) Expand()
{
return (Score, Exception);
}
}

View File

@@ -1,7 +1,9 @@
#if MIMEKIT
using System;
using MimeKit;
using WireMock.Matchers;
using WireMock.Matchers.Helpers;
using WireMock.Models;
using WireMock.Util;
namespace WireMock.Matchers;
@@ -11,7 +13,7 @@ namespace WireMock.Matchers;
/// </summary>
public class MimePartMatcher : IMatcher
{
private readonly Func<MimePart, double>[] _funcs;
private readonly Func<MimePart, MatchResult>[] _funcs;
/// <inheritdoc />
public string Name => nameof(MimePartMatcher);
@@ -39,9 +41,6 @@ public class MimePartMatcher : IMatcher
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc />
public bool ThrowException { get; }
/// <summary>
/// Initializes a new instance of the <see cref="MimePartMatcher"/> class.
/// </summary>
@@ -50,8 +49,7 @@ public class MimePartMatcher : IMatcher
IStringMatcher? contentTypeMatcher,
IStringMatcher? contentDispositionMatcher,
IStringMatcher? contentTransferEncodingMatcher,
IMatcher? contentMatcher,
bool throwException = false
IMatcher? contentMatcher
)
{
MatchBehaviour = matchBehaviour;
@@ -59,7 +57,6 @@ public class MimePartMatcher : IMatcher
ContentDispositionMatcher = contentDispositionMatcher;
ContentTransferEncodingMatcher = contentTransferEncodingMatcher;
ContentMatcher = contentMatcher;
ThrowException = throwException;
_funcs = new[]
{
@@ -75,29 +72,27 @@ public class MimePartMatcher : IMatcher
/// </summary>
/// <param name="mimePart">The MimePart.</param>
/// <returns>A value between 0.0 - 1.0 of the similarity.</returns>
public double IsMatch(MimePart mimePart)
public MatchResult IsMatch(MimePart mimePart)
{
var match = MatchScores.Mismatch;
var score = MatchScores.Mismatch;
Exception? exception = null;
try
{
if (Array.TrueForAll(_funcs, func => MatchScores.IsPerfect(func(mimePart))))
if (Array.TrueForAll(_funcs, func => func(mimePart).IsPerfect()))
{
match = MatchScores.Perfect;
score = MatchScores.Perfect;
}
}
catch
catch (Exception ex)
{
if (ThrowException)
{
throw;
}
exception = ex;
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
private double MatchOnContent(MimePart mimePart)
private MatchResult MatchOnContent(MimePart mimePart)
{
if (ContentMatcher == null)
{

View File

@@ -11,15 +11,12 @@ namespace WireMock.Matchers;
/// <seealso cref="IObjectMatcher" />
public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher
{
/// <inheritdoc cref="IMatcher.Name"/>
public string Name => "NotNullOrEmptyMatcher";
/// <inheritdoc />
public string Name => nameof(NotNullOrEmptyMatcher);
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc cref="IMatcher.ThrowException"/>
public bool ThrowException { get; }
/// <summary>
/// Initializes a new instance of the <see cref="NotNullOrEmptyMatcher"/> class.
/// </summary>
@@ -29,8 +26,8 @@ public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher
MatchBehaviour = matchBehaviour;
}
/// <inheritdoc cref="IObjectMatcher.IsMatch"/>
public double IsMatch(object? input)
/// <inheritdoc />
public MatchResult IsMatch(object? input)
{
bool match;
@@ -52,15 +49,15 @@ public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match));
}
/// <inheritdoc cref="IStringMatcher.IsMatch"/>
public double IsMatch(string? input)
/// <inheritdoc />
public MatchResult IsMatch(string? input)
{
var match = !string.IsNullOrEmpty(input);
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match));
}
/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
/// <inheritdoc />
public AnyOf<string, StringPattern>[] GetPatterns()
{
return EmptyArray<AnyOf<string, StringPattern>>.Value;

View File

@@ -20,27 +20,22 @@ public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher
private readonly AnyOf<string, StringPattern>[] _patterns;
private readonly Regex[] _expressions;
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc cref="IMatcher.ThrowException"/>
public bool ThrowException { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RegexMatcher"/> class.
/// </summary>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="useRegexExtended">Use RegexExtended (default = true).</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
public RegexMatcher(
[RegexPattern] AnyOf<string, StringPattern> pattern,
bool ignoreCase = false,
bool throwException = false,
bool useRegexExtended = true,
MatchOperator matchOperator = MatchOperator.Or) :
this(MatchBehaviour.AcceptOnMatch, new[] { pattern }, ignoreCase, throwException, useRegexExtended, matchOperator)
this(MatchBehaviour.AcceptOnMatch, new[] { pattern }, ignoreCase, useRegexExtended, matchOperator)
{
}
@@ -50,17 +45,15 @@ public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="useRegexExtended">Use RegexExtended (default = true).</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
public RegexMatcher(
MatchBehaviour matchBehaviour,
[RegexPattern] AnyOf<string, StringPattern> pattern,
bool ignoreCase = false,
bool throwException = false,
bool useRegexExtended = true,
MatchOperator matchOperator = MatchOperator.Or) :
this(matchBehaviour, new[] { pattern }, ignoreCase, throwException, useRegexExtended, matchOperator)
this(matchBehaviour, new[] { pattern }, ignoreCase, useRegexExtended, matchOperator)
{
}
@@ -70,21 +63,18 @@ public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="patterns">The patterns.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="useRegexExtended">Use RegexExtended (default = true).</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
public RegexMatcher(
MatchBehaviour matchBehaviour,
[RegexPattern] AnyOf<string, StringPattern>[] patterns,
bool ignoreCase = false,
bool throwException = false,
bool useRegexExtended = true,
MatchOperator matchOperator = MatchOperator.Or)
{
_patterns = Guard.NotNull(patterns);
IgnoreCase = ignoreCase;
MatchBehaviour = matchBehaviour;
ThrowException = throwException;
MatchOperator = matchOperator;
RegexOptions options = RegexOptions.Compiled | RegexOptions.Multiline;
@@ -97,26 +87,25 @@ public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher
_expressions = patterns.Select(p => useRegexExtended ? new RegexExtended(p.GetPattern(), options) : new Regex(p.GetPattern(), options)).ToArray();
}
/// <inheritdoc cref="IStringMatcher.IsMatch"/>
public virtual double IsMatch(string? input)
/// <inheritdoc />
public virtual MatchResult IsMatch(string? input)
{
double match = MatchScores.Mismatch;
var score = MatchScores.Mismatch;
Exception? exception = null;
if (input != null)
{
try
{
match = MatchScores.ToScore(_expressions.Select(e => e.IsMatch(input)).ToArray(), MatchOperator);
score = MatchScores.ToScore(_expressions.Select(e => e.IsMatch(input)).ToArray(), MatchOperator);
}
catch (Exception)
catch (Exception ex)
{
if (ThrowException)
{
throw;
}
exception = ex;
}
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
/// <inheritdoc />

View File

@@ -9,30 +9,25 @@ namespace WireMock.Matchers.Request;
/// </summary>
public class RequestMatchResult : IRequestMatchResult
{
/// <inheritdoc cref="IRequestMatchResult.TotalScore" />
/// <inheritdoc />
public double TotalScore => MatchDetails.Sum(md => md.Score);
/// <inheritdoc cref="IRequestMatchResult.TotalNumber" />
/// <inheritdoc />
public int TotalNumber => MatchDetails.Count;
/// <inheritdoc cref="IRequestMatchResult.IsPerfectMatch" />
/// <inheritdoc />
public bool IsPerfectMatch => Math.Abs(TotalScore - TotalNumber) < MatchScores.Tolerance;
/// <inheritdoc cref="IRequestMatchResult.AverageTotalScore" />
public double AverageTotalScore => TotalNumber == 0 ? 0.0 : TotalScore / TotalNumber;
/// <inheritdoc />
public double AverageTotalScore => TotalNumber == 0 ? MatchScores.Mismatch : TotalScore / TotalNumber;
/// <inheritdoc cref="IRequestMatchResult.MatchDetails" />
/// <inheritdoc />
public IList<MatchDetail> MatchDetails { get; } = new List<MatchDetail>();
/// <summary>
/// Adds the score.
/// </summary>
/// <param name="matcherType">The matcher Type.</param>
/// <param name="score">The score.</param>
/// <returns>The score.</returns>
public double AddScore(Type matcherType, double score)
/// <inheritdoc />
public double AddScore(Type matcherType, double score, Exception? exception)
{
MatchDetails.Add(new MatchDetail { MatcherType = matcherType, Score = score });
MatchDetails.Add(new MatchDetail { MatcherType = matcherType, Score = score, Exception = exception });
return score;
}
@@ -44,11 +39,16 @@ public class RequestMatchResult : IRequestMatchResult
/// <returns>
/// A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes <paramref name="obj" /> in the sort order. Zero This instance occurs in the same position in the sort order as <paramref name="obj" />. Greater than zero This instance follows <paramref name="obj" /> in the sort order.
/// </returns>
public int CompareTo(object obj)
public int CompareTo(object? obj)
{
if (obj == null)
{
return -1;
}
var compareObj = (RequestMatchResult)obj;
int averageTotalScoreResult = compareObj.AverageTotalScore.CompareTo(AverageTotalScore);
var averageTotalScoreResult = compareObj.AverageTotalScore.CompareTo(AverageTotalScore);
// In case the score is equal, prefer the one with the most matchers.
return averageTotalScoreResult == 0 ? compareObj.TotalNumber.CompareTo(TotalNumber) : averageTotalScoreResult;

View File

@@ -145,16 +145,16 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
double score = CalculateMatchScore(requestMessage);
return requestMatchResult.AddScore(GetType(), score);
var (score, exception) = CalculateMatchScore(requestMessage).Expand();
return requestMatchResult.AddScore(GetType(), score, exception);
}
private double CalculateMatchScore(IRequestMessage requestMessage)
private MatchResult CalculateMatchScore(IRequestMessage requestMessage)
{
if (Matchers != null)
if (Matchers != null && Matchers.Any())
{
var matchersResult = Matchers.Select(matcher => BodyDataMatchScoreCalculator.CalculateMatchScore(requestMessage.BodyData, matcher)).ToArray();
return MatchScores.ToScore(matchersResult, MatchOperator);
var results = Matchers.Select(matcher => BodyDataMatchScoreCalculator.CalculateMatchScore(requestMessage.BodyData, matcher)).ToArray();
return MatchResult.From(results, MatchOperator);
}
if (Func != null)
@@ -182,6 +182,6 @@ public class RequestMessageBodyMatcher : IRequestMatcher
return MatchScores.ToScore(BodyDataFunc(requestMessage.BodyData));
}
return MatchScores.Mismatch;
return default;
}
}

View File

@@ -43,7 +43,7 @@ public class RequestMessageClientIPMatcher : IRequestMatcher
MatchOperator matchOperator,
params string[] clientIPs) :
this(matchBehaviour, matchOperator, clientIPs
.Select(clientIP => new WildcardMatcher(matchBehaviour, new AnyOf<string, StringPattern>[] { clientIP }, false, false, matchOperator))
.Select(clientIP => new WildcardMatcher(matchBehaviour, new AnyOf<string, StringPattern>[] { clientIP }, false, matchOperator))
.Cast<IStringMatcher>().ToArray())
{
Behaviour = matchBehaviour;
@@ -75,16 +75,16 @@ public class RequestMessageClientIPMatcher : IRequestMatcher
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
double score = IsMatch(requestMessage);
return requestMatchResult.AddScore(GetType(), score);
var (score, exception) = GetMatchResult(requestMessage).Expand();
return requestMatchResult.AddScore(GetType(), score, exception);
}
private double IsMatch(IRequestMessage requestMessage)
private MatchResult GetMatchResult(IRequestMessage requestMessage)
{
if (Matchers != null)
{
var results = Matchers.Select(m => m.IsMatch(requestMessage.ClientIP)).ToArray();
return MatchScores.ToScore(results, MatchOperator);
return MatchResult.From(results, MatchOperator);
}
if (Funcs != null)
@@ -93,6 +93,6 @@ public class RequestMessageClientIPMatcher : IRequestMatcher
return MatchScores.ToScore(results, MatchOperator);
}
return MatchScores.Mismatch;
return default;
}
}

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>
@@ -88,19 +94,19 @@ public class RequestMessageCookieMatcher : IRequestMatcher
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
double score = IsMatch(requestMessage);
return requestMatchResult.AddScore(GetType(), score);
var (score, exception) = GetMatchResult(requestMessage).Expand();
return requestMatchResult.AddScore(GetType(), score, exception);
}
private double IsMatch(IRequestMessage requestMessage)
private MatchResult GetMatchResult(IRequestMessage requestMessage)
{
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)
{
@@ -109,15 +115,14 @@ public class RequestMessageCookieMatcher : IRequestMatcher
if (Matchers == null)
{
return MatchScores.Mismatch;
return default;
}
if (!cookies.ContainsKey(Name))
{
return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch);
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
}
string value = cookies[Name];
return Matchers.Max(m => m.IsMatch(value));
return Matchers.Max(m => m.IsMatch(cookies[Name]));
}
}

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using Stef.Validation;
using WireMock.Types;
@@ -64,11 +65,13 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
var score = CalculateMatchScore(requestMessage);
return requestMatchResult.AddScore(GetType(), score);
var results = CalculateMatchResults(requestMessage);
var (score, exception) = MatchResult.From(results, MatchOperator).Expand();
return requestMatchResult.AddScore(GetType(), score, exception);
}
private static double CalculateMatchScore(IRequestMessage requestMessage, IMatcher matcher)
private static MatchResult CalculateMatchResult(IRequestMessage requestMessage, IMatcher matcher)
{
// Check if the matcher is a IStringMatcher
// If the body is a Json or a String, use the BodyAsString to match on.
@@ -77,18 +80,12 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher
return stringMatcher.IsMatch(requestMessage.BodyData.BodyAsString);
}
return MatchScores.Mismatch;
return default;
}
private double CalculateMatchScore(IRequestMessage requestMessage)
private IReadOnlyList<MatchResult> CalculateMatchResults(IRequestMessage requestMessage)
{
if (Matchers == null)
{
return MatchScores.Mismatch;
}
var matchersResult = Matchers.Select(matcher => CalculateMatchScore(requestMessage, matcher)).ToArray();
return MatchScores.ToScore(matchersResult, MatchOperator);
return Matchers == null ? new[] { new MatchResult() } : Matchers.Select(matcher => CalculateMatchResult(requestMessage, matcher)).ToArray();
}
#if GRAPHQL

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>
@@ -100,19 +107,19 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
double score = IsMatch(requestMessage);
return requestMatchResult.AddScore(GetType(), score);
var (score, exception) = GetMatchResult(requestMessage).Expand();
return requestMatchResult.AddScore(GetType(), score, exception);
}
private double IsMatch(IRequestMessage requestMessage)
private MatchResult GetMatchResult(IRequestMessage requestMessage)
{
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,20 +131,20 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
{
if (!headers.ContainsKey(Name))
{
return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch);
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
}
var results = new List<double>();
var results = new List<MatchResult>();
foreach (var matcher in Matchers)
{
var resultsPerMatcher = headers[Name].Select(v => matcher.IsMatch(v)).ToArray();
var resultsPerMatcher = headers[Name].Select(matcher.IsMatch).ToArray();
results.Add(MatchScores.ToScore(resultsPerMatcher, MatchOperator.And));
results.Add(MatchResult.From(resultsPerMatcher, MatchOperator.And));
}
return MatchScores.ToScore(results, MatchOperator);
return MatchResult.From(results, MatchOperator);
}
return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch);
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
}
}

View File

@@ -39,14 +39,9 @@ internal class RequestMessageMethodMatcher : IRequestMatcher
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
double score = MatchBehaviourHelper.Convert(MatchBehaviour, IsMatch(requestMessage));
return requestMatchResult.AddScore(GetType(), score);
}
private double IsMatch(IRequestMessage requestMessage)
{
var scores = Methods.Select(m => string.Equals(m, requestMessage.Method, StringComparison.OrdinalIgnoreCase)).ToArray();
return MatchScores.ToScore(scores, MatchOperator);
var score = MatchScores.ToScore(scores, MatchOperator);
return requestMatchResult.AddScore(GetType(), score, null);
}
}

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Stef.Validation;
using WireMock.Http;
using WireMock.Util;
namespace WireMock.Matchers.Request;
@@ -54,27 +54,31 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher
#if !MIMEKIT
throw new System.NotSupportedException("The MultiPartMatcher can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
#else
var match = MatchScores.Mismatch;
var score = MatchScores.Mismatch;
Exception? exception = null;
if (Matchers?.Any() != true)
{
return requestMatchResult.AddScore(GetType(), match);
return requestMatchResult.AddScore(GetType(), score, null);
}
if (!MimeKitUtils.TryGetMimeMessage(requestMessage, out var message))
{
return requestMatchResult.AddScore(GetType(), score, null);
}
try
{
var message = MimeKitUtils.GetMimeMessage(requestMessage.BodyData!, requestMessage.Headers![HttpKnownHeaderNames.ContentType].ToString());
var mimePartMatchers = Matchers.OfType<MimePartMatcher>().ToArray();
foreach (var mimePart in message.BodyParts.OfType<MimeKit.MimePart>())
{
var matchesForMimePart = new List<double> { MatchScores.Mismatch };
var matchesForMimePart = new List<MatchResult> { default };
matchesForMimePart.AddRange(mimePartMatchers.Select(matcher => matcher.IsMatch(mimePart)));
match = matchesForMimePart.Max();
score = matchesForMimePart.Select(m => m.Score).Max();
if (MatchScores.IsPerfect(match))
if (MatchScores.IsPerfect(score))
{
if (MatchOperator == MatchOperator.Or)
{
@@ -83,17 +87,17 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher
}
else
{
match = MatchScores.Mismatch;
score = MatchScores.Mismatch;
break;
}
}
}
catch
catch (Exception ex)
{
// Empty
exception = ex;
}
return requestMatchResult.AddScore(GetType(), match);
return requestMatchResult.AddScore(GetType(), score, exception);
#endif
}
}

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.
@@ -54,7 +54,7 @@ public class RequestMessageParamMatcher : IRequestMatcher
/// <param name="ignoreCase">Defines if the key should be matched using case-ignore.</param>
/// <param name="values">The values.</param>
public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, string key, bool ignoreCase, params string[]? values) :
this(matchBehaviour, key, ignoreCase, values?.Select(value => new ExactMatcher(matchBehaviour, ignoreCase, false, MatchOperator.And, value)).Cast<IStringMatcher>().ToArray())
this(matchBehaviour, key, ignoreCase, values?.Select(value => new ExactMatcher(matchBehaviour, ignoreCase, MatchOperator.And, value)).Cast<IStringMatcher>().ToArray())
{
}
@@ -85,18 +85,18 @@ public class RequestMessageParamMatcher : IRequestMatcher
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
double score = MatchBehaviourHelper.Convert(MatchBehaviour, IsMatch(requestMessage));
return requestMatchResult.AddScore(GetType(), score);
var score = GetMatchScore(requestMessage);
return requestMatchResult.AddScore(GetType(), MatchBehaviourHelper.Convert(MatchBehaviour, score), null);
}
private double IsMatch(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
@@ -118,7 +118,7 @@ public class RequestMessageParamMatcher : IRequestMatcher
return MatchScores.Mismatch;
}
private double CalculateScore(IReadOnlyList<IStringMatcher> matchers, WireMockList<string> valuesPresentInRequestMessage)
private static double CalculateScore(IReadOnlyList<IStringMatcher> matchers, WireMockList<string> valuesPresentInRequestMessage)
{
var total = new List<double>();
@@ -130,7 +130,7 @@ public class RequestMessageParamMatcher : IRequestMatcher
double score = 0d;
foreach (string valuePresentInRequestMessage in valuesPresentInRequestMessage)
{
score += matcher.IsMatch(valuePresentInRequestMessage) / matcher.GetPatterns().Length;
score += matcher.IsMatch(valuePresentInRequestMessage).Score / matcher.GetPatterns().Length;
}
total.Add(score);
@@ -140,11 +140,11 @@ public class RequestMessageParamMatcher : IRequestMatcher
{
foreach (string valuePresentInRequestMessage in valuesPresentInRequestMessage)
{
double score = matchers.Max(m => m.IsMatch(valuePresentInRequestMessage));
var score = matchers.Max(m => m.IsMatch(valuePresentInRequestMessage).Score);
total.Add(score);
}
}
return total.Any() ? MatchScores.ToScore(total, MatchOperator.Average) : MatchScores.Mismatch;
return total.Any() ? MatchScores.ToScore(total, MatchOperator.Average) : default;
}
}

View File

@@ -43,7 +43,7 @@ public class RequestMessagePathMatcher : IRequestMatcher
MatchOperator matchOperator,
params string[] paths) :
this(matchBehaviour, matchOperator, paths
.Select(path => new WildcardMatcher(matchBehaviour, new AnyOf<string, StringPattern>[] { path }, false, false, matchOperator))
.Select(path => new WildcardMatcher(matchBehaviour, new AnyOf<string, StringPattern>[] { path }, false, matchOperator))
.Cast<IStringMatcher>().ToArray())
{
Behaviour = matchBehaviour;
@@ -75,16 +75,16 @@ public class RequestMessagePathMatcher : IRequestMatcher
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
double score = IsMatch(requestMessage);
return requestMatchResult.AddScore(GetType(), score);
var (score, exception) = GetMatchResult(requestMessage).Expand();
return requestMatchResult.AddScore(GetType(), score, exception);
}
private double IsMatch(IRequestMessage requestMessage)
private MatchResult GetMatchResult(IRequestMessage requestMessage)
{
if (Matchers != null)
{
var results = Matchers.Select(m => m.IsMatch(requestMessage.Path)).ToArray();
return MatchScores.ToScore(results, MatchOperator);
return MatchResult.From(results, MatchOperator);
}
if (Funcs != null)
@@ -93,6 +93,6 @@ public class RequestMessagePathMatcher : IRequestMatcher
return MatchScores.ToScore(results, MatchOperator);
}
return MatchScores.Mismatch;
return default;
}
}

View File

@@ -30,11 +30,10 @@ internal class RequestMessageScenarioAndStateMatcher : IRequestMatcher
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
double score = IsMatch();
return requestMatchResult.AddScore(GetType(), score);
return requestMatchResult.AddScore(GetType(), GetScore(), null);
}
private double IsMatch()
private double GetScore()
{
return Equals(_executionConditionState, _nextState) ? MatchScores.Perfect : MatchScores.Mismatch;
}

View File

@@ -43,7 +43,7 @@ public class RequestMessageUrlMatcher : IRequestMatcher
MatchOperator matchOperator,
params string[] urls) :
this(matchBehaviour, matchOperator, urls
.Select(url => new WildcardMatcher(matchBehaviour, new AnyOf<string, StringPattern>[] { url }, false, false, matchOperator))
.Select(url => new WildcardMatcher(matchBehaviour, new AnyOf<string, StringPattern>[] { url }, false, matchOperator))
.Cast<IStringMatcher>().ToArray())
{
Behaviour = matchBehaviour;
@@ -75,16 +75,16 @@ public class RequestMessageUrlMatcher : IRequestMatcher
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
double score = IsMatch(requestMessage);
return requestMatchResult.AddScore(GetType(), score);
var (score, exception) = GetMatchResult(requestMessage).Expand();
return requestMatchResult.AddScore(GetType(), score, exception);
}
private double IsMatch(IRequestMessage requestMessage)
private MatchResult GetMatchResult(IRequestMessage requestMessage)
{
if (Matchers != null)
{
var results = Matchers.Select(m => m.IsMatch(requestMessage.Url)).ToArray();
return MatchScores.ToScore(results, MatchOperator);
return MatchResult.From(results, MatchOperator);
}
if (Funcs != null)
@@ -93,6 +93,6 @@ public class RequestMessageUrlMatcher : IRequestMatcher
return MatchScores.ToScore(results, MatchOperator);
}
return MatchScores.Mismatch;
return default;
}
}

View File

@@ -18,12 +18,9 @@ public class SimMetricsMatcher : IStringMatcher
private readonly AnyOf<string, StringPattern>[] _patterns;
private readonly SimMetricType _simMetricType;
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc cref="IMatcher.ThrowException"/>
public bool ThrowException { get; }
/// <summary>
/// Initializes a new instance of the <see cref="SimMetricsMatcher"/> class.
/// </summary>
@@ -67,24 +64,21 @@ public class SimMetricsMatcher : IStringMatcher
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="patterns">The patterns.</param>
/// <param name="simMetricType">The SimMetric Type</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
public SimMetricsMatcher(
MatchBehaviour matchBehaviour,
AnyOf<string, StringPattern>[] patterns,
SimMetricType simMetricType = SimMetricType.Levenstein,
bool throwException = false,
MatchOperator matchOperator = MatchOperator.Average)
{
_patterns = Guard.NotNull(patterns);
_simMetricType = simMetricType;
MatchBehaviour = matchBehaviour;
ThrowException = throwException;
MatchOperator = matchOperator;
}
/// <inheritdoc cref="IStringMatcher.IsMatch"/>
public double IsMatch(string input)
/// <inheritdoc />
public MatchResult IsMatch(string? input)
{
IStringMetric stringMetricType = GetStringMetricType();
@@ -94,48 +88,30 @@ public class SimMetricsMatcher : IStringMatcher
private IStringMetric GetStringMetricType()
{
switch (_simMetricType)
return _simMetricType switch
{
case SimMetricType.BlockDistance:
return new BlockDistance();
case SimMetricType.ChapmanLengthDeviation:
return new ChapmanLengthDeviation();
case SimMetricType.CosineSimilarity:
return new CosineSimilarity();
case SimMetricType.DiceSimilarity:
return new DiceSimilarity();
case SimMetricType.EuclideanDistance:
return new EuclideanDistance();
case SimMetricType.JaccardSimilarity:
return new JaccardSimilarity();
case SimMetricType.Jaro:
return new Jaro();
case SimMetricType.JaroWinkler:
return new JaroWinkler();
case SimMetricType.MatchingCoefficient:
return new MatchingCoefficient();
case SimMetricType.MongeElkan:
return new MongeElkan();
case SimMetricType.NeedlemanWunch:
return new NeedlemanWunch();
case SimMetricType.OverlapCoefficient:
return new OverlapCoefficient();
case SimMetricType.QGramsDistance:
return new QGramsDistance();
case SimMetricType.SmithWaterman:
return new SmithWaterman();
case SimMetricType.SmithWatermanGotoh:
return new SmithWatermanGotoh();
case SimMetricType.SmithWatermanGotohWindowedAffine:
return new SmithWatermanGotohWindowedAffine();
case SimMetricType.ChapmanMeanLength:
return new ChapmanMeanLength();
default:
return new Levenstein();
}
SimMetricType.BlockDistance => new BlockDistance(),
SimMetricType.ChapmanLengthDeviation => new ChapmanLengthDeviation(),
SimMetricType.CosineSimilarity => new CosineSimilarity(),
SimMetricType.DiceSimilarity => new DiceSimilarity(),
SimMetricType.EuclideanDistance => new EuclideanDistance(),
SimMetricType.JaccardSimilarity => new JaccardSimilarity(),
SimMetricType.Jaro => new Jaro(),
SimMetricType.JaroWinkler => new JaroWinkler(),
SimMetricType.MatchingCoefficient => new MatchingCoefficient(),
SimMetricType.MongeElkan => new MongeElkan(),
SimMetricType.NeedlemanWunch => new NeedlemanWunch(),
SimMetricType.OverlapCoefficient => new OverlapCoefficient(),
SimMetricType.QGramsDistance => new QGramsDistance(),
SimMetricType.SmithWaterman => new SmithWaterman(),
SimMetricType.SmithWatermanGotoh => new SmithWatermanGotoh(),
SimMetricType.SmithWatermanGotohWindowedAffine => new SmithWatermanGotohWindowedAffine(),
SimMetricType.ChapmanMeanLength => new ChapmanMeanLength(),
_ => new Levenstein()
};
}
/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
/// <inheritdoc />
public AnyOf<string, StringPattern>[] GetPatterns()
{
return _patterns;
@@ -144,6 +120,6 @@ public class SimMetricsMatcher : IStringMatcher
/// <inheritdoc />
public MatchOperator MatchOperator { get; } = MatchOperator.Average;
/// <inheritdoc cref="IMatcher.Name"/>
/// <inheritdoc />
public string Name => $"SimMetricsMatcher.{_simMetricType}";
}

View File

@@ -49,15 +49,12 @@ public class WildcardMatcher : RegexMatcher
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="patterns">The patterns.</param>
/// <param name="ignoreCase">IgnoreCase</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use. (default = "Or")</param>
public WildcardMatcher(
MatchBehaviour matchBehaviour,
AnyOf<string, StringPattern>[] patterns,
bool ignoreCase = false,
bool throwException = false,
MatchOperator matchOperator = MatchOperator.Or) :
base(matchBehaviour, CreateArray(patterns), ignoreCase, throwException, true, matchOperator)
MatchOperator matchOperator = MatchOperator.Or) : base(matchBehaviour, CreateArray(patterns), ignoreCase, true, matchOperator)
{
_patterns = Guard.NotNull(patterns);
}

View File

@@ -1,94 +1,156 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.XPath;
using AnyOfTypes;
using WireMock.Extensions;
using WireMock.Models;
using Stef.Validation;
using WireMock.Admin.Mappings;
#if !NETSTANDARD1_3
using Wmhelp.XPath2;
#endif
namespace WireMock.Matchers
namespace WireMock.Matchers;
/// <summary>
/// XPath2Matcher
/// </summary>
/// <seealso cref="IStringMatcher" />
public class XPathMatcher : IStringMatcher
{
private readonly AnyOf<string, StringPattern>[] _patterns;
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <summary>
/// XPath2Matcher
/// Array of namespace prefix and uri.
/// </summary>
/// <seealso cref="IStringMatcher" />
public class XPathMatcher : IStringMatcher
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, null, patterns)
{
private readonly AnyOf<string, StringPattern>[] _patterns;
}
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
public MatchBehaviour MatchBehaviour { get; }
/// <summary>
/// Initializes a new instance of the <see cref="XPathMatcher"/> class.
/// </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;
}
/// <inheritdoc cref="IMatcher.ThrowException"/>
public bool ThrowException { get; }
/// <inheritdoc />
public MatchResult IsMatch(string? input)
{
var score = MatchScores.Mismatch;
/// <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, false, MatchOperator.Or, patterns)
if (input == null)
{
return CreateMatchResult(score);
}
/// <summary>
/// Initializes a new instance of the <see cref="XPathMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
/// <param name="patterns">The patterns.</param>
public XPathMatcher(
MatchBehaviour matchBehaviour,
bool throwException = false,
MatchOperator matchOperator = MatchOperator.Or,
params AnyOf<string, StringPattern>[] patterns)
try
{
_patterns = Guard.NotNull(patterns);
MatchBehaviour = matchBehaviour;
ThrowException = throwException;
MatchOperator = matchOperator;
}
var xPathEvaluator = new XPathEvaluator();
xPathEvaluator.Load(input);
/// <inheritdoc cref="IStringMatcher.IsMatch"/>
public double IsMatch(string? input)
{
double match = MatchScores.Mismatch;
if (input != null)
if (!xPathEvaluator.IsXmlDocumentLoaded)
{
try
{
var nav = new XmlDocument { InnerXml = input }.CreateNavigator();
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);
}
/// <inheritdoc />
public AnyOf<string, StringPattern>[] GetPatterns()
{
return _patterns;
}
/// <inheritdoc />
public MatchOperator MatchOperator { get; }
/// <inheritdoc />
public string Name => nameof(XPathMatcher);
private class XPathEvaluator
{
private XmlDocument? _xmlDocument;
private XPathNavigator? _xpathNavigator;
public bool IsXmlDocumentLoaded => _xmlDocument != null;
public void Load(string input)
{
try
{
_xmlDocument = new XmlDocument { InnerXml = input };
_xpathNavigator = _xmlDocument.CreateNavigator();
}
catch
{
_xmlDocument = default;
}
}
public bool[] Evaluate(AnyOf<string, StringPattern>[] patterns, IEnumerable<XmlNamespace>? xmlNamespaceMap)
{
XmlNamespaceManager? xmlNamespaceManager = GetXmlNamespaceManager(xmlNamespaceMap);
return patterns
.Select(p =>
#if NETSTANDARD1_3
match = MatchScores.ToScore(_patterns.Select(p => true.Equals(nav.Evaluate($"boolean({p.GetPattern()})"))).ToArray(), MatchOperator);
true.Equals(_xpathNavigator.Evaluate($"boolean({p.GetPattern()})", xmlNamespaceManager)))
#else
match = MatchScores.ToScore(_patterns.Select(p => true.Equals(nav.XPath2Evaluate($"boolean({p.GetPattern()})"))).ToArray(), MatchOperator);
true.Equals(_xpathNavigator.XPath2Evaluate($"boolean({p.GetPattern()})", xmlNamespaceManager)))
#endif
}
catch (Exception)
{
if (ThrowException)
{
throw;
}
}
.ToArray();
}
private XmlNamespaceManager? GetXmlNamespaceManager(IEnumerable<XmlNamespace>? xmlNamespaceMap)
{
if (_xpathNavigator == null || xmlNamespaceMap == null)
{
return default;
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
var nsManager = new XmlNamespaceManager(_xpathNavigator.NameTable);
foreach (XmlNamespace xmlNamespace in xmlNamespaceMap)
{
nsManager.AddNamespace(xmlNamespace.Prefix, xmlNamespace.Uri);
}
return nsManager;
}
/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
public AnyOf<string, StringPattern>[] GetPatterns()
{
return _patterns;
}
/// <inheritdoc />
public MatchOperator MatchOperator { get; }
/// <inheritdoc cref="IMatcher.Name"/>
public string Name => "XPathMatcher";
}
}

View File

@@ -70,6 +70,7 @@ namespace WireMock.Owin
services.AddSingleton<IRandomizerDoubleBetween0And1, RandomizerDoubleBetween0And1>();
services.AddSingleton<IOwinRequestMapper, OwinRequestMapper>();
services.AddSingleton<IOwinResponseMapper, OwinResponseMapper>();
services.AddSingleton<IGuidUtils, GuidUtils>();
#if NETCOREAPP3_1 || NET5_0 || NET6_0 || NET7_0
AddCors(services);

View File

@@ -67,7 +67,7 @@ namespace WireMock.Owin
catch (Exception ex)
{
_options.Logger.Error("HttpStatusCode set to 500 {0}", ex);
await _responseMapper.MapAsync(ResponseMessageBuilder.Create(JsonConvert.SerializeObject(ex), 500), ctx.Response).ConfigureAwait(false);
await _responseMapper.MapAsync(ResponseMessageBuilder.Create(500, JsonConvert.SerializeObject(ex)), ctx.Response).ConfigureAwait(false);
}
}
}

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

@@ -50,7 +50,7 @@ namespace WireMock.Owin.Mappers
_options = Guard.NotNull(options);
}
/// <inheritdoc cref="IOwinResponseMapper.MapAsync"/>
/// <inheritdoc />
public async Task MapAsync(IResponseMessage? responseMessage, IResponse response)
{
if (responseMessage == null)
@@ -134,10 +134,8 @@ namespace WireMock.Owin.Mappers
return (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(responseMessage.BodyData.BodyAsString!);
case BodyType.Json:
var formatting = responseMessage.BodyData.BodyAsJsonIndented == true
? Formatting.Indented
: Formatting.None;
string jsonBody = JsonConvert.SerializeObject(responseMessage.BodyData.BodyAsJson, new JsonSerializerSettings { Formatting = formatting, NullValueHandling = NullValueHandling.Ignore });
var formatting = responseMessage.BodyData.BodyAsJsonIndented == true ? Formatting.Indented : Formatting.None;
var jsonBody = JsonConvert.SerializeObject(responseMessage.BodyData.BodyAsJson, new JsonSerializerSettings { Formatting = formatting, NullValueHandling = NullValueHandling.Ignore });
return (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody);
case BodyType.Bytes:

View File

@@ -35,21 +35,37 @@ internal class MappingMatcher : IMappingMatcher
{
var nextState = GetNextState(mapping);
possibleMappings.Add(new MappingMatcherResult
var mappingMatcherResult = new MappingMatcherResult(mapping, mapping.GetRequestMatchResult(request, nextState));
var exceptions = mappingMatcherResult.RequestMatchResult.MatchDetails
.Where(md => md.Exception != null)
.Select(md => md.Exception)
.ToArray();
if (!exceptions.Any())
{
Mapping = mapping,
RequestMatchResult = mapping.GetRequestMatchResult(request, nextState)
});
possibleMappings.Add(mappingMatcherResult);
}
else if (!request.AbsolutePath.StartsWith("/__admin", StringComparison.OrdinalIgnoreCase))
{
foreach (var ex in exceptions)
{
LogException(mapping, ex!);
}
}
}
catch (Exception ex)
{
_options.Logger.Error($"Getting a Request MatchResult for Mapping '{mapping.Guid}' failed. This mapping will not be evaluated. Exception: {ex}");
LogException(mapping, ex);
}
}
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);
@@ -66,6 +82,11 @@ internal class MappingMatcher : IMappingMatcher
return (match, partialMatch);
}
private void LogException(IMapping mapping, Exception ex)
{
_options.Logger.Error($"Getting a Request MatchResult for Mapping '{mapping.Guid}' failed. This mapping will not be evaluated. Exception: {ex}");
}
private string? GetNextState(IMapping mapping)
{
// If the mapping does not have a scenario or _options.Scenarios does not contain this scenario from the mapping,

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

@@ -9,9 +9,8 @@ using JetBrains.Annotations;
using WireMock.Logging;
using WireMock.Owin.Mappers;
using Stef.Validation;
using RandomDataGenerator.FieldOptions;
using RandomDataGenerator.Randomizers;
using WireMock.Services;
using WireMock.Util;
namespace WireMock.Owin;
@@ -63,7 +62,7 @@ internal class OwinSelfHost : IOwinSelfHost
private void StartServers()
{
#if NET46
_logger.Info("Server using .net 4.6.1 or higher");
_logger.Info("Server using .net 4.6");
#else
_logger.Info("Server using .net 4.5.x");
#endif
@@ -74,12 +73,13 @@ internal class OwinSelfHost : IOwinSelfHost
var requestMapper = new OwinRequestMapper();
var responseMapper = new OwinResponseMapper(_options);
var matcher = new MappingMatcher(_options, new RandomizerDoubleBetween0And1());
var guidUtils = new GuidUtils();
Action<IAppBuilder> startup = app =>
{
app.Use<GlobalExceptionMiddleware>(_options, responseMapper);
_options.PreWireMockMiddlewareInit?.Invoke(app);
app.Use<WireMockMiddleware>(_options, requestMapper, responseMapper, matcher);
app.Use<WireMockMiddleware>(_options, requestMapper, responseMapper, matcher, guidUtils);
_options.PostWireMockMiddlewareInit?.Invoke(app);
};

View File

@@ -12,6 +12,8 @@ using WireMock.Types;
using WireMock.ResponseBuilders;
using WireMock.Settings;
using System.Collections.Generic;
using WireMock.Constants;
using WireMock.Util;
#if !USE_ASPNETCORE
using IContext = Microsoft.Owin.IOwinContext;
using OwinMiddleware = Microsoft.Owin.OwinMiddleware;
@@ -28,29 +30,47 @@ namespace WireMock.Owin
{
private readonly object _lock = new();
private static readonly Task CompletedTask = Task.FromResult(false);
private readonly IWireMockMiddlewareOptions _options;
private readonly IOwinRequestMapper _requestMapper;
private readonly IOwinResponseMapper _responseMapper;
private readonly IMappingMatcher _mappingMatcher;
private readonly LogEntryMapper _logEntryMapper;
private readonly IGuidUtils _guidUtils;
#if !USE_ASPNETCORE
public WireMockMiddleware(Next next, IWireMockMiddlewareOptions options, IOwinRequestMapper requestMapper, IOwinResponseMapper responseMapper, IMappingMatcher mappingMatcher) : base(next)
public WireMockMiddleware(
Next next,
IWireMockMiddlewareOptions options,
IOwinRequestMapper requestMapper,
IOwinResponseMapper responseMapper,
IMappingMatcher mappingMatcher,
IGuidUtils guidUtils
) : base(next)
{
_options = Guard.NotNull(options);
_requestMapper = Guard.NotNull(requestMapper);
_responseMapper = Guard.NotNull(responseMapper);
_mappingMatcher = Guard.NotNull(mappingMatcher);
_logEntryMapper = new LogEntryMapper(options);
_guidUtils = Guard.NotNull(guidUtils);
}
#else
public WireMockMiddleware(Next next, IWireMockMiddlewareOptions options, IOwinRequestMapper requestMapper, IOwinResponseMapper responseMapper, IMappingMatcher mappingMatcher)
public WireMockMiddleware(
Next next,
IWireMockMiddlewareOptions options,
IOwinRequestMapper requestMapper,
IOwinResponseMapper responseMapper,
IMappingMatcher mappingMatcher,
IGuidUtils guidUtils
)
{
_options = Guard.NotNull(options);
_requestMapper = Guard.NotNull(requestMapper);
_responseMapper = Guard.NotNull(responseMapper);
_mappingMatcher = Guard.NotNull(mappingMatcher);
_logEntryMapper = new LogEntryMapper(options);
_guidUtils = Guard.NotNull(guidUtils);
}
#endif
@@ -105,7 +125,7 @@ namespace WireMock.Owin
{
logRequest = true;
_options.Logger.Warn("HttpStatusCode set to 404 : No matching mapping found");
response = ResponseMessageBuilder.Create("No matching mapping found", 404);
response = ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound);
return;
}
@@ -113,11 +133,11 @@ namespace WireMock.Owin
if (targetMapping.IsAdminInterface && _options.AuthenticationMatcher != null && request.Headers != null)
{
bool present = request.Headers.TryGetValue(HttpKnownHeaderNames.Authorization, out WireMockList<string> authorization);
if (!present || _options.AuthenticationMatcher.IsMatch(authorization!.ToString()) < MatchScores.Perfect)
bool present = request.Headers.TryGetValue(HttpKnownHeaderNames.Authorization, out WireMockList<string>? authorization);
if (!present || _options.AuthenticationMatcher.IsMatch(authorization!.ToString()).Score < MatchScores.Perfect)
{
_options.Logger.Error("HttpStatusCode set to 401");
response = ResponseMessageBuilder.Create(null, HttpStatusCode.Unauthorized);
response = ResponseMessageBuilder.Create(HttpStatusCode.Unauthorized, null);
return;
}
}
@@ -161,14 +181,14 @@ namespace WireMock.Owin
}
catch (Exception ex)
{
_options.Logger.Error($"Providing a Response for Mapping '{result.Match?.Mapping?.Guid}' failed. HttpStatusCode set to 500. Exception: {ex}");
response = ResponseMessageBuilder.Create(ex.Message, 500);
_options.Logger.Error($"Providing a Response for Mapping '{result.Match?.Mapping.Guid}' failed. HttpStatusCode set to 500. Exception: {ex}");
response = ResponseMessageBuilder.Create(500, ex.Message);
}
finally
{
var log = new LogEntry
{
Guid = Guid.NewGuid(),
Guid = _guidUtils.NewGuid(),
RequestMessage = request,
ResponseMessage = response,
@@ -185,10 +205,10 @@ namespace WireMock.Owin
try
{
if (_options.SaveUnmatchedRequests == true && result.Match?.RequestMatchResult.IsPerfectMatch != true)
if (_options.SaveUnmatchedRequests == true && result.Match?.RequestMatchResult is not { IsPerfectMatch: true })
{
var filename = $"{log.Guid}.LogEntry.json";
_options.FileSystemHandler?.WriteUnmatchedRequest(filename, Util.JsonUtils.Serialize(log));
_options.FileSystemHandler?.WriteUnmatchedRequest(filename, JsonUtils.Serialize(log));
}
}
catch
@@ -196,7 +216,17 @@ namespace WireMock.Owin
// Empty catch
}
await _responseMapper.MapAsync(response, ctx.Response).ConfigureAwait(false);
try
{
await _responseMapper.MapAsync(response, ctx.Response).ConfigureAwait(false);
}
catch (Exception ex)
{
_options.Logger.Error("HttpStatusCode set to 404 : No matching mapping found", ex);
var notFoundResponse = ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound);
await _responseMapper.MapAsync(notFoundResponse, ctx.Response).ConfigureAwait(false);
}
}
await CompletedTask.ConfigureAwait(false);

View File

@@ -4,11 +4,11 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Newtonsoft.Json;
#if USE_ASPNETCORE
using System.Security.Cryptography.X509Certificates;
#endif
using Stef.Validation;
using WireMock.Http;
using WireMock.Models;
using WireMock.Owin;
using WireMock.Types;
@@ -80,6 +80,7 @@ public class RequestMessage : IRequestMessage
#if MIMEKIT
/// <inheritdoc />
[JsonIgnore] // Issue 1001
public object? BodyAsMimeMessage { get; }
#endif
@@ -112,7 +113,7 @@ public class RequestMessage : IRequestMessage
/// <summary>
/// Used for Unit Testing
/// </summary>
public RequestMessage(
internal RequestMessage(
UrlDetails urlDetails,
string method,
string clientIP,
@@ -122,19 +123,6 @@ public class RequestMessage : IRequestMessage
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessage"/> class.
/// </summary>
/// <param name="options">The<seealso cref="IWireMockMiddlewareOptions"/>.</param>
/// <param name="urlDetails">The original url details.</param>
/// <param name="method">The HTTP method.</param>
/// <param name="clientIP">The client IP Address.</param>
/// <param name="bodyData">The BodyData.</param>
/// <param name="headers">The headers.</param>
/// <param name="cookies">The cookies.</param>
#if USE_ASPNETCORE
/// <param name="clientCertificate">The client certificate</param>
#endif
internal RequestMessage(
IWireMockMiddlewareOptions? options,
UrlDetails urlDetails, string method,
@@ -177,25 +165,29 @@ public class RequestMessage : IRequestMessage
DetectedBodyTypeFromContentType = BodyData?.DetectedBodyTypeFromContentType.ToString();
DetectedCompression = BodyData?.DetectedCompression;
#if MIMEKIT
try
{
BodyAsMimeMessage = MimeKitUtils.GetMimeMessage(BodyData, headers![HttpKnownHeaderNames.ContentType].First());
}
catch
{
// Ignore exception from MimeMessage.Load
}
#endif
Headers = headers?.ToDictionary(header => header.Key, header => new WireMockList<string>(header.Value));
Cookies = cookies;
RawQuery = urlDetails.Url.Query;
Query = QueryStringParser.Parse(RawQuery, options?.QueryParameterMultipleValueSupport);
QueryIgnoreCase = new Dictionary<string, WireMockList<string>>(Query, StringComparer.OrdinalIgnoreCase);
#if USE_ASPNETCORE
ClientCertificate = clientCertificate;
#endif
#if MIMEKIT
try
{
if (MimeKitUtils.TryGetMimeMessage(this, out var mimeMessage))
{
BodyAsMimeMessage = mimeMessage;
}
}
catch
{
// Ignore exception from MimeMessage.Load
}
#endif
}
/// <inheritdoc />

View File

@@ -16,12 +16,17 @@ internal static class ResponseMessageBuilder
{ HttpKnownHeaderNames.ContentType, new WireMockList<string> { WireMockConstants.ContentTypeJson } }
};
internal static ResponseMessage Create(string? message, HttpStatusCode statusCode, Guid? guid = null)
internal static ResponseMessage Create(HttpStatusCode statusCode, string? status, Guid? guid = null)
{
return Create(message, (int)statusCode, guid);
return Create((int)statusCode, status, guid);
}
internal static ResponseMessage Create(string? message, int statusCode = 200, Guid? guid = null)
internal static ResponseMessage Create(int statusCode, string? status, Guid? guid = null)
{
return Create(statusCode, status, null, guid);
}
internal static ResponseMessage Create(int statusCode, string? status, string? error, Guid? guid = null)
{
var response = new ResponseMessage
{
@@ -29,7 +34,7 @@ internal static class ResponseMessageBuilder
Headers = ContentTypeJsonHeaders
};
if (message != null)
if (status != null || error != null)
{
response.BodyData = new BodyData
{
@@ -37,7 +42,8 @@ internal static class ResponseMessageBuilder
BodyAsJson = new StatusModel
{
Guid = guid,
Status = message
Status = status,
Error = error
}
};
}
@@ -45,7 +51,7 @@ internal static class ResponseMessageBuilder
return response;
}
internal static ResponseMessage Create(int statusCode)
internal static ResponseMessage Create(HttpStatusCode statusCode)
{
return new ResponseMessage
{

View File

@@ -139,7 +139,6 @@ internal class MappingConverter
sb.AppendLine(@$" .WithBody(new JsonPartialMatcher(
value: {ToCSharpStringLiteral(jsonPartialMatcher.Value.ToString())},
ignoreCase: {ToCSharpBooleanLiteral(jsonPartialMatcher.IgnoreCase)},
throwException: {ToCSharpBooleanLiteral(jsonPartialMatcher.ThrowException)},
regex: {ToCSharpBooleanLiteral(jsonPartialMatcher.Regex)}
))");
}
@@ -148,7 +147,6 @@ internal class MappingConverter
sb.AppendLine(@$" .WithBody(new JsonPartialWildcardMatcher(
value: {ToCSharpStringLiteral(jsonPartialWildcardMatcher.Value.ToString())},
ignoreCase: {ToCSharpBooleanLiteral(jsonPartialWildcardMatcher.IgnoreCase)},
throwException: {ToCSharpBooleanLiteral(jsonPartialWildcardMatcher.ThrowException)},
regex: {ToCSharpBooleanLiteral(jsonPartialWildcardMatcher.Regex)}
))");
}
@@ -261,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

@@ -46,7 +46,6 @@ internal class MatcherMapper
var matchBehaviour = matcher.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch;
var matchOperator = StringUtils.ParseMatchOperator(matcher.MatchOperator);
bool ignoreCase = matcher.IgnoreCase == true;
bool throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true;
bool useRegexExtended = _settings.UseRegexExtended == true;
bool useRegex = matcher.Regex == true;
@@ -64,51 +63,52 @@ internal class MatcherMapper
throw new NotSupportedException("It's not allowed to use the 'CSharpCodeMatcher' because WireMockServerSettings.AllowCSharpCodeMatcher is not set to 'true'.");
case nameof(LinqMatcher):
return new LinqMatcher(matchBehaviour, throwExceptionWhenMatcherFails, matchOperator, stringPatterns);
return new LinqMatcher(matchBehaviour, matchOperator, stringPatterns);
case nameof(ExactMatcher):
return new ExactMatcher(matchBehaviour, ignoreCase, throwExceptionWhenMatcherFails, matchOperator, stringPatterns);
return new ExactMatcher(matchBehaviour, ignoreCase, matchOperator, stringPatterns);
case nameof(ExactObjectMatcher):
return CreateExactObjectMatcher(matchBehaviour, stringPatterns[0], throwExceptionWhenMatcherFails);
return CreateExactObjectMatcher(matchBehaviour, stringPatterns[0]);
#if GRAPHQL
case nameof(GraphQLMatcher):
return new GraphQLMatcher(stringPatterns[0].GetPattern(), matchBehaviour, throwExceptionWhenMatcherFails, matchOperator);
return new GraphQLMatcher(stringPatterns[0].GetPattern(), matchBehaviour, matchOperator);
#endif
#if MIMEKIT
case nameof(MimePartMatcher):
return CreateMimePartMatcher(matchBehaviour, matcher, throwExceptionWhenMatcherFails);
return CreateMimePartMatcher(matchBehaviour, matcher);
#endif
case nameof(RegexMatcher):
return new RegexMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails, useRegexExtended, matchOperator);
return new RegexMatcher(matchBehaviour, stringPatterns, ignoreCase, useRegexExtended, matchOperator);
case nameof(JsonMatcher):
var valueForJsonMatcher = matcher.Pattern ?? matcher.Patterns;
return new JsonMatcher(matchBehaviour, valueForJsonMatcher!, ignoreCase, throwExceptionWhenMatcherFails);
return new JsonMatcher(matchBehaviour, valueForJsonMatcher!, ignoreCase);
case nameof(JsonPartialMatcher):
var valueForJsonPartialMatcher = matcher.Pattern ?? matcher.Patterns;
return new JsonPartialMatcher(matchBehaviour, valueForJsonPartialMatcher!, ignoreCase, throwExceptionWhenMatcherFails, useRegex);
return new JsonPartialMatcher(matchBehaviour, valueForJsonPartialMatcher!, ignoreCase, useRegex);
case nameof(JsonPartialWildcardMatcher):
var valueForJsonPartialWildcardMatcher = matcher.Pattern ?? matcher.Patterns;
return new JsonPartialWildcardMatcher(matchBehaviour, valueForJsonPartialWildcardMatcher!, ignoreCase, throwExceptionWhenMatcherFails, useRegex);
return new JsonPartialWildcardMatcher(matchBehaviour, valueForJsonPartialWildcardMatcher!, ignoreCase, useRegex);
case nameof(JsonPathMatcher):
return new JsonPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, matchOperator, stringPatterns);
return new JsonPathMatcher(matchBehaviour, matchOperator, stringPatterns);
case nameof(JmesPathMatcher):
return new JmesPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, matchOperator, stringPatterns);
return new JmesPathMatcher(matchBehaviour, matchOperator, stringPatterns);
case nameof(XPathMatcher):
return new XPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, matchOperator, stringPatterns);
var xmlNamespaces = matcher.XmlNamespaceMap;
return new XPathMatcher(matchBehaviour, matchOperator, xmlNamespaces, stringPatterns);
case nameof(WildcardMatcher):
return new WildcardMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails, matchOperator);
return new WildcardMatcher(matchBehaviour, stringPatterns, ignoreCase, matchOperator);
case nameof(ContentTypeMatcher):
return new ContentTypeMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails);
return new ContentTypeMatcher(matchBehaviour, stringPatterns, ignoreCase);
case nameof(SimMetricsMatcher):
SimMetricType type = SimMetricType.Levenstein;
@@ -117,7 +117,7 @@ internal class MatcherMapper
throw new NotSupportedException($"Matcher '{matcherName}' with Type '{matcherType}' is not supported.");
}
return new SimMetricsMatcher(matchBehaviour, stringPatterns, type, throwExceptionWhenMatcherFails);
return new SimMetricsMatcher(matchBehaviour, stringPatterns, type);
default:
if (_settings.CustomMatcherMappings != null && _settings.CustomMatcherMappings.ContainsKey(matcherName))
@@ -160,6 +160,10 @@ internal class MatcherMapper
case JsonPartialWildcardMatcher jsonPartialWildcardMatcher:
model.Regex = jsonPartialWildcardMatcher.Regex;
break;
case XPathMatcher xpathMatcher:
model.XmlNamespaceMap = xpathMatcher.XmlNamespaceMap;
break;
}
switch (matcher)
@@ -236,7 +240,7 @@ internal class MatcherMapper
return EmptyArray<AnyOf<string, StringPattern>>.Value;
}
private static ExactObjectMatcher CreateExactObjectMatcher(MatchBehaviour matchBehaviour, AnyOf<string, StringPattern> stringPattern, bool throwException)
private static ExactObjectMatcher CreateExactObjectMatcher(MatchBehaviour matchBehaviour, AnyOf<string, StringPattern> stringPattern)
{
byte[] bytePattern;
try
@@ -248,18 +252,18 @@ internal class MatcherMapper
throw new ArgumentException($"Matcher 'ExactObjectMatcher' has invalid pattern. The pattern value '{stringPattern}' is not a Base64String.", nameof(stringPattern));
}
return new ExactObjectMatcher(matchBehaviour, bytePattern, throwException);
return new ExactObjectMatcher(matchBehaviour, bytePattern);
}
#if MIMEKIT
private MimePartMatcher CreateMimePartMatcher(MatchBehaviour matchBehaviour, MatcherModel? matcher, bool throwExceptionWhenMatcherFails)
private MimePartMatcher CreateMimePartMatcher(MatchBehaviour matchBehaviour, MatcherModel? matcher)
{
var contentTypeMatcher = Map(matcher?.ContentTypeMatcher) as IStringMatcher;
var contentDispositionMatcher = Map(matcher?.ContentDispositionMatcher) as IStringMatcher;
var contentTransferEncodingMatcher = Map(matcher?.ContentTransferEncodingMatcher) as IStringMatcher;
var contentMatcher = Map(matcher?.ContentMatcher);
return new MimePartMatcher(matchBehaviour, contentTypeMatcher, contentDispositionMatcher, contentTransferEncodingMatcher, contentMatcher, throwExceptionWhenMatcherFails);
return new MimePartMatcher(matchBehaviour, contentTypeMatcher, contentDispositionMatcher, contentTransferEncodingMatcher, contentMatcher);
}
#endif
}

View File

@@ -30,8 +30,8 @@ internal class ProxyMappingConverter
{
var useDefinedRequestMatchers = proxyAndRecordSettings.UseDefinedRequestMatchers;
var excludedHeaders = new List<string>(proxyAndRecordSettings.ExcludedHeaders ?? new string[] { }) { "Cookie" };
var excludedCookies = proxyAndRecordSettings.ExcludedCookies ?? new string[0];
var excludedParams = proxyAndRecordSettings.ExcludedParams ?? new string[0];
var excludedCookies = proxyAndRecordSettings.ExcludedCookies ?? EmptyArray<string>.Value;
var excludedParams = proxyAndRecordSettings.ExcludedParams ?? EmptyArray<string>.Value;
var request = (Request?)mapping?.RequestMatcher;
var clientIPMatcher = request?.GetRequestMessageMatcher<RequestMessageClientIPMatcher>();
@@ -137,7 +137,6 @@ internal class ProxyMappingConverter
}
// Body
bool throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true;
if (useDefinedRequestMatchers && bodyMatcher?.Matchers is not null)
{
newRequest.WithBody(bodyMatcher.Matchers);
@@ -147,16 +146,16 @@ internal class ProxyMappingConverter
switch (requestMessage.BodyData?.DetectedBodyType)
{
case BodyType.Json:
newRequest.WithBody(new JsonMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsJson!, true, throwExceptionWhenMatcherFails));
newRequest.WithBody(new JsonMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsJson!, true));
break;
case BodyType.String:
case BodyType.FormUrlEncoded:
newRequest.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, true, throwExceptionWhenMatcherFails, MatchOperator.Or, requestMessage.BodyData.BodyAsString!));
newRequest.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, true, MatchOperator.Or, requestMessage.BodyData.BodyAsString!));
break;
case BodyType.Bytes:
newRequest.WithBody(new ExactObjectMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsBytes!, throwExceptionWhenMatcherFails));
newRequest.WithBody(new ExactObjectMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsBytes!));
break;
}
}

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

@@ -237,7 +237,6 @@ public partial class WireMockServer
ReadStaticMappings = _settings.ReadStaticMappings,
RequestLogExpirationDuration = _settings.RequestLogExpirationDuration,
SaveUnmatchedRequests = _settings.SaveUnmatchedRequests,
ThrowExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails,
UseRegexExtended = _settings.UseRegexExtended,
WatchStaticMappings = _settings.WatchStaticMappings,
WatchStaticMappingsInSubdirectories = _settings.WatchStaticMappingsInSubdirectories,
@@ -273,7 +272,6 @@ public partial class WireMockServer
_settings.ReadStaticMappings = settings.ReadStaticMappings;
_settings.RequestLogExpirationDuration = settings.RequestLogExpirationDuration;
_settings.SaveUnmatchedRequests = settings.SaveUnmatchedRequests;
_settings.ThrowExceptionWhenMatcherFails = settings.ThrowExceptionWhenMatcherFails;
_settings.UseRegexExtended = settings.UseRegexExtended;
_settings.WatchStaticMappings = settings.WatchStaticMappings;
_settings.WatchStaticMappingsInSubdirectories = settings.WatchStaticMappingsInSubdirectories;
@@ -303,7 +301,7 @@ public partial class WireMockServer
_options.AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate;
#endif
return ResponseMessageBuilder.Create("Settings updated");
return ResponseMessageBuilder.Create(200, "Settings updated");
}
#endregion Settings
@@ -314,7 +312,7 @@ public partial class WireMockServer
if (mapping == null)
{
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
return ResponseMessageBuilder.Create("Mapping not found", HttpStatusCode.NotFound);
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
}
var model = _mappingConverter.ToMappingModel(mapping);
@@ -330,14 +328,14 @@ public partial class WireMockServer
if (code is null)
{
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
return ResponseMessageBuilder.Create("Mapping not found", HttpStatusCode.NotFound);
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
}
return ToResponseMessage(code);
}
_settings.Logger.Warn("HttpStatusCode set to 400");
return ResponseMessageBuilder.Create("GUID is missing", HttpStatusCode.BadRequest);
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "GUID is missing");
}
private static MappingConverterType GetMappingConverterType(IRequestMessage requestMessage)
@@ -365,22 +363,22 @@ public partial class WireMockServer
var mappingModel = DeserializeObject<MappingModel>(requestMessage);
var guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid);
return ResponseMessageBuilder.Create("Mapping added or updated", HttpStatusCode.OK, guidFromPut);
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "Mapping added or updated", guidFromPut);
}
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
return ResponseMessageBuilder.Create("Mapping not found", HttpStatusCode.NotFound);
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
}
private IResponseMessage MappingDelete(IRequestMessage requestMessage)
{
if (TryParseGuidFromRequestMessage(requestMessage, out var guid) && DeleteMapping(guid))
{
return ResponseMessageBuilder.Create("Mapping removed", HttpStatusCode.OK, guid);
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "Mapping removed", guid);
}
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
return ResponseMessageBuilder.Create("Mapping not found", HttpStatusCode.NotFound);
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
}
private static bool TryParseGuidFromRequestMessage(IRequestMessage requestMessage, out Guid guid)
@@ -409,7 +407,7 @@ public partial class WireMockServer
{
SaveStaticMappings();
return ResponseMessageBuilder.Create("Mappings saved to disk");
return ResponseMessageBuilder.Create(200, "Mappings saved to disk");
}
private MappingModel[] ToMappingModels()
@@ -439,22 +437,22 @@ public partial class WireMockServer
if (mappingModels.Length == 1)
{
Guid? guid = ConvertMappingAndRegisterAsRespondProvider(mappingModels[0]);
return ResponseMessageBuilder.Create("Mapping added", 201, guid);
return ResponseMessageBuilder.Create(201, "Mapping added", guid);
}
ConvertMappingsAndRegisterAsRespondProvider(mappingModels);
return ResponseMessageBuilder.Create("Mappings added", 201);
return ResponseMessageBuilder.Create(201, "Mappings added");
}
catch (ArgumentException a)
{
_settings.Logger.Error("HttpStatusCode set to 400 {0}", a);
return ResponseMessageBuilder.Create(a.Message, 400);
return ResponseMessageBuilder.Create(400, a.Message);
}
catch (Exception e)
{
_settings.Logger.Error("HttpStatusCode set to 500 {0}", e);
return ResponseMessageBuilder.Create(e.ToString(), 500);
return ResponseMessageBuilder.Create(500, e.ToString());
}
}
@@ -465,18 +463,18 @@ public partial class WireMockServer
var deletedGuids = MappingsDeleteMappingFromBody(requestMessage);
if (deletedGuids != null)
{
return ResponseMessageBuilder.Create($"Mappings deleted. Affected GUIDs: [{string.Join(", ", deletedGuids.ToArray())}]");
return ResponseMessageBuilder.Create(200, $"Mappings deleted. Affected GUIDs: [{string.Join(", ", deletedGuids.ToArray())}]");
}
// return bad request
return ResponseMessageBuilder.Create("Poorly formed mapping JSON.", 400);
return ResponseMessageBuilder.Create(400, "Poorly formed mapping JSON.");
}
ResetMappings();
ResetScenarios();
return ResponseMessageBuilder.Create("Mappings deleted");
return ResponseMessageBuilder.Create(200, "Mappings deleted");
}
private IEnumerable<Guid>? MappingsDeleteMappingFromBody(IRequestMessage requestMessage)
@@ -528,7 +526,7 @@ public partial class WireMockServer
message = $"{message} and static mappings reloaded";
}
return ResponseMessageBuilder.Create(message);
return ResponseMessageBuilder.Create(200, message);
}
#endregion Mappings
@@ -546,18 +544,18 @@ public partial class WireMockServer
}
_settings.Logger.Warn("HttpStatusCode set to 404 : Request not found");
return ResponseMessageBuilder.Create("Request not found", HttpStatusCode.NotFound);
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Request not found");
}
private IResponseMessage RequestDelete(IRequestMessage requestMessage)
{
if (TryParseGuidFromRequestMessage(requestMessage, out var guid) && DeleteLogEntry(guid))
{
return ResponseMessageBuilder.Create("Request removed");
return ResponseMessageBuilder.Create(200, "Request removed");
}
_settings.Logger.Warn("HttpStatusCode set to 404 : Request not found");
return ResponseMessageBuilder.Create("Request not found", HttpStatusCode.NotFound);
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Request not found");
}
#endregion Request/{guid}
@@ -576,7 +574,7 @@ public partial class WireMockServer
{
ResetLogEntries();
return ResponseMessageBuilder.Create("Requests deleted");
return ResponseMessageBuilder.Create(200, "Requests deleted");
}
#endregion Requests
@@ -623,7 +621,7 @@ public partial class WireMockServer
{
ResetScenarios();
return ResponseMessageBuilder.Create("Scenarios reset");
return ResponseMessageBuilder.Create(200, "Scenarios reset");
}
private IResponseMessage ScenarioReset(IRequestMessage requestMessage)
@@ -633,8 +631,8 @@ public partial class WireMockServer
requestMessage.Path.Split('/').Reverse().Skip(1).First();
return ResetScenario(name) ?
ResponseMessageBuilder.Create("Scenario reset") :
ResponseMessageBuilder.Create($"No scenario found by name '{name}'.", HttpStatusCode.NotFound);
ResponseMessageBuilder.Create(200, "Scenario reset") :
ResponseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'.");
}
#endregion

View File

@@ -1,5 +1,6 @@
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using WireMock.Matchers;
using WireMock.Types;
@@ -25,7 +26,7 @@ namespace WireMock.Server
_settings.FileSystemHandler.WriteFile(filename, requestMessage.BodyAsBytes);
return ResponseMessageBuilder.Create("File created");
return ResponseMessageBuilder.Create(200, "File created");
}
private IResponseMessage FilePut(IRequestMessage requestMessage)
@@ -35,12 +36,12 @@ namespace WireMock.Server
if (!_settings.FileSystemHandler.FileExists(filename))
{
_settings.Logger.Info("The file '{0}' does not exist, updating file will be skipped.", filename);
return ResponseMessageBuilder.Create("File is not found", 404);
return ResponseMessageBuilder.Create(404, "File is not found");
}
_settings.FileSystemHandler.WriteFile(filename, requestMessage.BodyAsBytes);
return ResponseMessageBuilder.Create("File updated");
return ResponseMessageBuilder.Create(200, "File updated");
}
private IResponseMessage FileGet(IRequestMessage requestMessage)
@@ -50,7 +51,7 @@ namespace WireMock.Server
if (!_settings.FileSystemHandler.FileExists(filename))
{
_settings.Logger.Info("The file '{0}' does not exist.", filename);
return ResponseMessageBuilder.Create("File is not found", 404);
return ResponseMessageBuilder.Create(404, "File is not found");
}
byte[] bytes = _settings.FileSystemHandler.ReadFile(filename);
@@ -86,10 +87,10 @@ namespace WireMock.Server
if (!_settings.FileSystemHandler.FileExists(filename))
{
_settings.Logger.Info("The file '{0}' does not exist.", filename);
return ResponseMessageBuilder.Create(404);
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound);
}
return ResponseMessageBuilder.Create(204);
return ResponseMessageBuilder.Create(HttpStatusCode.NoContent);
}
private IResponseMessage FileDelete(IRequestMessage requestMessage)
@@ -99,11 +100,11 @@ namespace WireMock.Server
if (!_settings.FileSystemHandler.FileExists(filename))
{
_settings.Logger.Info("The file '{0}' does not exist.", filename);
return ResponseMessageBuilder.Create("File is not deleted", 404);
return ResponseMessageBuilder.Create(404, "File is not deleted");
}
_settings.FileSystemHandler.DeleteFile(filename);
return ResponseMessageBuilder.Create("File deleted.");
return ResponseMessageBuilder.Create(200, "File deleted.");
}
private string GetFileNameFromRequestMessage(IRequestMessage requestMessage)

View File

@@ -52,7 +52,7 @@ public partial class WireMockServer
if (mappingModels.Length == 1)
{
Guid? guid = ConvertWireMockOrgMappingAndRegisterAsRespondProvider(mappingModels[0]);
return ResponseMessageBuilder.Create("Mapping added", 201, guid);
return ResponseMessageBuilder.Create(201, "Mapping added", guid);
}
foreach (var mappingModel in mappingModels)
@@ -60,17 +60,17 @@ public partial class WireMockServer
ConvertWireMockOrgMappingAndRegisterAsRespondProvider(mappingModel);
}
return ResponseMessageBuilder.Create("Mappings added", 201);
return ResponseMessageBuilder.Create(201, "Mappings added");
}
catch (ArgumentException a)
{
_settings.Logger.Error("HttpStatusCode set to 400 {0}", a);
return ResponseMessageBuilder.Create(a.Message, 400);
return ResponseMessageBuilder.Create(400, a.Message);
}
catch (Exception e)
{
_settings.Logger.Error("HttpStatusCode set to 500 {0}", e);
return ResponseMessageBuilder.Create(e.ToString(), 500);
return ResponseMessageBuilder.Create(500, e.ToString());
}
}

View File

@@ -1,7 +1,7 @@
using System.Net;
#if OPENAPIPARSER
using System;
using System.Linq;
using System.Net;
using WireMock.Net.OpenApiParser;
#endif
@@ -14,16 +14,16 @@ public partial class WireMockServer
#if OPENAPIPARSER
try
{
var mappingModels = new WireMockOpenApiParser().FromText(requestMessage.Body, out var diagnostic);
var mappingModels = new WireMockOpenApiParser().FromText(requestMessage.Body!, out var diagnostic);
return diagnostic.Errors.Any() ? ToJson(diagnostic, false, HttpStatusCode.BadRequest) : ToJson(mappingModels);
}
catch (Exception e)
{
_settings.Logger.Error("HttpStatusCode set to {0} {1}", HttpStatusCode.BadRequest, e);
return ResponseMessageBuilder.Create(e.Message, HttpStatusCode.BadRequest);
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, e.Message);
}
#else
return ResponseMessageBuilder.Create("Not supported for .NETStandard 1.3 and .NET 4.5.2 or lower.", 400);
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "Not supported for .NETStandard 1.3 and .NET 4.5.2 or lower.");
#endif
}
@@ -32,7 +32,7 @@ public partial class WireMockServer
#if OPENAPIPARSER
try
{
var mappingModels = new WireMockOpenApiParser().FromText(requestMessage.Body, out var diagnostic);
var mappingModels = new WireMockOpenApiParser().FromText(requestMessage.Body!, out var diagnostic);
if (diagnostic.Errors.Any())
{
return ToJson(diagnostic, false, HttpStatusCode.BadRequest);
@@ -40,15 +40,15 @@ public partial class WireMockServer
ConvertMappingsAndRegisterAsRespondProvider(mappingModels);
return ResponseMessageBuilder.Create("OpenApi document converted to Mappings", HttpStatusCode.Created);
return ResponseMessageBuilder.Create(HttpStatusCode.Created, "OpenApi document converted to Mappings");
}
catch (Exception e)
{
_settings.Logger.Error("HttpStatusCode set to {0} {1}", HttpStatusCode.BadRequest, e);
return ResponseMessageBuilder.Create(e.Message, HttpStatusCode.BadRequest);
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, e.Message);
}
#else
return ResponseMessageBuilder.Create("Not supported for .NETStandard 1.3 and .NET 4.5.2 or lower.", 400);
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "Not supported for .NETStandard 1.3 and .NET 4.5.2 or lower.");
#endif
}
}

View File

@@ -4,6 +4,7 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using JetBrains.Annotations;
@@ -11,6 +12,7 @@ using Newtonsoft.Json;
using Stef.Validation;
using WireMock.Admin.Mappings;
using WireMock.Authentication;
using WireMock.Constants;
using WireMock.Exceptions;
using WireMock.Handlers;
using WireMock.Http;
@@ -139,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>
@@ -391,7 +418,7 @@ public partial class WireMockServer : IWireMockServer
Given(Request.Create().WithPath("/*").UsingAnyMethod())
.WithGuid(Guid.Parse("90008000-0000-4444-a17e-669cd84f1f05"))
.AtPriority(1000)
.RespondWith(new DynamicResponseProvider(_ => ResponseMessageBuilder.Create("No matching mapping found", 404)));
.RespondWith(new DynamicResponseProvider(_ => ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound)));
}
/// <inheritdoc cref="IWireMockServer.Reset" />

View File

@@ -221,12 +221,6 @@ public class WireMockServerSettings
[PublicAPI]
public bool? HandleRequestsSynchronously { get; set; }
/// <summary>
/// Throw an exception when the <see cref="IMatcher"/> fails because of invalid input. (default set to <c>false</c>).
/// </summary>
[PublicAPI]
public bool? ThrowExceptionWhenMatcherFails { get; set; }
/// <summary>
/// If https is used, these settings can be used to configure the CertificateSettings in case a custom certificate instead the default .NET certificate should be used.
///

View File

@@ -57,7 +57,6 @@ public static class WireMockServerSettingsParser
SaveUnmatchedRequests = parser.GetBoolValue(nameof(WireMockServerSettings.SaveUnmatchedRequests)),
StartAdminInterface = parser.GetBoolValue("StartAdminInterface", true),
StartTimeout = parser.GetIntValue(nameof(WireMockServerSettings.StartTimeout), WireMockServerSettings.DefaultStartTimeout),
ThrowExceptionWhenMatcherFails = parser.GetBoolValue("ThrowExceptionWhenMatcherFails"),
UseRegexExtended = parser.GetBoolValue(nameof(WireMockServerSettings.UseRegexExtended), true),
WatchStaticMappings = parser.GetBoolValue("WatchStaticMappings"),
WatchStaticMappingsInSubdirectories = parser.GetBoolValue("WatchStaticMappingsInSubdirectories"),

View File

@@ -85,27 +85,27 @@ internal static class BodyParser
public static BodyType DetectBodyTypeFromContentType(string? contentTypeValue)
{
if (string.IsNullOrEmpty(contentTypeValue) || !MediaTypeHeaderValue.TryParse(contentTypeValue, out MediaTypeHeaderValue contentType))
if (string.IsNullOrEmpty(contentTypeValue) || !MediaTypeHeaderValue.TryParse(contentTypeValue, out MediaTypeHeaderValue? contentType))
{
return BodyType.Bytes;
}
if (MatchScores.IsPerfect(FormUrlEncodedMatcher.IsMatch(contentType.MediaType)))
if (FormUrlEncodedMatcher.IsMatch(contentType.MediaType).IsPerfect())
{
return BodyType.FormUrlEncoded;
}
if (TextContentTypeMatchers.Any(matcher => MatchScores.IsPerfect(matcher.IsMatch(contentType.MediaType))))
if (TextContentTypeMatchers.Any(matcher => matcher.IsMatch(contentType.MediaType).IsPerfect()))
{
return BodyType.String;
}
if (JsonContentTypesMatchers.Any(matcher => MatchScores.IsPerfect(matcher.IsMatch(contentType.MediaType))))
if (JsonContentTypesMatchers.Any(matcher => matcher.IsMatch(contentType.MediaType).IsPerfect()))
{
return BodyType.Json;
}
if (MultipartContentTypesMatchers.Any(matcher => MatchScores.IsPerfect(matcher.IsMatch(contentType.MediaType))))
if (MultipartContentTypesMatchers.Any(matcher => matcher.IsMatch(contentType.MediaType).IsPerfect()))
{
return BodyType.MultiPart;
}

View File

@@ -1,8 +1,11 @@
#if MIMEKIT
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using MimeKit;
using Stef.Validation;
using WireMock.Http;
using WireMock.Types;
@@ -10,21 +13,39 @@ namespace WireMock.Util;
internal static class MimeKitUtils
{
public static MimeMessage GetMimeMessage(IBodyData? bodyData, string contentTypeHeaderValue)
public static bool TryGetMimeMessage(IRequestMessage requestMessage, [NotNullWhen(true)] out MimeMessage? mimeMessage)
{
var bytes = bodyData?.DetectedBodyType switch
Guard.NotNull(requestMessage);
if (requestMessage.BodyData != null &&
requestMessage.Headers?.TryGetValue(HttpKnownHeaderNames.ContentType, out var contentTypeHeader) == true &&
StartsWithMultiPart(contentTypeHeader) // Only parse when "multipart/mixed"
)
{
// If the body is bytes, use the BodyAsBytes to match on.
BodyType.Bytes => bodyData.BodyAsBytes!,
var bytes = requestMessage.BodyData?.DetectedBodyType switch
{
// If the body is bytes, use the BodyAsBytes to match on.
BodyType.Bytes => requestMessage.BodyData.BodyAsBytes!,
// If the body is a String or MultiPart, use the BodyAsString to match on.
BodyType.String or BodyType.MultiPart => Encoding.UTF8.GetBytes(bodyData.BodyAsString!),
// If the body is a String or MultiPart, use the BodyAsString to match on.
BodyType.String or BodyType.MultiPart => Encoding.UTF8.GetBytes(requestMessage.BodyData.BodyAsString!),
_ => throw new NotSupportedException()
};
_ => throw new NotSupportedException()
};
var fixedBytes = FixBytes(bytes, contentTypeHeaderValue);
return MimeMessage.Load(new MemoryStream(fixedBytes));
var fixedBytes = FixBytes(bytes, contentTypeHeader[0]);
mimeMessage = MimeMessage.Load(new MemoryStream(fixedBytes));
return true;
}
mimeMessage = null;
return false;
}
private static bool StartsWithMultiPart(WireMockList<string> contentTypeHeader)
{
return contentTypeHeader.Any(ct => ct.TrimStart().StartsWith("multipart/", StringComparison.OrdinalIgnoreCase));
}
private static byte[] FixBytes(byte[] bytes, WireMockList<string> contentType)

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

@@ -82,7 +82,6 @@
<ItemGroup Condition=" '$(TargetFramework)' != 'netstandard1.3' ">
<PackageReference Include="XPath2.Extensions" Version="1.1.3" />
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="6.12.2" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.8" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net451' or '$(TargetFramework)' == 'net452' ">

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

@@ -1,3 +1,4 @@
using FluentAssertions;
using NFluent;
using WireMock.Matchers;
using Xunit;
@@ -11,12 +12,13 @@ public class CSharpCodeMatcherTests
{
// Assign
string input = "x";
// Act
var matcher = new CSharpCodeMatcher("return it == \"x\";");
// Act
var score = matcher.IsMatch(input).Score;
// Assert
Check.That(matcher.IsMatch(input)).IsEqualTo(1.0d);
score.Should().Be(MatchScores.Perfect);
}
[Fact]
@@ -24,12 +26,13 @@ public class CSharpCodeMatcherTests
{
// Assign
string input = "y";
// Act
var matcher = new CSharpCodeMatcher("return it == \"x\";");
// Act
var score = matcher.IsMatch(input).Score;
// Assert
Check.That(matcher.IsMatch(input)).IsEqualTo(0.0d);
score.Should().Be(MatchScores.Mismatch);
}
[Fact]
@@ -37,12 +40,13 @@ public class CSharpCodeMatcherTests
{
// Assign
string input = "x";
// Act
var matcher = new CSharpCodeMatcher(MatchBehaviour.RejectOnMatch, MatchOperator.Or, "return it == \"x\";");
// Act
var score = matcher.IsMatch(input).Score;
// Assert
Check.That(matcher.IsMatch(input)).IsEqualTo(0.0d);
score.Should().Be(MatchScores.Mismatch);
}
[Fact]
@@ -55,12 +59,13 @@ public class CSharpCodeMatcherTests
Name = "Test"
};
// Act
var matcher = new CSharpCodeMatcher("return it.Id > 1 && it.Name == \"Test\";");
double match = matcher.IsMatch(input);
// Act
var score = matcher.IsMatch(input).Score;
// Assert
Assert.Equal(1.0, match);
score.Should().Be(MatchScores.Perfect);
}
[Fact]

View File

@@ -1,3 +1,4 @@
using FluentAssertions;
using NFluent;
using WireMock.Matchers;
using Xunit;
@@ -14,7 +15,12 @@ public class ContentTypeMatcherTests
public void ContentTypeMatcher_IsMatchWithIgnoreCaseFalse_Positive(string contentType)
{
var matcher = new ContentTypeMatcher("application/json");
Check.That(matcher.IsMatch(contentType)).IsEqualTo(1.0d);
// Act
var score = matcher.IsMatch(contentType).Score;
// Assert
score.Should().Be(MatchScores.Perfect);
}
[Theory]
@@ -26,7 +32,12 @@ public class ContentTypeMatcherTests
public void ContentTypeMatcher_IsMatchWithIgnoreCaseTrue_Positive(string contentType)
{
var matcher = new ContentTypeMatcher("application/json", true);
Check.That(matcher.IsMatch(contentType)).IsEqualTo(1.0d);
// Act
var score = matcher.IsMatch(contentType).Score;
// Assert
score.Should().Be(MatchScores.Perfect);
}
[Fact]

View File

@@ -39,7 +39,7 @@ public class ExactMatcherTests
var matcher = new ExactMatcher(true, "x");
// Act
double result = matcher.IsMatch("X");
double result = matcher.IsMatch("X").Score;
// Assert
Check.That(result).IsEqualTo(1.0);
@@ -52,7 +52,7 @@ public class ExactMatcherTests
var matcher = new ExactMatcher("x");
// Act
double result = matcher.IsMatch("x");
double result = matcher.IsMatch("x").Score;
// Assert
Check.That(result).IsEqualTo(1.0);
@@ -65,7 +65,7 @@ public class ExactMatcherTests
var matcher = new ExactMatcher("x");
// Act
double result = matcher.IsMatch("y");
double result = matcher.IsMatch("y").Score;
// Assert
Check.That(result).IsEqualTo(0.0);
@@ -78,7 +78,7 @@ public class ExactMatcherTests
var matcher = new ExactMatcher("x", "y");
// Act
double result = matcher.IsMatch("x");
double result = matcher.IsMatch("x").Score;
// Assert
Check.That(result).IsEqualTo(1.0);
@@ -91,7 +91,7 @@ public class ExactMatcherTests
var matcher = new ExactMatcher("x", "y");
// Act
double result = matcher.IsMatch("x");
double result = matcher.IsMatch("x").Score;
// Assert
Check.That(result).IsEqualTo(1.0);
@@ -104,10 +104,10 @@ public class ExactMatcherTests
public void ExactMatcher_IsMatch_WithMultiplePatterns_Average_ReturnsMatch(MatchOperator matchOperator, double score)
{
// Assign
var matcher = new ExactMatcher(MatchBehaviour.AcceptOnMatch, false, false, matchOperator, "x", "y");
var matcher = new ExactMatcher(MatchBehaviour.AcceptOnMatch, false, matchOperator, "x", "y");
// Act
double result = matcher.IsMatch("x");
double result = matcher.IsMatch("x").Score;
// Assert
Check.That(result).IsEqualTo(score);
@@ -120,7 +120,7 @@ public class ExactMatcherTests
var matcher = new ExactMatcher("cat");
// Act
double result = matcher.IsMatch("caR");
double result = matcher.IsMatch("caR").Score;
// Assert
Check.That(result).IsEqualTo(0.0);
@@ -130,10 +130,10 @@ public class ExactMatcherTests
public void ExactMatcher_IsMatch_SinglePattern_AcceptOnMatch()
{
// Assign
var matcher = new ExactMatcher(MatchBehaviour.AcceptOnMatch, false, false, MatchOperator.Or, "cat");
var matcher = new ExactMatcher(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, "cat");
// Act
double result = matcher.IsMatch("cat");
double result = matcher.IsMatch("cat").Score;
// Assert
Check.That(result).IsEqualTo(1.0);
@@ -143,10 +143,10 @@ public class ExactMatcherTests
public void ExactMatcher_IsMatch_SinglePattern_RejectOnMatch()
{
// Assign
var matcher = new ExactMatcher(MatchBehaviour.RejectOnMatch, false, false, MatchOperator.Or, "cat");
var matcher = new ExactMatcher(MatchBehaviour.RejectOnMatch, false, MatchOperator.Or, "cat");
// Act
double result = matcher.IsMatch("cat");
double result = matcher.IsMatch("cat").Score;
// Assert
Check.That(result).IsEqualTo(0.0);

View File

@@ -14,7 +14,7 @@ public class ExactObjectMatcherTests
// Act
var matcher = new ExactObjectMatcher(obj);
string name = matcher.Name;
var name = matcher.Name;
// Assert
Check.That(name).Equals("ExactObjectMatcher");
@@ -28,10 +28,10 @@ public class ExactObjectMatcherTests
// Act
var matcher = new ExactObjectMatcher(new byte[] { 1, 2 });
double result = matcher.IsMatch(checkValue);
var score = matcher.IsMatch(checkValue).Score;
// Assert
Check.That(result).IsEqualTo(1.0);
Check.That(score).IsEqualTo(1.0);
}
[Fact]
@@ -42,10 +42,10 @@ public class ExactObjectMatcherTests
// Act
var matcher = new ExactObjectMatcher(obj);
double result = matcher.IsMatch(new { x = 500, s = "s" });
var score = matcher.IsMatch(new { x = 500, s = "s" }).Score;
// Assert
Check.That(result).IsEqualTo(1.0);
Check.That(score).IsEqualTo(1.0);
}
[Fact]
@@ -56,9 +56,9 @@ public class ExactObjectMatcherTests
// Act
var matcher = new ExactObjectMatcher(MatchBehaviour.RejectOnMatch, obj);
double result = matcher.IsMatch(new { x = 500, s = "s" });
var score = matcher.IsMatch(new { x = 500, s = "s" }).Score;
// Assert
Check.That(result).IsEqualTo(0.0);
Check.That(score).IsEqualTo(0.0);
}
}

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]
@@ -51,13 +55,13 @@ public class GraphQLMatcherTests
var result = matcher.IsMatch(input);
// Assert
result.Should().Be(MatchScores.Perfect);
result.Score.Should().Be(MatchScores.Perfect);
matcher.GetPatterns().Should().Contain(TestSchema);
}
[Fact]
public void GraphQLMatcher_For_ValidSchema_And_CorrectGraphQLQuery_IsMatch()
public void GraphQLMatcher_For_ValidSchema_And_CorrectGraphQL_Query_IsMatch()
{
// Arrange
var input = @"{
@@ -71,7 +75,26 @@ public class GraphQLMatcherTests
var result = matcher.IsMatch(input);
// Assert
result.Should().Be(MatchScores.Perfect);
result.Score.Should().Be(MatchScores.Perfect);
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);
}
@@ -93,7 +116,7 @@ public class GraphQLMatcherTests
var result = matcher.IsMatch(input);
// Assert
result.Should().Be(MatchScores.Mismatch);
result.Score.Should().Be(MatchScores.Mismatch);
}
[Fact]
@@ -111,21 +134,22 @@ public class GraphQLMatcherTests
var result = matcher.IsMatch(input);
// Assert
result.Should().Be(MatchScores.Perfect);
result.Score.Should().Be(MatchScores.Perfect);
}
[Fact]
public void GraphQLMatcher_For_ValidSchema_And_IncorrectQueryWithError_WithThrowExceptionTrue_ThrowsException()
public void GraphQLMatcher_For_ValidSchema_And_IncorrectQueryWithError_WithThrowExceptionTrue_ReturnsError()
{
// Arrange
var input = "{\"query\":\"{\\r\\n studentsX {\\r\\n fullName\\r\\n X\\r\\n }\\r\\n}\"}";
// Act
var matcher = new GraphQLMatcher(TestSchema, MatchBehaviour.AcceptOnMatch, true);
Action action = () => matcher.IsMatch(input);
var matcher = new GraphQLMatcher(TestSchema);
var result = matcher.IsMatch(input);
// Assert
action.Should().Throw<Exception>();
result.Score.Should().Be(MatchScores.Mismatch);
result.Exception!.Message.Should().StartWith("Cannot query field 'studentsX' on type 'Query'");
}
[Fact]

Some files were not shown because too many files have changed in this diff Show More