From 0441c1d85e2cb61eb44bab0051e2b733fc5f0ac6 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 9 Jun 2022 21:31:54 +0200 Subject: [PATCH] Add MatchOperator "Or", "And" and "Average" for patterns (#755) * wip * ... * . * ... * ... * path * url * b * t * client * . * RequestMessageMethodMatcherTests * . * h * . * fix tests * . --- .../MainApp.cs | 2 +- .../Admin/Mappings/BodyModel.cs | 36 +- .../Admin/Mappings/ClientIPModel.cs | 28 +- .../Admin/Mappings/HeaderModel.cs | 52 +- .../Admin/Mappings/MappingModel.cs | 117 ++- .../Admin/Mappings/MatcherModel.cs | 9 + .../Admin/Mappings/PathModel.cs | 28 +- .../Admin/Mappings/RequestModel.cs | 14 + .../Admin/Mappings/UrlModel.cs | 30 +- .../IRequestMessage.cs | 209 +++-- .../IResponseMessage.cs | 2 +- .../Matchers/Request/IRequestMatcher.cs | 2 +- .../Models/IWebhookRequest.cs | 4 +- .../Matchers/CSharpCodeMatcher.cs | 291 ++++--- .../AzureADAuthenticationMatcher.cs | 2 + src/WireMock.Net/Http/HttpClientBuilder.cs | 91 ++- src/WireMock.Net/Http/HttpClientFactory2.cs | 24 + src/WireMock.Net/Http/WebhookSender.cs | 14 +- src/WireMock.Net/IMapping.cs | 204 +++-- .../Matchers/AbstractJsonPartialMatcher.cs | 158 ++-- .../Matchers/ContentTypeMatcher.cs | 131 ++- src/WireMock.Net/Matchers/ExactMatcher.cs | 114 +-- .../Matchers/ExactObjectMatcher.cs | 151 ++-- src/WireMock.Net/Matchers/IObjectMatcher.cs | 4 +- src/WireMock.Net/Matchers/IStringMatcher.cs | 38 +- src/WireMock.Net/Matchers/JSONPathMatcher.cs | 183 +++-- src/WireMock.Net/Matchers/JmesPathMatcher.cs | 35 +- src/WireMock.Net/Matchers/JsonMatcher.cs | 271 ++++--- .../Matchers/JsonPartialMatcher.cs | 65 +- .../Matchers/JsonPartialWildCardMatcher.cs | 65 +- src/WireMock.Net/Matchers/LinqMatcher.cs | 271 ++++--- src/WireMock.Net/Matchers/MatchOperator.cs | 22 + src/WireMock.Net/Matchers/MatchScores.cs | 125 +-- .../Matchers/NotNullOrEmptyMatcher.cs | 118 +-- src/WireMock.Net/Matchers/RegexMatcher.cs | 43 +- .../Request/RequestMessageBodyMatcher.cs | 377 ++++----- .../Request/RequestMessageClientIPMatcher.cs | 135 ++-- .../Request/RequestMessageHeaderMatcher.cs | 212 ++--- .../Request/RequestMessageMethodMatcher.cs | 73 +- .../Request/RequestMessageParamMatcher.cs | 234 +++--- .../Request/RequestMessagePathMatcher.cs | 137 ++-- .../Request/RequestMessageUrlMatcher.cs | 135 ++-- .../Matchers/SimMetricsMatcher.cs | 264 +++--- src/WireMock.Net/Matchers/WildcardMatcher.cs | 23 +- src/WireMock.Net/Matchers/XPathMatcher.cs | 24 +- src/WireMock.Net/Models/StringPattern.cs | 25 +- src/WireMock.Net/Models/WebhookRequest.cs | 4 +- src/WireMock.Net/Proxy/ProxyHelper.cs | 156 ++-- .../RequestBuilders/IBodyRequestBuilder.cs | 125 ++- .../IClientIPRequestBuilder.cs | 70 +- .../RequestBuilders/IHeadersRequestBuilder.cs | 141 ++-- .../RequestBuilders/IMethodRequestBuilder.cs | 171 ++-- .../RequestBuilders/IParamsRequestBuilder.cs | 180 ++--- .../IUrlAndPathRequestBuilder.cs | 128 +-- .../RequestBuilders/Request.ClientIP.cs | 48 ++ .../RequestBuilders/Request.UsingMethods.cs | 183 ++--- .../RequestBuilders/Request.WithBody.cs | 149 ++-- .../RequestBuilders/Request.WithHeaders.cs | 119 ++- .../RequestBuilders/Request.WithParam.cs | 6 +- .../RequestBuilders/Request.WithPath.cs | 48 ++ .../RequestBuilders/Request.WithUrl.cs | 49 ++ src/WireMock.Net/RequestBuilders/Request.cs | 208 ++--- src/WireMock.Net/RequestMessage.cs | 14 +- .../ResponseBuilders/Response.WithProxy.cs | 53 +- src/WireMock.Net/ResponseMessage.cs | 2 +- .../Serialization/MappingConverter.cs | 398 ++++----- .../Serialization/MatcherMapper.cs | 358 +++++---- .../Server/RespondWithAProvider.cs | 445 +++++----- .../Server/WireMockServer.Admin.cs | 316 +------- .../Server/WireMockServer.ConvertMapping.cs | 318 ++++++++ src/WireMock.Net/Server/WireMockServer.cs | 39 + .../Settings/ProxyAndRecordSettings.cs | 81 +- src/WireMock.Net/Settings/WebProxySettings.cs | 39 +- .../Handlebars/HandlebarsContextFactory.cs | 46 +- src/WireMock.Net/Transformers/ITransformer.cs | 14 +- src/WireMock.Net/Transformers/Transformer.cs | 466 +++++------ src/WireMock.Net/Util/JsonUtils.cs | 19 +- src/WireMock.Net/Util/StringUtils.cs | 73 +- src/WireMock.Net/WireMock.Net.csproj | 9 +- .../Matchers/CSharpCodeMatcherTests.cs | 2 +- .../Matchers/ExactMatcherTests.cs | 35 +- .../Matchers/JmesPathMatcherTests.cs | 6 +- .../Matchers/JsonPathMatcherTests.cs | 289 ++++--- .../Matchers/WildcardMatcherTest.cs | 22 +- .../Matchers/XPathMatcherTests.cs | 87 +- .../Plugin/PluginLoaderTests.cs | 43 +- .../RequestBuilderWithUrlTests.cs | 4 +- .../RequestMessageBodyMatcherTests.cs | 124 ++- .../RequestMessageHeaderMatcherTests.cs | 6 +- .../RequestMessageMethodMatcherTests.cs | 61 ++ .../RequestMessageParamMatcherTests.cs | 4 +- .../Serialization/CustomPathParamMatcher.cs | 201 ++--- .../Serialization/MatcherModelMapperTests.cs | 28 +- test/WireMock.Net.Tests/TestUtils.cs | 111 ++- .../WireMock.Net.Tests/WireMockServerTests.cs | 757 +++++++++--------- 95 files changed, 5736 insertions(+), 5111 deletions(-) create mode 100644 src/WireMock.Net/Http/HttpClientFactory2.cs create mode 100644 src/WireMock.Net/Matchers/MatchOperator.cs create mode 100644 src/WireMock.Net/RequestBuilders/Request.ClientIP.cs create mode 100644 src/WireMock.Net/RequestBuilders/Request.WithPath.cs create mode 100644 src/WireMock.Net/RequestBuilders/Request.WithUrl.cs create mode 100644 src/WireMock.Net/Server/WireMockServer.ConvertMapping.cs create mode 100644 test/WireMock.Net.Tests/RequestMatchers/RequestMessageMethodMatcherTests.cs diff --git a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs index 0ad7dfa6..9ea422e8 100644 --- a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs +++ b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs @@ -79,7 +79,7 @@ namespace WireMock.Net.ConsoleApplication // server.AllowPartialMapping(); - server.Given(Request.Create().WithPath("/mypath").UsingPost()) + server.Given(Request.Create().WithPath(MatchOperator.Or, "/mypath", "/mypath1", "/mypath2").UsingPost()) .RespondWith(Response.Create() .WithHeader("Content-Type", "application/json") .WithBodyAsJson("{{JsonPath.SelectToken request.body \"..name\"}}") diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/BodyModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/BodyModel.cs index 52d2f7f0..86dd4381 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/BodyModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/BodyModel.cs @@ -1,19 +1,27 @@ -namespace WireMock.Admin.Mappings +namespace WireMock.Admin.Mappings; + +/// +/// Body Model +/// +[FluentBuilder.AutoGenerateBuilder] +public class BodyModel { /// - /// Body Model + /// Gets or sets the matcher. /// - [FluentBuilder.AutoGenerateBuilder] - public class BodyModel - { - /// - /// Gets or sets the matcher. - /// - public MatcherModel? Matcher { get; set; } + public MatcherModel? Matcher { get; set; } - /// - /// Gets or sets the matchers. - /// - public MatcherModel[]? Matchers { get; set; } - } + /// + /// Gets or sets the matchers. + /// + public MatcherModel[]? Matchers { get; set; } + + /// + /// The Operator to use when matchers are defined. [Optional] + /// - null = Same as "or". + /// - "or" = Only one pattern should match. + /// - "and" = All patterns should match. + /// - "average" = The average value from all patterns. + /// + public string? MatchOperator { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/ClientIPModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/ClientIPModel.cs index af1dc24f..bba78a94 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/ClientIPModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/ClientIPModel.cs @@ -1,14 +1,22 @@ -namespace WireMock.Admin.Mappings +namespace WireMock.Admin.Mappings; + +/// +/// ClientIPModel +/// +[FluentBuilder.AutoGenerateBuilder] +public class ClientIPModel { /// - /// ClientIPModel + /// Gets or sets the matchers. /// - [FluentBuilder.AutoGenerateBuilder] - public class ClientIPModel - { - /// - /// Gets or sets the matchers. - /// - public MatcherModel[] Matchers { get; set; } - } + public MatcherModel[]? Matchers { get; set; } + + /// + /// The Operator to use when matchers are defined. [Optional] + /// - null = Same as "or". + /// - "or" = Only one pattern should match. + /// - "and" = All patterns should match. + /// - "average" = The average value from all patterns. + /// + public string? MatchOperator { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/HeaderModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/HeaderModel.cs index 724c11b9..ea8882c4 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/HeaderModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/HeaderModel.cs @@ -1,31 +1,39 @@ using System.Collections.Generic; -namespace WireMock.Admin.Mappings +namespace WireMock.Admin.Mappings; + +/// +/// Header Model +/// +[FluentBuilder.AutoGenerateBuilder] +public class HeaderModel { /// - /// Header Model + /// Gets or sets the name. /// - [FluentBuilder.AutoGenerateBuilder] - public class HeaderModel - { - /// - /// Gets or sets the name. - /// - public string Name { get; set; } = null!; + public string Name { get; set; } = null!; - /// - /// Gets or sets the matchers. - /// - public IList? Matchers { get; set; } + /// + /// Gets or sets the matchers. + /// + public IList? Matchers { get; set; } - /// - /// Gets or sets the ignore case. - /// - public bool? IgnoreCase { get; set; } + /// + /// Gets or sets the ignore case. + /// + public bool? IgnoreCase { get; set; } - /// - /// Reject on match. - /// - public bool? RejectOnMatch { get; set; } - } + /// + /// Reject on match. + /// + public bool? RejectOnMatch { get; set; } + + /// + /// The Operator to use when matchers are defined. [Optional] + /// - null = Same as "or". + /// - "or" = Only one pattern should match. + /// - "and" = All patterns should match. + /// - "average" = The average value from all patterns. + /// + public string? MatchOperator { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs index e9544e8e..693143a7 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/MappingModel.cs @@ -1,78 +1,77 @@ using System; using WireMock.Models; -namespace WireMock.Admin.Mappings +namespace WireMock.Admin.Mappings; + +/// +/// MappingModel +/// +[FluentBuilder.AutoGenerateBuilder] +public class MappingModel { /// - /// MappingModel + /// Gets or sets the unique identifier. /// - [FluentBuilder.AutoGenerateBuilder] - public class MappingModel - { - /// - /// Gets or sets the unique identifier. - /// - public Guid? Guid { get; set; } + public Guid? Guid { get; set; } - /// - /// Gets or sets the TimeSettings when which this mapping should be used. - /// - public TimeSettingsModel TimeSettings { get; set; } + /// + /// Gets or sets the TimeSettings when which this mapping should be used. + /// + public TimeSettingsModel? TimeSettings { get; set; } - /// - /// The unique title. - /// - public string Title { get; set; } + /// + /// The unique title. + /// + public string? Title { get; set; } - /// - /// The description. - /// - public string Description { get; set; } + /// + /// The description. + /// + public string? Description { get; set; } - /// - /// The priority. (A low value means higher priority.) - /// - public int? Priority { get; set; } + /// + /// The priority. (A low value means higher priority.) + /// + public int? Priority { get; set; } - /// - /// The Scenario. - /// - public string Scenario { get; set; } + /// + /// The Scenario. + /// + public string? Scenario { get; set; } - /// - /// Execution state condition for the current mapping. - /// - public string WhenStateIs { get; set; } + /// + /// Execution state condition for the current mapping. + /// + public string? WhenStateIs { get; set; } - /// - /// The next state which will be signaled after the current mapping execution. - /// In case the value is null state will not be changed. - /// - public string SetStateTo { get; set; } + /// + /// The next state which will be signaled after the current mapping execution. + /// In case the value is null state will not be changed. + /// + public string? SetStateTo { get; set; } - /// - /// The request model. - /// - public RequestModel Request { get; set; } + /// + /// The request model. + /// + public RequestModel Request { get; set; } - /// - /// The response model. - /// - public ResponseModel Response { get; set; } + /// + /// The response model. + /// + public ResponseModel Response { get; set; } - /// - /// Saves this mapping as a static mapping file. - /// - public bool? SaveToFile { get; set; } + /// + /// Saves this mapping as a static mapping file. + /// + public bool? SaveToFile { get; set; } - /// - /// The Webhook. - /// - public WebhookModel Webhook { get; set; } + /// + /// The Webhook. + /// + public WebhookModel? Webhook { get; set; } - /// - /// The Webhooks. - /// - public WebhookModel[] Webhooks { get; set; } - } + /// + /// The Webhooks. + /// + public WebhookModel[]? Webhooks { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs index 1c1cda31..51e08357 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/MatcherModel.cs @@ -35,5 +35,14 @@ namespace WireMock.Admin.Mappings /// Reject on match. /// public bool? RejectOnMatch { get; set; } + + /// + /// The Operator to use when multiple patterns are defined. Optional. + /// - null = Same as "or". + /// - "or" = Only one pattern should match. + /// - "and" = All patterns should match. + /// - "average" = The average value from all patterns. + /// + public string? MatchOperator { get; set; } } } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/PathModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/PathModel.cs index 8122c898..9413d3fe 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/PathModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/PathModel.cs @@ -1,14 +1,22 @@ -namespace WireMock.Admin.Mappings +namespace WireMock.Admin.Mappings; + +/// +/// PathModel +/// +[FluentBuilder.AutoGenerateBuilder] +public class PathModel { /// - /// PathModel + /// Gets or sets the matchers. /// - [FluentBuilder.AutoGenerateBuilder] - public class PathModel - { - /// - /// Gets or sets the matchers. - /// - public MatcherModel[]? Matchers { get; set; } - } + public MatcherModel[]? Matchers { get; set; } + + /// + /// The Operator to use when matchers are defined. [Optional] + /// - null = Same as "or". + /// - "or" = Only one pattern should match. + /// - "and" = All patterns should match. + /// - "average" = The average value from all patterns. + /// + public string? MatchOperator { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/RequestModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/RequestModel.cs index 3f396685..b3b25cd8 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/RequestModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/RequestModel.cs @@ -28,6 +28,20 @@ public class RequestModel /// public string[]? Methods { get; set; } + /// + /// Reject on match for Methods. + /// + public bool? MethodsRejectOnMatch { get; set; } + + /// + /// The Operator to use when Methods are defined. [Optional] + /// - null = Same as "or". + /// - "or" = Only one method should match. + /// - "and" = All methods should match. + /// - "average" = The average value from all methods. + /// + public string? MethodsMatchOperator { get; set; } + /// /// Gets or sets the Headers. /// diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/UrlModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/UrlModel.cs index eb6df454..57348abf 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/UrlModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/UrlModel.cs @@ -1,14 +1,22 @@ -namespace WireMock.Admin.Mappings +namespace WireMock.Admin.Mappings; + +/// +/// UrlModel +/// +[FluentBuilder.AutoGenerateBuilder] +public class UrlModel { /// - /// UrlModel + /// Gets or sets the matchers. /// - [FluentBuilder.AutoGenerateBuilder] - public class UrlModel - { - /// - /// Gets or sets the matchers. - /// - public MatcherModel[] Matchers { get; set; } - } -} + public MatcherModel[]? Matchers { get; set; } + + /// + /// The Operator to use when matchers are defined. [Optional] + /// - null = Same as "or". + /// - "or" = Only one pattern should match. + /// - "and" = All patterns should match. + /// - "average" = The average value from all patterns. + /// + public string? MatchOperator { get; set; } +} \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/IRequestMessage.cs b/src/WireMock.Net.Abstractions/IRequestMessage.cs index 39cd3f86..ca80ec89 100644 --- a/src/WireMock.Net.Abstractions/IRequestMessage.cs +++ b/src/WireMock.Net.Abstractions/IRequestMessage.cs @@ -3,136 +3,135 @@ using System.Collections.Generic; using WireMock.Types; using WireMock.Util; -namespace WireMock +namespace WireMock; + +/// +/// IRequestMessage +/// +public interface IRequestMessage { /// - /// IRequestMessage + /// Gets the Client IP Address. /// - public interface IRequestMessage - { - /// - /// Gets the Client IP Address. - /// - string ClientIP { get; } + string ClientIP { get; } - /// - /// Gets the url (relative). - /// - string Url { get; } + /// + /// Gets the url (relative). + /// + string Url { get; } - /// - /// Gets the AbsoluteUrl. - /// - string AbsoluteUrl { get; } + /// + /// Gets the AbsoluteUrl. + /// + string AbsoluteUrl { get; } - /// - /// The ProxyUrl (if a proxy is used). - /// - string ProxyUrl { get; set; } + /// + /// The ProxyUrl (if a proxy is used). + /// + string ProxyUrl { get; set; } - /// - /// Gets the DateTime. - /// - DateTime DateTime { get; } + /// + /// Gets the DateTime. + /// + DateTime DateTime { get; } - /// - /// Gets the path (relative). - /// - string Path { get; } + /// + /// Gets the path (relative). + /// + string Path { get; } - /// - /// Gets the AbsolutePath. - /// - string AbsolutePath { get; } + /// + /// Gets the AbsolutePath. + /// + string AbsolutePath { get; } - /// - /// Gets the path segments. - /// - string[] PathSegments { get; } + /// + /// Gets the path segments. + /// + string[] PathSegments { get; } - /// - /// Gets the absolute path segments. - /// - string[] AbsolutePathSegments { get; } + /// + /// Gets the absolute path segments. + /// + string[] AbsolutePathSegments { get; } - /// - /// Gets the method. - /// - string Method { get; } + /// + /// Gets the method. + /// + string Method { get; } - /// - /// Gets the headers. - /// - IDictionary> Headers { get; } + /// + /// Gets the headers. + /// + IDictionary>? Headers { get; } - /// - /// Gets the cookies. - /// - IDictionary Cookies { get; } + /// + /// Gets the cookies. + /// + IDictionary? Cookies { get; } - /// - /// Gets the query. - /// - IDictionary> Query { get; } + /// + /// Gets the query. + /// + IDictionary>? Query { get; } - /// - /// Gets the raw query. - /// - string RawQuery { get; } + /// + /// Gets the raw query. + /// + string RawQuery { get; } - /// - /// The body. - /// - IBodyData? BodyData { get; } + /// + /// The body. + /// + IBodyData? BodyData { get; } - /// - /// The original body as string. Convenience getter for Handlebars. - /// - string Body { get; } + /// + /// The original body as string. Convenience getter for Handlebars. + /// + string Body { get; } - /// - /// The body (as JSON object). Convenience getter for Handlebars. - /// - object BodyAsJson { get; } + /// + /// The body (as JSON object). Convenience getter for Handlebars. + /// + object BodyAsJson { get; } - /// - /// The body (as bytearray). Convenience getter for Handlebars. - /// - byte[] BodyAsBytes { get; } + /// + /// The body (as bytearray). Convenience getter for Handlebars. + /// + byte[] BodyAsBytes { get; } - /// - /// The detected body type. Convenience getter for Handlebars. - /// - string DetectedBodyType { get; } + /// + /// The detected body type. Convenience getter for Handlebars. + /// + string DetectedBodyType { get; } - /// - /// The detected body type from the Content-Type header. Convenience getter for Handlebars. - /// - string DetectedBodyTypeFromContentType { get; } + /// + /// The detected body type from the Content-Type header. Convenience getter for Handlebars. + /// + string DetectedBodyTypeFromContentType { get; } - /// - /// The detected compression from the Content-Encoding header. Convenience getter for Handlebars. - /// - string DetectedCompression { get; } + /// + /// The detected compression from the Content-Encoding header. Convenience getter for Handlebars. + /// + string DetectedCompression { get; } - /// - /// Gets the Host - /// - string Host { get; } + /// + /// Gets the Host + /// + string Host { get; } - /// - /// Gets the protocol - /// - string Protocol { get; } + /// + /// Gets the protocol + /// + string Protocol { get; } - /// - /// Gets the port - /// - int Port { get; } + /// + /// Gets the port + /// + int Port { get; } - /// - /// Gets the origin - /// - string Origin { get; } - } + /// + /// Gets the origin + /// + string Origin { get; } } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/IResponseMessage.cs b/src/WireMock.Net.Abstractions/IResponseMessage.cs index b249171d..467edc88 100644 --- a/src/WireMock.Net.Abstractions/IResponseMessage.cs +++ b/src/WireMock.Net.Abstractions/IResponseMessage.cs @@ -38,7 +38,7 @@ namespace WireMock /// /// Gets the headers. /// - IDictionary> Headers { get; } + IDictionary>? Headers { get; } /// /// Gets or sets the status code. diff --git a/src/WireMock.Net.Abstractions/Matchers/Request/IRequestMatcher.cs b/src/WireMock.Net.Abstractions/Matchers/Request/IRequestMatcher.cs index c3375d8f..cc2e904b 100644 --- a/src/WireMock.Net.Abstractions/Matchers/Request/IRequestMatcher.cs +++ b/src/WireMock.Net.Abstractions/Matchers/Request/IRequestMatcher.cs @@ -15,6 +15,6 @@ namespace WireMock.Matchers.Request /// /// A value between 0.0 - 1.0 of the similarity. /// - double GetMatchingScore([NotNull] IRequestMessage requestMessage, [NotNull] IRequestMatchResult requestMatchResult); + double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult); } } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Models/IWebhookRequest.cs b/src/WireMock.Net.Abstractions/Models/IWebhookRequest.cs index 0f6e9be5..caa6df46 100644 --- a/src/WireMock.Net.Abstractions/Models/IWebhookRequest.cs +++ b/src/WireMock.Net.Abstractions/Models/IWebhookRequest.cs @@ -22,12 +22,12 @@ namespace WireMock.Models /// /// The Headers to send. /// - IDictionary> Headers { get; } + IDictionary>? Headers { get; } /// /// The body to send. /// - IBodyData BodyData { get; set; } + IBodyData? BodyData { get; set; } /// /// Use Transformer. diff --git a/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs b/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs index 9173b93a..d8eb739d 100644 --- a/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs +++ b/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs @@ -3,138 +3,137 @@ using System.Linq; using System.Reflection; using System.Text; using AnyOfTypes; -using JetBrains.Annotations; using Newtonsoft.Json.Linq; +using Stef.Validation; using WireMock.Exceptions; using WireMock.Extensions; using WireMock.Models; -using Stef.Validation; -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// CSharpCode / CS-Script Matcher +/// +/// +internal class CSharpCodeMatcher : ICSharpCodeMatcher { - /// - /// CSharpCode / CS-Script Matcher - /// - /// - internal class CSharpCodeMatcher : ICSharpCodeMatcher + private const string TemplateForIsMatchWithString = "public class CodeHelper {{ public bool IsMatch(string it) {{ {0} }} }}"; + + private const string TemplateForIsMatchWithDynamic = "public class CodeHelper {{ public bool IsMatch(dynamic it) {{ {0} }} }}"; + + private readonly string[] _usings = { - private const string TemplateForIsMatchWithString = "public class CodeHelper {{ public bool IsMatch(string it) {{ {0} }} }}"; + "System", + "System.Linq", + "System.Collections.Generic", + "Microsoft.CSharp", + "Newtonsoft.Json.Linq" + }; - private const string TemplateForIsMatchWithDynamic = "public class CodeHelper {{ public bool IsMatch(dynamic it) {{ {0} }} }}"; + public MatchBehaviour MatchBehaviour { get; } - private readonly string[] _usings = - { - "System", - "System.Linq", - "System.Collections.Generic", - "Microsoft.CSharp", - "Newtonsoft.Json.Linq" - }; - - public MatchBehaviour MatchBehaviour { get; } - - /// - public bool ThrowException { get; } - - private readonly AnyOf[] _patterns; - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - public CSharpCodeMatcher([NotNull] params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, patterns) + /// + public bool ThrowException { get; } + + private readonly AnyOf[] _patterns; + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + public CSharpCodeMatcher(params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The to use. (default = "Or") + /// The patterns. + public CSharpCodeMatcher(MatchBehaviour matchBehaviour, MatchOperator matchOperator = MatchOperator.Or, params AnyOf[] patterns) + { + _patterns = Guard.NotNull(patterns); + MatchBehaviour = matchBehaviour; + ThrowException = false; + MatchOperator = matchOperator; + } + + public double IsMatch(string input) + { + return IsMatchInternal(input); + } + + public double IsMatch(object? input) + { + return IsMatchInternal(input); + } + + public double IsMatchInternal(object? input) + { + double match = MatchScores.Mismatch; + + if (input != null) { + match = MatchScores.ToScore(_patterns.Select(pattern => IsMatch(input, pattern.GetPattern())).ToArray(), MatchOperator); } - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The patterns. - public CSharpCodeMatcher(MatchBehaviour matchBehaviour, [NotNull] params AnyOf[] patterns) - { - Guard.NotNull(patterns, nameof(patterns)); + return MatchBehaviourHelper.Convert(MatchBehaviour, match); + } - MatchBehaviour = matchBehaviour; - ThrowException = false; - _patterns = patterns; - } + private bool IsMatch(dynamic input, string pattern) + { + bool isMatchWithString = input is string; + var inputValue = isMatchWithString ? input : JObject.FromObject(input); + string source = GetSourceForIsMatchWithString(pattern, isMatchWithString); - public double IsMatch(string input) - { - return IsMatchInternal(input); - } - - public double IsMatch(object input) - { - return IsMatchInternal(input); - } - - public double IsMatchInternal(object input) - { - double match = MatchScores.Mismatch; - - if (input != null) - { - match = MatchScores.ToScore(_patterns.Select(pattern => IsMatch(input, pattern.GetPattern()))); - } - - return MatchBehaviourHelper.Convert(MatchBehaviour, match); - } - - private bool IsMatch(dynamic input, string pattern) - { - bool isMatchWithString = input is string; - var inputValue = isMatchWithString ? input : JObject.FromObject(input); - string source = GetSourceForIsMatchWithString(pattern, isMatchWithString); - - object result = null; + object? result; #if (NET451 || NET452) - var compilerParams = new System.CodeDom.Compiler.CompilerParameters + var compilerParams = new System.CodeDom.Compiler.CompilerParameters + { + GenerateInMemory = true, + GenerateExecutable = false, + ReferencedAssemblies = { - GenerateInMemory = true, - GenerateExecutable = false, - ReferencedAssemblies = - { - "System.dll", - "System.Core.dll", - "Microsoft.CSharp.dll", - "Newtonsoft.Json.dll" - } - }; - - using (var codeProvider = new Microsoft.CSharp.CSharpCodeProvider()) - { - var compilerResults = codeProvider.CompileAssemblyFromSource(compilerParams, source); - - if (compilerResults.Errors.Count != 0) - { - var errors = from System.CodeDom.Compiler.CompilerError er in compilerResults.Errors select er.ToString(); - throw new WireMockException(string.Join(", ", errors)); - } - - object helper = compilerResults.CompiledAssembly.CreateInstance("CodeHelper"); - if (helper == null) - { - throw new WireMockException("CSharpCodeMatcher: Unable to create instance from WireMock.CodeHelper"); - } - - var methodInfo = helper.GetType().GetMethod("IsMatch"); - if (methodInfo == null) - { - throw new WireMockException("CSharpCodeMatcher: Unable to find method 'IsMatch' in WireMock.CodeHelper"); - } - - try - { - result = methodInfo.Invoke(helper, new[] { inputValue }); - } - catch (Exception ex) - { - throw new WireMockException("CSharpCodeMatcher: Unable to call method 'IsMatch' in WireMock.CodeHelper", ex); - } + "System.dll", + "System.Core.dll", + "Microsoft.CSharp.dll", + "Newtonsoft.Json.dll" } + }; + + using (var codeProvider = new Microsoft.CSharp.CSharpCodeProvider()) + { + var compilerResults = codeProvider.CompileAssemblyFromSource(compilerParams, source); + + if (compilerResults.Errors.Count != 0) + { + var errors = from System.CodeDom.Compiler.CompilerError er in compilerResults.Errors select er.ToString(); + throw new WireMockException(string.Join(", ", errors)); + } + + var helper = compilerResults.CompiledAssembly?.CreateInstance("CodeHelper"); + if (helper == null) + { + throw new WireMockException("CSharpCodeMatcher: Unable to create instance from WireMock.CodeHelper"); + } + + var methodInfo = helper.GetType().GetMethod("IsMatch"); + if (methodInfo == null) + { + throw new WireMockException("CSharpCodeMatcher: Unable to find method 'IsMatch' in WireMock.CodeHelper"); + } + + try + { + result = methodInfo.Invoke(helper, new[] { inputValue }); + } + catch (Exception ex) + { + throw new WireMockException("CSharpCodeMatcher: Unable to call method 'IsMatch' in WireMock.CodeHelper", ex); + } + } #elif (NET46 || NET461) dynamic script; try @@ -169,11 +168,7 @@ namespace WireMock.Matchers dynamic script; try { -//#if NETSTANDARD2_0 -// script = csscript.GenericExtensions.CreateObject(assembly, "*"); -//#else script = CSScripting.ReflectionExtensions.CreateObject(assembly, "*"); -//#endif } catch (Exception ex) { @@ -191,38 +186,40 @@ namespace WireMock.Matchers #else throw new NotSupportedException("The 'CSharpCodeMatcher' cannot be used in netstandard 1.3"); #endif - try - { - return (bool)result; - } - catch - { - throw new WireMockException($"Unable to cast result '{result}' to bool"); - } - } - - private string GetSourceForIsMatchWithString(string pattern, bool isMatchWithString) + try { - string template = isMatchWithString ? TemplateForIsMatchWithString : TemplateForIsMatchWithDynamic; - - var stringBuilder = new StringBuilder(); - foreach (string @using in _usings) - { - stringBuilder.AppendLine($"using {@using};"); - } - stringBuilder.AppendLine(); - stringBuilder.AppendFormat(template, pattern); - - return stringBuilder.ToString(); + return (bool)result; } - - /// - public AnyOf[] GetPatterns() + catch { - return _patterns; + throw new WireMockException($"Unable to cast result '{result}' to bool"); } - - /// - public string Name => "CSharpCodeMatcher"; } + + private string GetSourceForIsMatchWithString(string pattern, bool isMatchWithString) + { + string template = isMatchWithString ? TemplateForIsMatchWithString : TemplateForIsMatchWithDynamic; + + var stringBuilder = new StringBuilder(); + foreach (string @using in _usings) + { + stringBuilder.AppendLine($"using {@using};"); + } + stringBuilder.AppendLine(); + stringBuilder.AppendFormat(template, pattern); + + return stringBuilder.ToString(); + } + + /// + public AnyOf[] GetPatterns() + { + return _patterns; + } + + /// + public MatchOperator MatchOperator { get; } + + /// + public string Name => "CSharpCodeMatcher"; } \ No newline at end of file diff --git a/src/WireMock.Net/Authentication/AzureADAuthenticationMatcher.cs b/src/WireMock.Net/Authentication/AzureADAuthenticationMatcher.cs index e8177f18..46b56705 100644 --- a/src/WireMock.Net/Authentication/AzureADAuthenticationMatcher.cs +++ b/src/WireMock.Net/Authentication/AzureADAuthenticationMatcher.cs @@ -39,6 +39,8 @@ namespace WireMock.Authentication return new AnyOf[0]; } + public MatchOperator MatchOperator { get; } = MatchOperator.Or; + public double IsMatch(string input) { var token = Regex.Replace(input, BearerPrefix, string.Empty, RegexOptions.IgnoreCase); diff --git a/src/WireMock.Net/Http/HttpClientBuilder.cs b/src/WireMock.Net/Http/HttpClientBuilder.cs index 5b270c35..70142cc2 100644 --- a/src/WireMock.Net/Http/HttpClientBuilder.cs +++ b/src/WireMock.Net/Http/HttpClientBuilder.cs @@ -3,64 +3,63 @@ using System.Net.Http; using WireMock.HttpsCertificate; using WireMock.Settings; -namespace WireMock.Http +namespace WireMock.Http; + +internal static class HttpClientBuilder { - internal static class HttpClientBuilder + public static HttpClient Build(HttpClientSettings settings) { - public static HttpClient Build(HttpClientSettings settings) - { #if NETSTANDARD || NETCOREAPP3_1 || NET5_0 || NET6_0 - var handler = new HttpClientHandler - { - CheckCertificateRevocationList = false, - SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls, - ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true, - AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate - }; + var handler = new HttpClientHandler + { + CheckCertificateRevocationList = false, + SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls, + ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true, + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate + }; #elif NET46 - var handler = new HttpClientHandler - { - ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true, - AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate - }; + var handler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true, + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate + }; #else - var handler = new WebRequestHandler - { - ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true, - AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate - }; + var handler = new WebRequestHandler + { + ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true, + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate + }; #endif - if (!string.IsNullOrEmpty(settings.ClientX509Certificate2ThumbprintOrSubjectName)) + if (!string.IsNullOrEmpty(settings.ClientX509Certificate2ThumbprintOrSubjectName)) + { + handler.ClientCertificateOptions = ClientCertificateOption.Manual; + + var x509Certificate2 = CertificateLoader.LoadCertificate(settings.ClientX509Certificate2ThumbprintOrSubjectName); + handler.ClientCertificates.Add(x509Certificate2); + } + + handler.AllowAutoRedirect = settings.AllowAutoRedirect == true; + + // If UseCookies enabled, httpClient ignores Cookie header + handler.UseCookies = false; + + if (settings.WebProxySettings != null) + { + handler.UseProxy = true; + + handler.Proxy = new WebProxy(settings.WebProxySettings.Address); + if (settings.WebProxySettings.UserName != null && settings.WebProxySettings.Password != null) { - handler.ClientCertificateOptions = ClientCertificateOption.Manual; - - var x509Certificate2 = CertificateLoader.LoadCertificate(settings.ClientX509Certificate2ThumbprintOrSubjectName); - handler.ClientCertificates.Add(x509Certificate2); - } - - handler.AllowAutoRedirect = settings.AllowAutoRedirect == true; - - // If UseCookies enabled, httpClient ignores Cookie header - handler.UseCookies = false; - - if (settings.WebProxySettings != null) - { - handler.UseProxy = true; - - handler.Proxy = new WebProxy(settings.WebProxySettings.Address); - if (settings.WebProxySettings.UserName != null && settings.WebProxySettings.Password != null) - { - handler.Proxy.Credentials = new NetworkCredential(settings.WebProxySettings.UserName, settings.WebProxySettings.Password); - } + handler.Proxy.Credentials = new NetworkCredential(settings.WebProxySettings.UserName, settings.WebProxySettings.Password); } + } #if !NETSTANDARD1_3 - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; - ServicePointManager.ServerCertificateValidationCallback = (message, cert, chain, errors) => true; + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; + ServicePointManager.ServerCertificateValidationCallback = (message, cert, chain, errors) => true; #endif - return new HttpClient(handler); - } + return HttpClientFactory2.Create(handler); } } \ No newline at end of file diff --git a/src/WireMock.Net/Http/HttpClientFactory2.cs b/src/WireMock.Net/Http/HttpClientFactory2.cs new file mode 100644 index 00000000..03690172 --- /dev/null +++ b/src/WireMock.Net/Http/HttpClientFactory2.cs @@ -0,0 +1,24 @@ +using System.Net.Http; + +namespace WireMock.Http; + +internal static class HttpClientFactory2 +{ + public static HttpClient Create(params DelegatingHandler[] handlers) + { +#if NETSTANDARD1_3 + return new HttpClient(); +#else + return HttpClientFactory.Create(handlers); +#endif + } + + public static HttpClient Create(HttpMessageHandler innerHandler, params DelegatingHandler[] handlers) + { +#if NETSTANDARD1_3 + return new HttpClient(innerHandler); +#else + return HttpClientFactory.Create(innerHandler, handlers); +#endif + } +} \ No newline at end of file diff --git a/src/WireMock.Net/Http/WebhookSender.cs b/src/WireMock.Net/Http/WebhookSender.cs index 628807a1..cf3d9272 100644 --- a/src/WireMock.Net/Http/WebhookSender.cs +++ b/src/WireMock.Net/Http/WebhookSender.cs @@ -26,15 +26,15 @@ namespace WireMock.Http _settings = settings ?? throw new ArgumentNullException(nameof(settings)); } - public Task SendAsync([NotNull] HttpClient client, [NotNull] IWebhookRequest request, [NotNull] IRequestMessage originalRequestMessage, [NotNull] IResponseMessage originalResponseMessage) + public Task SendAsync(HttpClient client, IWebhookRequest request, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage) { - Guard.NotNull(client, nameof(client)); - Guard.NotNull(request, nameof(request)); - Guard.NotNull(originalRequestMessage, nameof(originalRequestMessage)); - Guard.NotNull(originalResponseMessage, nameof(originalResponseMessage)); + Guard.NotNull(client); + Guard.NotNull(request); + Guard.NotNull(originalRequestMessage); + Guard.NotNull(originalResponseMessage); - IBodyData bodyData; - IDictionary> headers; + IBodyData? bodyData; + IDictionary>? headers; if (request.UseTransformer == true) { ITransformer responseMessageTransformer; diff --git a/src/WireMock.Net/IMapping.cs b/src/WireMock.Net/IMapping.cs index e088cf91..b49dfa93 100644 --- a/src/WireMock.Net/IMapping.cs +++ b/src/WireMock.Net/IMapping.cs @@ -1,4 +1,3 @@ -using JetBrains.Annotations; using System; using System.Threading.Tasks; using WireMock.Matchers.Request; @@ -6,130 +5,125 @@ using WireMock.Models; using WireMock.ResponseProviders; using WireMock.Settings; -namespace WireMock +namespace WireMock; + +/// +/// The IMapping interface. +/// +public interface IMapping { /// - /// The IMapping interface. + /// Gets the unique identifier. /// - public interface IMapping - { - /// - /// Gets the unique identifier. - /// - Guid Guid { get; } + Guid Guid { get; } - /// - /// Gets the TimeSettings (Start, End and TTL). - /// - ITimeSettings TimeSettings { get; } + /// + /// Gets the TimeSettings (Start, End and TTL). + /// + ITimeSettings TimeSettings { get; } - /// - /// Gets the unique title. - /// - string Title { get; } + /// + /// Gets the unique title. + /// + string Title { get; } - /// - /// Gets the description. - /// - string Description { get; } + /// + /// Gets the description. + /// + string Description { get; } - /// - /// The full filename path for this mapping (only defined for static mappings). - /// - string Path { get; set; } + /// + /// The full filename path for this mapping (only defined for static mappings). + /// + string Path { get; set; } - /// - /// Gets the priority. (A low value means higher priority.) - /// - int Priority { get; } + /// + /// Gets the priority. (A low value means higher priority.) + /// + int Priority { get; } - /// - /// Scenario. - /// - [CanBeNull] - string Scenario { get; } + /// + /// Scenario. + /// + string? Scenario { get; } - /// - /// Execution state condition for the current mapping. - /// - [CanBeNull] - string ExecutionConditionState { get; } + /// + /// Execution state condition for the current mapping. + /// + string? ExecutionConditionState { get; } - /// - /// The next state which will be signaled after the current mapping execution. - /// In case the value is null, state will not be changed. - /// - [CanBeNull] - string NextState { get; } + /// + /// The next state which will be signaled after the current mapping execution. + /// In case the value is null, state will not be changed. + /// + string? NextState { get; } - /// - /// The number of times this match should be matched before the state will be changed to the next state. - /// - [CanBeNull] - int? StateTimes { get; } + /// + /// The number of times this match should be matched before the state will be changed to the next state. + /// + int? StateTimes { get; } - /// - /// The Request matcher. - /// - IRequestMatcher RequestMatcher { get; } + /// + /// The Request matcher. + /// + IRequestMatcher RequestMatcher { get; } - /// - /// The Provider. - /// - IResponseProvider Provider { get; } + /// + /// The Provider. + /// + IResponseProvider Provider { get; } - /// - /// The WireMockServerSettings. - /// - WireMockServerSettings Settings { get; } + /// + /// The WireMockServerSettings. + /// + WireMockServerSettings Settings { get; } - /// - /// Is State started ? - /// - bool IsStartState { get; } + /// + /// Is State started ? + /// + bool IsStartState { get; } - /// - /// Gets a value indicating whether this mapping is an Admin Interface. - /// - /// - /// true if this mapping is an Admin Interface; otherwise, false. - /// - bool IsAdminInterface { get; } + /// + /// Gets a value indicating whether this mapping is an Admin Interface. + /// + /// + /// true if this mapping is an Admin Interface; otherwise, false. + /// + bool IsAdminInterface { get; } - /// - /// Gets a value indicating whether this mapping is a Proxy Mapping. - /// - /// - /// true if this mapping is a Proxy Mapping; otherwise, false. - /// - bool IsProxy { get; } + /// + /// Gets a value indicating whether this mapping is a Proxy Mapping. + /// + /// + /// true if this mapping is a Proxy Mapping; otherwise, false. + /// + bool IsProxy { get; } - /// - /// Gets a value indicating whether this mapping to be logged. - /// - /// - /// true if this mapping to be logged; otherwise, false. - /// - bool LogMapping { get; } + /// + /// Gets a value indicating whether this mapping to be logged. + /// + /// + /// true if this mapping to be logged; otherwise, false. + /// + bool LogMapping { get; } - /// - /// The Webhooks. - /// - IWebhook[] Webhooks { get; } + /// + /// The Webhooks. + /// + IWebhook[]? Webhooks { get; } - /// - /// ProvideResponseAsync - /// - /// The request message. - /// The including a new (optional) . - Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(IRequestMessage requestMessage); + /// + /// ProvideResponseAsync + /// + /// The request message. + /// The including a new (optional) . + Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(IRequestMessage requestMessage); - /// - /// Gets the RequestMatchResult based on the RequestMessage. - /// - /// The request message. - /// The Next State. - /// The . - IRequestMatchResult GetRequestMatchResult(IRequestMessage requestMessage, [CanBeNull] string nextState); - } + /// + /// Gets the RequestMatchResult based on the RequestMessage. + /// + /// The request message. + /// The Next State. + /// The . + IRequestMatchResult GetRequestMatchResult(IRequestMessage requestMessage, string? nextState); } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/AbstractJsonPartialMatcher.cs b/src/WireMock.Net/Matchers/AbstractJsonPartialMatcher.cs index 70d610f1..2f3fb27a 100644 --- a/src/WireMock.Net/Matchers/AbstractJsonPartialMatcher.cs +++ b/src/WireMock.Net/Matchers/AbstractJsonPartialMatcher.cs @@ -1,89 +1,87 @@ using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using Newtonsoft.Json.Linq; -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// Generic AbstractJsonPartialMatcher +/// +public abstract class AbstractJsonPartialMatcher : JsonMatcher { /// - /// Generic AbstractJsonPartialMatcher + /// Initializes a new instance of the class. /// - public abstract class AbstractJsonPartialMatcher : JsonMatcher + /// The string value to check for equality. + /// Ignore the case from the PropertyName and PropertyValue (string only). + /// Throw an exception when the internal matching fails because of invalid input. + protected AbstractJsonPartialMatcher(string value, bool ignoreCase = false, bool throwException = false) + : base(value, ignoreCase, throwException) { - /// - /// Initializes a new instance of the class. - /// - /// The string value to check for equality. - /// Ignore the case from the PropertyName and PropertyValue (string only). - /// Throw an exception when the internal matching fails because of invalid input. - protected AbstractJsonPartialMatcher([NotNull] string value, bool ignoreCase = false, bool throwException = false) - : base(value, ignoreCase, throwException) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The object value to check for equality. - /// Ignore the case from the PropertyName and PropertyValue (string only). - /// Throw an exception when the internal matching fails because of invalid input. - protected AbstractJsonPartialMatcher([NotNull] object value, bool ignoreCase = false, bool throwException = false) - : base(value, ignoreCase, throwException) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The value to check for equality. - /// Ignore the case from the PropertyName and PropertyValue (string only). - /// Throw an exception when the internal matching fails because of invalid input. - protected AbstractJsonPartialMatcher(MatchBehaviour matchBehaviour, [NotNull] object value, bool ignoreCase = false, bool throwException = false) - : base(matchBehaviour, value, ignoreCase, throwException) - { - } - - /// - protected override bool IsMatch(JToken value, JToken input) - { - if (value == null || value == input) - { - return true; - } - - if (input == null || value.Type != input.Type) - { - return false; - } - - switch (value.Type) - { - case JTokenType.Object: - var nestedValues = value.ToObject>(); - return nestedValues?.Any() != true || - nestedValues.All(pair => IsMatch(pair.Value, input.SelectToken(pair.Key))); - - case JTokenType.Array: - var valuesArray = value.ToObject(); - var tokenArray = input.ToObject(); - - if (valuesArray?.Any() != true) - { - return true; - } - - return tokenArray?.Any() == true && - valuesArray.All(subFilter => tokenArray.Any(subToken => IsMatch(subFilter, subToken))); - - default: - return IsMatch(value.ToString(), input.ToString()); - } - } - - /// - /// Check if two strings are a match (matching can be done exact or wildcard) - /// - protected abstract bool IsMatch(string value, string input); } -} + + /// + /// Initializes a new instance of the class. + /// + /// The object value to check for equality. + /// Ignore the case from the PropertyName and PropertyValue (string only). + /// Throw an exception when the internal matching fails because of invalid input. + protected AbstractJsonPartialMatcher(object value, bool ignoreCase = false, bool throwException = false) + : base(value, ignoreCase, throwException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The value to check for equality. + /// Ignore the case from the PropertyName and PropertyValue (string only). + /// Throw an exception when the internal matching fails because of invalid input. + protected AbstractJsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool throwException = false) + : base(matchBehaviour, value, ignoreCase, throwException) + { + } + + /// + protected override bool IsMatch(JToken? value, JToken? input) + { + if (value == null || value == input) + { + return true; + } + + if (input == null || value.Type != input.Type) + { + return false; + } + + switch (value.Type) + { + case JTokenType.Object: + var nestedValues = value.ToObject>(); + return nestedValues?.Any() != true || + nestedValues.All(pair => IsMatch(pair.Value, input.SelectToken(pair.Key))); + + case JTokenType.Array: + var valuesArray = value.ToObject(); + var tokenArray = input.ToObject(); + + if (valuesArray?.Any() != true) + { + return true; + } + + return tokenArray?.Any() == true && + valuesArray.All(subFilter => tokenArray.Any(subToken => IsMatch(subFilter, subToken))); + + default: + return IsMatch(value.ToString(), input.ToString()); + } + } + + /// + /// Check if two strings are a match (matching can be done exact or wildcard) + /// + protected abstract bool IsMatch(string value, string input); +} \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/ContentTypeMatcher.cs b/src/WireMock.Net/Matchers/ContentTypeMatcher.cs index d4ac069e..a5d21a82 100644 --- a/src/WireMock.Net/Matchers/ContentTypeMatcher.cs +++ b/src/WireMock.Net/Matchers/ContentTypeMatcher.cs @@ -3,75 +3,74 @@ using AnyOfTypes; using JetBrains.Annotations; using WireMock.Models; -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// ContentTypeMatcher which accepts also all charsets +/// +/// +public class ContentTypeMatcher : WildcardMatcher { + private readonly AnyOf[] _patterns; + /// - /// ContentTypeMatcher which accepts also all charsets + /// Initializes a new instance of the class. /// - /// - public class ContentTypeMatcher : WildcardMatcher + /// The pattern. + /// IgnoreCase (default false) + public ContentTypeMatcher([NotNull] AnyOf pattern, bool ignoreCase = false) : this(new[] { pattern }, ignoreCase) { - private readonly AnyOf[] _patterns; - - /// - /// Initializes a new instance of the class. - /// - /// The pattern. - /// IgnoreCase (default false) - public ContentTypeMatcher([NotNull] AnyOf pattern, bool ignoreCase = false) : this(new[] { pattern }, ignoreCase) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The pattern. - /// IgnoreCase (default false) - public ContentTypeMatcher(MatchBehaviour matchBehaviour, [NotNull] AnyOf pattern, bool ignoreCase = false) : this(matchBehaviour, new[] { pattern }, ignoreCase) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - /// IgnoreCase (default false) - public ContentTypeMatcher([NotNull] AnyOf[] patterns, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The patterns. - /// IgnoreCase (default false) - /// Throw an exception when the internal matching fails because of invalid input. - public ContentTypeMatcher(MatchBehaviour matchBehaviour, [NotNull] AnyOf[] patterns, bool ignoreCase = false, bool throwException = false) : - base(matchBehaviour, patterns, ignoreCase, throwException) - { - _patterns = patterns; - } - - /// - public override double IsMatch(string input) - { - if (string.IsNullOrEmpty(input) || !MediaTypeHeaderValue.TryParse(input, out MediaTypeHeaderValue contentType)) - { - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch); - } - - return base.IsMatch(contentType.MediaType); - } - - /// - public override AnyOf[] GetPatterns() - { - return _patterns; - } - - /// - public override string Name => "ContentTypeMatcher"; } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The pattern. + /// IgnoreCase (default false) + public ContentTypeMatcher(MatchBehaviour matchBehaviour, [NotNull] AnyOf pattern, bool ignoreCase = false) : this(matchBehaviour, new[] { pattern }, ignoreCase) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + /// IgnoreCase (default false) + public ContentTypeMatcher(AnyOf[] patterns, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The patterns. + /// IgnoreCase (default false) + /// Throw an exception when the internal matching fails because of invalid input. + public ContentTypeMatcher(MatchBehaviour matchBehaviour, AnyOf[] patterns, bool ignoreCase = false, bool throwException = false) : + base(matchBehaviour, patterns, ignoreCase, throwException) + { + _patterns = patterns; + } + + /// + public override double IsMatch(string? input) + { + if (string.IsNullOrEmpty(input) || !MediaTypeHeaderValue.TryParse(input, out MediaTypeHeaderValue contentType)) + { + return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch); + } + + return base.IsMatch(contentType.MediaType); + } + + /// + public override AnyOf[] GetPatterns() + { + return _patterns; + } + + /// + public override string Name => "ContentTypeMatcher"; } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/ExactMatcher.cs b/src/WireMock.Net/Matchers/ExactMatcher.cs index e9f301e7..70fb1c92 100644 --- a/src/WireMock.Net/Matchers/ExactMatcher.cs +++ b/src/WireMock.Net/Matchers/ExactMatcher.cs @@ -1,67 +1,69 @@ using System.Linq; using AnyOfTypes; -using JetBrains.Annotations; +using Stef.Validation; using WireMock.Extensions; using WireMock.Models; -using Stef.Validation; -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// ExactMatcher +/// +/// +public class ExactMatcher : IStringMatcher { + private readonly AnyOf[] _values; + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + public bool ThrowException { get; } + /// - /// ExactMatcher + /// Initializes a new instance of the class. /// - /// - public class ExactMatcher : IStringMatcher + /// The values. + public ExactMatcher(params AnyOf[] values) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, values) { - private readonly AnyOf[] _values; - - /// - public MatchBehaviour MatchBehaviour { get; } - - /// - public bool ThrowException { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The values. - public ExactMatcher([NotNull] params AnyOf[] values) : this(MatchBehaviour.AcceptOnMatch, false, values) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// Throw an exception when the internal matching fails because of invalid input. - /// The values. - public ExactMatcher(MatchBehaviour matchBehaviour, bool throwException = false, [NotNull] params AnyOf[] values) - { - Guard.NotNull(values, nameof(values)); - - MatchBehaviour = matchBehaviour; - ThrowException = throwException; - _values = values; - } - - /// - public double IsMatch(string input) - { - if (_values.Length == 1) - { - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(_values[0].GetPattern() == input)); - } - - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(_values.Select(v => v.GetPattern()).Contains(input))); - } - - /// - public AnyOf[] GetPatterns() - { - return _values; - } - - /// - public string Name => "ExactMatcher"; } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// Throw an exception when the internal matching fails because of invalid input. + /// The to use. (default = "Or") + /// The values. + public ExactMatcher( + MatchBehaviour matchBehaviour, + bool throwException = false, + MatchOperator matchOperator = MatchOperator.Or, + params AnyOf[] values) + { + _values = Guard.NotNull(values); + + MatchBehaviour = matchBehaviour; + ThrowException = throwException; + MatchOperator = matchOperator; + } + + /// + public double IsMatch(string? input) + { + double score = MatchScores.ToScore(_values.Select(v => v.GetPattern() == input).ToArray(), MatchOperator); + return MatchBehaviourHelper.Convert(MatchBehaviour, score); + } + + /// + public AnyOf[] GetPatterns() + { + return _values; + } + + /// + public MatchOperator MatchOperator { get; } + + /// + public string Name => "ExactMatcher"; } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/ExactObjectMatcher.cs b/src/WireMock.Net/Matchers/ExactObjectMatcher.cs index fe116668..8b4e3786 100644 --- a/src/WireMock.Net/Matchers/ExactObjectMatcher.cs +++ b/src/WireMock.Net/Matchers/ExactObjectMatcher.cs @@ -1,83 +1,86 @@ -using JetBrains.Annotations; using System.Linq; using Stef.Validation; -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// ExactObjectMatcher +/// +/// +public class ExactObjectMatcher : IObjectMatcher { /// - /// ExactObjectMatcher + /// Gets the value as object. /// - /// - public class ExactObjectMatcher : IObjectMatcher + public object? ValueAsObject { get; } + + /// + /// Gets the value as byte[]. + /// + public byte[]? ValueAsBytes { get; } + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + public bool ThrowException { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The value. + public ExactObjectMatcher(object value) : this(MatchBehaviour.AcceptOnMatch, value) { - /// - /// Gets the value as object. - /// - public object ValueAsObject { get; } - - /// - /// Gets the value as byte[]. - /// - public byte[] ValueAsBytes { get; } - - /// - public MatchBehaviour MatchBehaviour { get; } - - /// - public bool ThrowException { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The value. - public ExactObjectMatcher([NotNull] object value) : this(MatchBehaviour.AcceptOnMatch, value) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The value. - public ExactObjectMatcher(MatchBehaviour matchBehaviour, [NotNull] object value) - { - Guard.NotNull(value, nameof(value)); - - ValueAsObject = value; - MatchBehaviour = matchBehaviour; - } - - /// - /// Initializes a new instance of the class. - /// - /// The value. - public ExactObjectMatcher([NotNull] byte[] value) : this(MatchBehaviour.AcceptOnMatch, value) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// Throw an exception when the internal matching fails because of invalid input. - /// The value. - public ExactObjectMatcher(MatchBehaviour matchBehaviour, [NotNull] byte[] value, bool throwException = false) - { - Guard.NotNull(value, nameof(value)); - - MatchBehaviour = matchBehaviour; - ThrowException = throwException; - ValueAsBytes = value; - } - - /// - public double IsMatch(object input) - { - bool equals = ValueAsObject != null ? Equals(ValueAsObject, input) : ValueAsBytes.SequenceEqual((byte[])input); - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(equals)); - } - - /// - public string Name => "ExactObjectMatcher"; } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The value. + public ExactObjectMatcher(MatchBehaviour matchBehaviour, object value) + { + ValueAsObject = Guard.NotNull(value); + MatchBehaviour = matchBehaviour; + } + + /// + /// Initializes a new instance of the class. + /// + /// The value. + public ExactObjectMatcher(byte[] value) : this(MatchBehaviour.AcceptOnMatch, value) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// Throw an exception when the internal matching fails because of invalid input. + /// The value. + public ExactObjectMatcher(MatchBehaviour matchBehaviour, byte[] value, bool throwException = false) + { + ValueAsBytes = Guard.NotNull(value); + MatchBehaviour = matchBehaviour; + ThrowException = throwException; + } + + /// + public double IsMatch(object? input) + { + bool equals = false; + if (ValueAsObject != null) + { + equals = Equals(ValueAsObject, input); + } + else if (input != null) + { + equals = ValueAsBytes?.SequenceEqual((byte[])input) == true; + } + + return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(equals)); + } + + /// + public string Name => "ExactObjectMatcher"; } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/IObjectMatcher.cs b/src/WireMock.Net/Matchers/IObjectMatcher.cs index 883e246d..233782a9 100644 --- a/src/WireMock.Net/Matchers/IObjectMatcher.cs +++ b/src/WireMock.Net/Matchers/IObjectMatcher.cs @@ -1,4 +1,4 @@ -namespace WireMock.Matchers +namespace WireMock.Matchers { /// /// IObjectMatcher @@ -10,6 +10,6 @@ /// /// The input. /// A value between 0.0 - 1.0 of the similarity. - double IsMatch(object input); + double IsMatch(object? input); } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/IStringMatcher.cs b/src/WireMock.Net/Matchers/IStringMatcher.cs index 126373fe..bf815eee 100644 --- a/src/WireMock.Net/Matchers/IStringMatcher.cs +++ b/src/WireMock.Net/Matchers/IStringMatcher.cs @@ -1,25 +1,29 @@ using AnyOfTypes; using WireMock.Models; -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// IStringMatcher +/// +/// +public interface IStringMatcher : IMatcher { /// - /// IStringMatcher + /// Determines whether the specified input is match. /// - /// - public interface IStringMatcher : IMatcher - { - /// - /// Determines whether the specified input is match. - /// - /// The input. - /// A value between 0.0 - 1.0 of the similarity. - double IsMatch(string input); + /// The input. + /// A value between 0.0 - 1.0 of the similarity. + double IsMatch(string? input); - /// - /// Gets the patterns. - /// - /// Patterns - AnyOf[] GetPatterns(); - } + /// + /// Gets the patterns. + /// + /// Patterns + AnyOf[] GetPatterns(); + + /// + /// The . + /// + MatchOperator MatchOperator { get; } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/JSONPathMatcher.cs b/src/WireMock.Net/Matchers/JSONPathMatcher.cs index ba951d52..1e6be6f5 100644 --- a/src/WireMock.Net/Matchers/JSONPathMatcher.cs +++ b/src/WireMock.Net/Matchers/JSONPathMatcher.cs @@ -1,121 +1,126 @@ using System.Linq; using AnyOfTypes; -using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Stef.Validation; using WireMock.Extensions; using WireMock.Models; -using Stef.Validation; -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// JsonPathMatcher +/// +/// +/// +public class JsonPathMatcher : IStringMatcher, IObjectMatcher { + private readonly AnyOf[] _patterns; + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + public bool ThrowException { get; } + /// - /// JsonPathMatcher + /// Initializes a new instance of the class. /// - /// - /// - public class JsonPathMatcher : IStringMatcher, IObjectMatcher + /// The patterns. + public JsonPathMatcher(params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns.ToAnyOfPatterns()) { - private readonly AnyOf[] _patterns; + } - /// - public MatchBehaviour MatchBehaviour { get; } + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + public JsonPathMatcher(params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns) + { + } - /// - public bool ThrowException { get; } + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// Throw an exception when the internal matching fails because of invalid input. + /// The to use. (default = "Or") + /// The patterns. + public JsonPathMatcher( + MatchBehaviour matchBehaviour, + bool throwException = false, + MatchOperator matchOperator = MatchOperator.Or, + params AnyOf[] patterns) + { + _patterns = Guard.NotNull(patterns); + MatchBehaviour = matchBehaviour; + ThrowException = throwException; + MatchOperator = matchOperator; + } - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - public JsonPathMatcher([NotNull] params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, patterns.ToAnyOfPatterns()) + /// + public double IsMatch(string? input) + { + double match = MatchScores.Mismatch; + if (input != null) { - } - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - public JsonPathMatcher([NotNull] params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, patterns) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// Throw an exception when the internal matching fails because of invalid input. - /// The patterns. - public JsonPathMatcher(MatchBehaviour matchBehaviour, bool throwException = false, [NotNull] params AnyOf[] patterns) - { - Guard.NotNull(patterns, nameof(patterns)); - - MatchBehaviour = matchBehaviour; - ThrowException = throwException; - _patterns = patterns; - } - - /// - public double IsMatch(string input) - { - double match = MatchScores.Mismatch; - if (input != null) + try { - try + var jToken = JToken.Parse(input); + match = IsMatch(jToken); + } + catch (JsonException) + { + if (ThrowException) { - var jToken = JToken.Parse(input); - match = IsMatch(jToken); - } - catch (JsonException) - { - if (ThrowException) - { - throw; - } + throw; } } - - return MatchBehaviourHelper.Convert(MatchBehaviour, match); } - /// - public double IsMatch(object input) - { - double match = MatchScores.Mismatch; + return MatchBehaviourHelper.Convert(MatchBehaviour, match); + } - // When input is null or byte[], return Mismatch. - if (input != null && !(input is byte[])) + /// + public double IsMatch(object? input) + { + double match = MatchScores.Mismatch; + + // When input is null or byte[], return Mismatch. + if (input != null && !(input is byte[])) + { + try { - try + // Check if JToken or object + JToken jToken = input as JToken ?? JObject.FromObject(input); + match = IsMatch(jToken); + } + catch (JsonException) + { + if (ThrowException) { - // Check if JToken or object - JToken jToken = input is JToken token ? token : JObject.FromObject(input); - match = IsMatch(jToken); - } - catch (JsonException) - { - if (ThrowException) - { - throw; - } + throw; } } - - return MatchBehaviourHelper.Convert(MatchBehaviour, match); } - /// - public AnyOf[] GetPatterns() - { - return _patterns; - } + return MatchBehaviourHelper.Convert(MatchBehaviour, match); + } - /// - public string Name => "JsonPathMatcher"; + /// + public AnyOf[] GetPatterns() + { + return _patterns; + } - private double IsMatch(JToken jToken) - { - return MatchScores.ToScore(_patterns.Select(pattern => jToken.SelectToken(pattern.GetPattern()) != null)); - } + /// + public MatchOperator MatchOperator { get; } + + /// + public string Name => "JsonPathMatcher"; + + private double IsMatch(JToken jToken) + { + return MatchScores.ToScore(_patterns.Select(pattern => jToken.SelectToken(pattern.GetPattern()) != null).ToArray(), MatchOperator); } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/JmesPathMatcher.cs b/src/WireMock.Net/Matchers/JmesPathMatcher.cs index 41e8117a..84ad7925 100644 --- a/src/WireMock.Net/Matchers/JmesPathMatcher.cs +++ b/src/WireMock.Net/Matchers/JmesPathMatcher.cs @@ -1,11 +1,10 @@ +using System.Linq; using AnyOfTypes; using DevLab.JmesPath; -using JetBrains.Annotations; using Newtonsoft.Json; -using System.Linq; +using Stef.Validation; using WireMock.Extensions; using WireMock.Models; -using Stef.Validation; namespace WireMock.Matchers { @@ -26,7 +25,7 @@ namespace WireMock.Matchers /// Initializes a new instance of the class. /// /// The patterns. - public JmesPathMatcher([NotNull] params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, patterns.ToAnyOfPatterns()) + public JmesPathMatcher(params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns.ToAnyOfPatterns()) { } @@ -34,7 +33,7 @@ namespace WireMock.Matchers /// Initializes a new instance of the class. /// /// The patterns. - public JmesPathMatcher([NotNull] params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, patterns) + public JmesPathMatcher(params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns) { } @@ -42,8 +41,10 @@ namespace WireMock.Matchers /// Initializes a new instance of the class. /// /// Throw an exception when the internal matching fails because of invalid input. + /// The to use. /// The patterns. - public JmesPathMatcher(bool throwException = false, [NotNull] params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, throwException, patterns) + public JmesPathMatcher(bool throwException = false, MatchOperator matchOperator = MatchOperator.Or, params AnyOf[] patterns) : + this(MatchBehaviour.AcceptOnMatch, throwException, matchOperator, patterns) { } @@ -52,25 +53,30 @@ namespace WireMock.Matchers /// /// The match behaviour. /// Throw an exception when the internal matching fails because of invalid input. + /// The to use. /// The patterns. - public JmesPathMatcher(MatchBehaviour matchBehaviour, bool throwException = false, [NotNull] params AnyOf[] patterns) + public JmesPathMatcher( + MatchBehaviour matchBehaviour, + bool throwException = false, + MatchOperator matchOperator = MatchOperator.Or, + params AnyOf[] patterns) { - Guard.NotNull(patterns, nameof(patterns)); - + _patterns = Guard.NotNull(patterns); MatchBehaviour = matchBehaviour; ThrowException = throwException; - _patterns = patterns; + MatchOperator = matchOperator; } /// - public double IsMatch(string input) + public double IsMatch(string? input) { double match = MatchScores.Mismatch; if (input != null) { try { - match = MatchScores.ToScore(_patterns.Select(pattern => bool.Parse(new JmesPath().Transform(input, pattern.GetPattern())))); + var results = _patterns.Select(pattern => bool.Parse(new JmesPath().Transform(input, pattern.GetPattern()))).ToArray(); + match = MatchScores.ToScore(results, MatchOperator); } catch (JsonException) { @@ -85,7 +91,7 @@ namespace WireMock.Matchers } /// - public double IsMatch(object input) + public double IsMatch(object? input) { double match = MatchScores.Mismatch; @@ -105,6 +111,9 @@ namespace WireMock.Matchers return _patterns; } + /// + public MatchOperator MatchOperator { get; } + /// public string Name => "JmesPathMatcher"; } diff --git a/src/WireMock.Net/Matchers/JsonMatcher.cs b/src/WireMock.Net/Matchers/JsonMatcher.cs index eed4a542..28810b2e 100644 --- a/src/WireMock.Net/Matchers/JsonMatcher.cs +++ b/src/WireMock.Net/Matchers/JsonMatcher.cs @@ -6,164 +6,163 @@ using Newtonsoft.Json.Linq; using Stef.Validation; using WireMock.Util; -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// JsonMatcher +/// +public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher { + /// + public object Value { get; } + + /// + public virtual string Name => "JsonMatcher"; + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + public bool IgnoreCase { get; } + + /// + public bool ThrowException { get; } + + private readonly JToken _valueAsJToken; + private readonly Func _jTokenConverter; + /// - /// JsonMatcher + /// Initializes a new instance of the class. /// - public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher + /// The string value to check for equality. + /// Ignore the case from the PropertyName and PropertyValue (string only). + /// Throw an exception when the internal matching fails because of invalid input. + public JsonMatcher(string value, bool ignoreCase = false, bool throwException = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, throwException) { - /// - public object Value { get; } + } - /// - public virtual string Name => "JsonMatcher"; + /// + /// Initializes a new instance of the class. + /// + /// The object value to check for equality. + /// Ignore the case from the PropertyName and PropertyValue (string only). + /// Throw an exception when the internal matching fails because of invalid input. + public JsonMatcher(object value, bool ignoreCase = false, bool throwException = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, throwException) + { + } - /// - public MatchBehaviour MatchBehaviour { get; } + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The value to check for equality. + /// Ignore the case from the PropertyName and PropertyValue (string only). + /// Throw an exception when the internal matching fails because of invalid input. + public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool throwException = false) + { + Guard.NotNull(value, nameof(value)); - /// - public bool IgnoreCase { get; } + MatchBehaviour = matchBehaviour; + IgnoreCase = ignoreCase; + ThrowException = throwException; - /// - public bool ThrowException { get; } + Value = value; + _valueAsJToken = ConvertValueToJToken(value); + _jTokenConverter = ignoreCase + ? (Func)Rename + : jToken => jToken; + } - private readonly JToken _valueAsJToken; - private readonly Func _jTokenConverter; + /// + public double IsMatch(object? input) + { + bool match = false; - /// - /// Initializes a new instance of the class. - /// - /// The string value to check for equality. - /// Ignore the case from the PropertyName and PropertyValue (string only). - /// Throw an exception when the internal matching fails because of invalid input. - public JsonMatcher(string value, bool ignoreCase = false, bool throwException = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, throwException) + // When input is null or byte[], return Mismatch. + if (input != null && input is not byte[]) { - } - - /// - /// Initializes a new instance of the class. - /// - /// The object value to check for equality. - /// Ignore the case from the PropertyName and PropertyValue (string only). - /// Throw an exception when the internal matching fails because of invalid input. - public JsonMatcher(object value, bool ignoreCase = false, bool throwException = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, throwException) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The value to check for equality. - /// Ignore the case from the PropertyName and PropertyValue (string only). - /// Throw an exception when the internal matching fails because of invalid input. - public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool throwException = false) - { - Guard.NotNull(value, nameof(value)); - - MatchBehaviour = matchBehaviour; - IgnoreCase = ignoreCase; - ThrowException = throwException; - - Value = value; - _valueAsJToken = ConvertValueToJToken(value); - _jTokenConverter = ignoreCase - ? (Func)Rename - : jToken => jToken; - } - - /// - public double IsMatch(object? input) - { - bool match = false; - - // When input is null or byte[], return Mismatch. - if (input != null && input is not byte[]) + try { - try - { - var inputAsJToken = ConvertValueToJToken(input); + var inputAsJToken = ConvertValueToJToken(input); - match = IsMatch( - _jTokenConverter(_valueAsJToken), - _jTokenConverter(inputAsJToken)); - } - catch (JsonException) + match = IsMatch( + _jTokenConverter(_valueAsJToken), + _jTokenConverter(inputAsJToken)); + } + catch (JsonException) + { + if (ThrowException) { - if (ThrowException) - { - throw; - } + throw; } } - - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)); } - /// - /// Compares the input against the matcher value - /// - /// Matcher value - /// Input value - /// - protected virtual bool IsMatch(JToken value, JToken input) + return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)); + } + + /// + /// Compares the input against the matcher value + /// + /// Matcher value + /// Input value + /// + protected virtual bool IsMatch(JToken value, JToken input) + { + return JToken.DeepEquals(value, input); + } + + private static JToken ConvertValueToJToken(object value) + { + // Check if JToken, string, IEnumerable or object + switch (value) { - return JToken.DeepEquals(value, input); - } + case JToken tokenValue: + return tokenValue; - private static JToken ConvertValueToJToken(object value) - { - // Check if JToken, string, IEnumerable or object - switch (value) - { - case JToken tokenValue: - return tokenValue; + case string stringValue: + return JsonUtils.Parse(stringValue)!; - case string stringValue: - return JsonUtils.Parse(stringValue); + case IEnumerable enumerableValue: + return JArray.FromObject(enumerableValue); - case IEnumerable enumerableValue: - return JArray.FromObject(enumerableValue); - - default: - return JObject.FromObject(value); - } - } - - private static string? ToUpper(string? input) - { - return input?.ToUpperInvariant(); - } - - // https://stackoverflow.com/questions/11679804/json-net-rename-properties - private static JToken Rename(JToken json) - { - if (json is JProperty property) - { - JToken propertyValue = property.Value; - if (propertyValue.Type == JTokenType.String) - { - string stringValue = propertyValue.Value(); - propertyValue = ToUpper(stringValue); - } - - return new JProperty(ToUpper(property.Name), Rename(propertyValue)); - } - - if (json is JArray array) - { - var renamedValues = array.Select(Rename); - return new JArray(renamedValues); - } - - if (json is JObject obj) - { - var renamedProperties = obj.Properties().Select(Rename); - return new JObject(renamedProperties); - } - - return json; + default: + return JObject.FromObject(value); } } + + private static string? ToUpper(string? input) + { + return input?.ToUpperInvariant(); + } + + // https://stackoverflow.com/questions/11679804/json-net-rename-properties + private static JToken Rename(JToken json) + { + if (json is JProperty property) + { + JToken propertyValue = property.Value; + if (propertyValue.Type == JTokenType.String) + { + string stringValue = propertyValue.Value()!; + propertyValue = ToUpper(stringValue); + } + + return new JProperty(ToUpper(property.Name)!, Rename(propertyValue)); + } + + if (json is JArray array) + { + var renamedValues = array.Select(Rename); + return new JArray(renamedValues); + } + + if (json is JObject obj) + { + var renamedProperties = obj.Properties().Select(Rename); + return new JObject(renamedProperties); + } + + return json; + } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/JsonPartialMatcher.cs b/src/WireMock.Net/Matchers/JsonPartialMatcher.cs index 3e457393..9527e581 100644 --- a/src/WireMock.Net/Matchers/JsonPartialMatcher.cs +++ b/src/WireMock.Net/Matchers/JsonPartialMatcher.cs @@ -1,38 +1,35 @@ -using JetBrains.Annotations; +namespace WireMock.Matchers; -namespace WireMock.Matchers +/// +/// JsonPartialMatcher +/// +public class JsonPartialMatcher : AbstractJsonPartialMatcher { - /// - /// JsonPartialMatcher - /// - public class JsonPartialMatcher : AbstractJsonPartialMatcher + /// + public override string Name => nameof(JsonPartialMatcher); + + /// + public JsonPartialMatcher(string value, bool ignoreCase = false, bool throwException = false) + : base(value, ignoreCase, throwException) { - /// - public override string Name => nameof(JsonPartialMatcher); - - /// - public JsonPartialMatcher([NotNull] string value, bool ignoreCase = false, bool throwException = false) - : base(value, ignoreCase, throwException) - { - } - - /// - public JsonPartialMatcher([NotNull] object value, bool ignoreCase = false, bool throwException = false) - : base(value, ignoreCase, throwException) - { - } - - /// - public JsonPartialMatcher(MatchBehaviour matchBehaviour, [NotNull] object value, bool ignoreCase = false, bool throwException = false) - : base(matchBehaviour, value, ignoreCase, throwException) - { - } - - /// - protected override bool IsMatch(string value, string input) - { - var exactStringMatcher = new ExactMatcher(MatchBehaviour.AcceptOnMatch, ThrowException, value); - return MatchScores.IsPerfect(exactStringMatcher.IsMatch(input)); - } } -} + + /// + public JsonPartialMatcher(object value, bool ignoreCase = false, bool throwException = false) + : base(value, ignoreCase, throwException) + { + } + + /// + public JsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool throwException = false) + : base(matchBehaviour, value, ignoreCase, throwException) + { + } + + /// + protected override bool IsMatch(string value, string input) + { + var exactStringMatcher = new ExactMatcher(MatchBehaviour.AcceptOnMatch, ThrowException, MatchOperator.Or, value); + return MatchScores.IsPerfect(exactStringMatcher.IsMatch(input)); + } +} \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/JsonPartialWildCardMatcher.cs b/src/WireMock.Net/Matchers/JsonPartialWildCardMatcher.cs index 5f804150..bc0deb04 100644 --- a/src/WireMock.Net/Matchers/JsonPartialWildCardMatcher.cs +++ b/src/WireMock.Net/Matchers/JsonPartialWildCardMatcher.cs @@ -1,38 +1,37 @@ using JetBrains.Annotations; -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// JsonPartialWildCardMatcher +/// +public class JsonPartialWildcardMatcher : AbstractJsonPartialMatcher { - /// - /// JsonPartialWildCardMatcher - /// - public class JsonPartialWildcardMatcher : AbstractJsonPartialMatcher + /// + public override string Name => nameof(JsonPartialWildcardMatcher); + + /// + public JsonPartialWildcardMatcher(string value, bool ignoreCase = false, bool throwException = false) + : base(value, ignoreCase, throwException) { - /// - public override string Name => nameof(JsonPartialWildcardMatcher); - - /// - public JsonPartialWildcardMatcher([NotNull] string value, bool ignoreCase = false, bool throwException = false) - : base(value, ignoreCase, throwException) - { - } - - /// - public JsonPartialWildcardMatcher([NotNull] object value, bool ignoreCase = false, bool throwException = false) - : base(value, ignoreCase, throwException) - { - } - - /// - public JsonPartialWildcardMatcher(MatchBehaviour matchBehaviour, [NotNull] object value, bool ignoreCase = false, bool throwException = false) - : base(matchBehaviour, value, ignoreCase, throwException) - { - } - - /// - protected override bool IsMatch(string value, string input) - { - var wildcardStringMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, value, IgnoreCase); - return MatchScores.IsPerfect(wildcardStringMatcher.IsMatch(input)); - } } -} + + /// + public JsonPartialWildcardMatcher(object value, bool ignoreCase = false, bool throwException = false) + : base(value, ignoreCase, throwException) + { + } + + /// + public JsonPartialWildcardMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool throwException = false) + : base(matchBehaviour, value, ignoreCase, throwException) + { + } + + /// + protected override bool IsMatch(string value, string input) + { + var wildcardStringMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, value, IgnoreCase); + return MatchScores.IsPerfect(wildcardStringMatcher.IsMatch(input)); + } +} \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/LinqMatcher.cs b/src/WireMock.Net/Matchers/LinqMatcher.cs index 76330fab..df69ad55 100644 --- a/src/WireMock.Net/Matchers/LinqMatcher.cs +++ b/src/WireMock.Net/Matchers/LinqMatcher.cs @@ -1,146 +1,151 @@ using System.Linq; using System.Linq.Dynamic.Core; using AnyOfTypes; -using JetBrains.Annotations; using Newtonsoft.Json.Linq; +using Stef.Validation; using WireMock.Extensions; using WireMock.Models; using WireMock.Util; -using Stef.Validation; -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// System.Linq.Dynamic.Core Expression Matcher +/// +/// +/// +public class LinqMatcher : IObjectMatcher, IStringMatcher { + private readonly AnyOf[] _patterns; + + /// + public MatchBehaviour MatchBehaviour { get; } + /// + public bool ThrowException { get; } + /// - /// System.Linq.Dynamic.Core Expression Matcher + /// Initializes a new instance of the class. /// - /// - /// - public class LinqMatcher : IObjectMatcher, IStringMatcher + /// The pattern. + public LinqMatcher(AnyOf pattern) : this(new[] { pattern }) { - private readonly AnyOf[] _patterns; - - /// - public MatchBehaviour MatchBehaviour { get; } - /// - public bool ThrowException { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The pattern. - public LinqMatcher([NotNull] AnyOf pattern) : this(new[] { pattern }) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - public LinqMatcher([NotNull] params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, patterns) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The pattern. - public LinqMatcher(MatchBehaviour matchBehaviour, [NotNull] AnyOf pattern) : this(matchBehaviour, false, pattern) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The patterns. - /// Throw an exception when the internal matching fails because of invalid input. - public LinqMatcher(MatchBehaviour matchBehaviour, bool throwException = false, [NotNull] params AnyOf[] patterns) - { - Guard.NotNull(patterns, nameof(patterns)); - - MatchBehaviour = matchBehaviour; - ThrowException = throwException; - _patterns = patterns; - } - - /// - public double IsMatch(string input) - { - double match = MatchScores.Mismatch; - - // Convert a single input string to a Queryable string-list with 1 entry. - IQueryable queryable = new[] { input }.AsQueryable(); - - try - { - // Use the Any(...) method to check if the result matches - match = MatchScores.ToScore(_patterns.Select(pattern => queryable.Any(pattern.GetPattern()))); - - return MatchBehaviourHelper.Convert(MatchBehaviour, match); - } - catch - { - if (ThrowException) - { - throw; - } - } - - return MatchBehaviourHelper.Convert(MatchBehaviour, match); - } - - /// - public double IsMatch(object input) - { - double match = MatchScores.Mismatch; - - JObject value; - switch (input) - { - case JObject valueAsJObject: - value = valueAsJObject; - break; - - default: - value = JObject.FromObject(input); - break; - } - - // Convert a single object to a Queryable JObject-list with 1 entry. - var queryable1 = new[] { value }.AsQueryable(); - - try - { - // Generate the DynamicLinq select statement. - string dynamicSelect = JsonUtils.GenerateDynamicLinqStatement(value); - - // Execute DynamicLinq Select statement. - var queryable2 = queryable1.Select(dynamicSelect); - - // Use the Any(...) method to check if the result matches. - match = MatchScores.ToScore(_patterns.Select(pattern => queryable2.Any(pattern))); - - return MatchBehaviourHelper.Convert(MatchBehaviour, match); - } - catch - { - if (ThrowException) - { - throw; - } - } - - return MatchBehaviourHelper.Convert(MatchBehaviour, match); - } - - /// - public AnyOf[] GetPatterns() - { - return _patterns; - } - - /// - public string Name => "LinqMatcher"; } -} + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + public LinqMatcher(params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The pattern. + public LinqMatcher(MatchBehaviour matchBehaviour, AnyOf pattern) : this(matchBehaviour, false, MatchOperator.Or, pattern) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// Throw an exception when the internal matching fails because of invalid input. + /// The to use. (default = "Or") + /// The patterns. + public LinqMatcher( + MatchBehaviour matchBehaviour, + bool throwException = false, + MatchOperator matchOperator = MatchOperator.Or, + params AnyOf[] patterns) + { + _patterns = Guard.NotNull(patterns); + MatchBehaviour = matchBehaviour; + ThrowException = throwException; + MatchOperator = matchOperator; + } + + /// + public double IsMatch(string input) + { + double match = MatchScores.Mismatch; + + // Convert a single input string to a Queryable string-list with 1 entry. + IQueryable queryable = new[] { input }.AsQueryable(); + + 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); + } + catch + { + if (ThrowException) + { + throw; + } + } + + return MatchBehaviourHelper.Convert(MatchBehaviour, match); + } + + /// + public double IsMatch(object input) + { + double match = MatchScores.Mismatch; + + JObject value; + switch (input) + { + case JObject valueAsJObject: + value = valueAsJObject; + break; + + default: + value = JObject.FromObject(input); + break; + } + + // Convert a single object to a Queryable JObject-list with 1 entry. + var queryable1 = new[] { value }.AsQueryable(); + + try + { + // Generate the DynamicLinq select statement. + string dynamicSelect = JsonUtils.GenerateDynamicLinqStatement(value); + + // Execute DynamicLinq Select statement. + var queryable2 = queryable1.Select(dynamicSelect); + + // Use the Any(...) method to check if the result matches. + match = MatchScores.ToScore(_patterns.Select(pattern => queryable2.Any(pattern)).ToArray(), MatchOperator); + + return MatchBehaviourHelper.Convert(MatchBehaviour, match); + } + catch + { + if (ThrowException) + { + throw; + } + } + + return MatchBehaviourHelper.Convert(MatchBehaviour, match); + } + + /// + public AnyOf[] GetPatterns() + { + return _patterns; + } + + /// + public MatchOperator MatchOperator { get; } + + /// + public string Name => "LinqMatcher"; +} \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/MatchOperator.cs b/src/WireMock.Net/Matchers/MatchOperator.cs new file mode 100644 index 00000000..b54adc9e --- /dev/null +++ b/src/WireMock.Net/Matchers/MatchOperator.cs @@ -0,0 +1,22 @@ +namespace WireMock.Matchers; + +/// +/// The Operator to use when multiple patterns are defined. +/// +public enum MatchOperator +{ + /// + /// Only one pattern needs to match. [Default] + /// + Or, + + /// + /// All patterns should match. + /// + And, + + /// + /// The average value from all patterns. + /// + Average, +} \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/MatchScores.cs b/src/WireMock.Net/Matchers/MatchScores.cs index 5cefe4ce..cb79e712 100644 --- a/src/WireMock.Net/Matchers/MatchScores.cs +++ b/src/WireMock.Net/Matchers/MatchScores.cs @@ -1,75 +1,84 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// MatchScores +/// +public static class MatchScores { /// - /// MatchScores + /// The tolerance /// - public static class MatchScores + public const double Tolerance = 0.000001; + + /// + /// The default mismatch score + /// + public const double Mismatch = 0.0; + + /// + /// The default perfect match score + /// + public const double Perfect = 1.0; + + /// + /// The almost perfect match score + /// + public const double AlmostPerfect = 0.99; + + /// + /// Is the value a perfect match? + /// + /// The value. + /// true/false + public static bool IsPerfect(double value) { - /// - /// The tolerance - /// - public const double Tolerance = 0.000001; + return Math.Abs(value - Perfect) < Tolerance; + } - /// - /// The default mismatch score - /// - public const double Mismatch = 0.0; + /// + /// Convert a bool to the score. + /// + /// if set to true [value]. + /// score + public static double ToScore(bool value) + { + return value ? Perfect : Mismatch; + } - /// - /// The default perfect match score - /// - public const double Perfect = 1.0; + /// + /// Calculates the score from multiple values. + /// + /// The values. + /// The . + /// average score + public static double ToScore(IReadOnlyCollection values, MatchOperator matchOperator) + { + return ToScore(values.Select(ToScore).ToArray(), matchOperator); + } - /// - /// The almost perfect match score - /// - public const double AlmostPerfect = 0.99; - - /// - /// Is the value a perfect match? - /// - /// The value. - /// true/false - public static bool IsPerfect(double value) + /// + /// Calculates the score from multiple values. + /// + /// The values. + /// + /// average score + public static double ToScore(IReadOnlyCollection values, MatchOperator matchOperator) + { + if (!values.Any()) { - return Math.Abs(value - Perfect) < Tolerance; + return Mismatch; } - /// - /// Convert a bool to the score. - /// - /// if set to true [value]. - /// score - public static double ToScore(bool value) + return matchOperator switch { - return value ? Perfect : Mismatch; - } - - /// - /// Calculates the score from multiple values. - /// - /// The values. - /// average score - [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] - public static double ToScore(IEnumerable values) - { - return values.Any() ? values.Select(ToScore).Average() : Mismatch; - } - - /// - /// Calculates the score from multiple values. - /// - /// The values. - /// average score - [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] - public static double ToScore(IEnumerable values) - { - return values.Any() ? values.Average() : Mismatch; - } + MatchOperator.Or => ToScore(values.Any(IsPerfect)), + MatchOperator.And => ToScore(values.All(IsPerfect)), + _ => values.Average() + }; } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/NotNullOrEmptyMatcher.cs b/src/WireMock.Net/Matchers/NotNullOrEmptyMatcher.cs index 4b16b48e..a971f3e2 100644 --- a/src/WireMock.Net/Matchers/NotNullOrEmptyMatcher.cs +++ b/src/WireMock.Net/Matchers/NotNullOrEmptyMatcher.cs @@ -2,67 +2,69 @@ using System.Linq; using AnyOfTypes; using WireMock.Models; -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// NotNullOrEmptyMatcher +/// +/// +public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher { + /// + public string Name => "NotNullOrEmptyMatcher"; + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + public bool ThrowException { get; } + /// - /// NotNullOrEmptyMatcher + /// Initializes a new instance of the class. /// - /// - public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher + /// The match behaviour. + public NotNullOrEmptyMatcher(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) { - /// - public string Name => "NotNullOrEmptyMatcher"; - - /// - public MatchBehaviour MatchBehaviour { get; } - - /// - public bool ThrowException { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - public NotNullOrEmptyMatcher(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - MatchBehaviour = matchBehaviour; - } - - /// - public double IsMatch(object input) - { - bool match; - - switch (input) - { - case string @string: - match = !string.IsNullOrEmpty(@string); - break; - - case byte[] bytes: - match = bytes != null && bytes.Any(); - break; - - default: - match = input != null; - break; - } - - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)); - } - - /// - public double IsMatch(string input) - { - var match = !string.IsNullOrEmpty(input); - - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)); - } - - /// - public AnyOf[] GetPatterns() - { - return new AnyOf[0]; - } + MatchBehaviour = matchBehaviour; } + + /// + public double IsMatch(object? input) + { + bool match; + + switch (input) + { + case string @string: + match = !string.IsNullOrEmpty(@string); + break; + + case byte[] bytes: + match = bytes.Any(); + break; + + default: + match = input != null; + break; + } + + return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)); + } + + /// + public double IsMatch(string? input) + { + var match = !string.IsNullOrEmpty(input); + + return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)); + } + + /// + public AnyOf[] GetPatterns() + { + return new AnyOf[0]; + } + + /// + public MatchOperator MatchOperator { get; } = MatchOperator.Or; } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/RegexMatcher.cs b/src/WireMock.Net/Matchers/RegexMatcher.cs index 03a77551..fb20eeae 100644 --- a/src/WireMock.Net/Matchers/RegexMatcher.cs +++ b/src/WireMock.Net/Matchers/RegexMatcher.cs @@ -33,8 +33,14 @@ public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher /// Ignore the case from the pattern. /// Throw an exception when the internal matching fails because of invalid input. /// Use RegexExtended (default = true). - public RegexMatcher([NotNull, RegexPattern] AnyOf pattern, bool ignoreCase = false, bool throwException = false, bool useRegexExtended = true) : - this(MatchBehaviour.AcceptOnMatch, new[] { pattern }, ignoreCase, throwException, useRegexExtended) + /// The to use. (default = "Or") + public RegexMatcher( + [RegexPattern] AnyOf pattern, + bool ignoreCase = false, + bool throwException = false, + bool useRegexExtended = true, + MatchOperator matchOperator = MatchOperator.Or) : + this(MatchBehaviour.AcceptOnMatch, new[] { pattern }, ignoreCase, throwException, useRegexExtended, matchOperator) { } @@ -46,8 +52,15 @@ public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher /// Ignore the case from the pattern. /// Throw an exception when the internal matching fails because of invalid input. /// Use RegexExtended (default = true). - public RegexMatcher(MatchBehaviour matchBehaviour, [NotNull, RegexPattern] AnyOf pattern, bool ignoreCase = false, bool throwException = false, bool useRegexExtended = true) : - this(matchBehaviour, new[] { pattern }, ignoreCase, throwException, useRegexExtended) + /// The to use. (default = "Or") + public RegexMatcher( + MatchBehaviour matchBehaviour, + [RegexPattern] AnyOf pattern, + bool ignoreCase = false, + bool throwException = false, + bool useRegexExtended = true, + MatchOperator matchOperator = MatchOperator.Or) : + this(matchBehaviour, new[] { pattern }, ignoreCase, throwException, useRegexExtended, matchOperator) { } @@ -59,14 +72,20 @@ public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher /// Ignore the case from the pattern. /// Throw an exception when the internal matching fails because of invalid input. /// Use RegexExtended (default = true). - public RegexMatcher(MatchBehaviour matchBehaviour, [NotNull, RegexPattern] AnyOf[] patterns, bool ignoreCase = false, bool throwException = false, bool useRegexExtended = true) + /// The to use. (default = "Or") + public RegexMatcher( + MatchBehaviour matchBehaviour, + [RegexPattern] AnyOf[] patterns, + bool ignoreCase = false, + bool throwException = false, + bool useRegexExtended = true, + MatchOperator matchOperator = MatchOperator.Or) { - Guard.NotNull(patterns, nameof(patterns)); - - _patterns = patterns; + _patterns = Guard.NotNull(patterns); IgnoreCase = ignoreCase; MatchBehaviour = matchBehaviour; ThrowException = throwException; + MatchOperator = matchOperator; RegexOptions options = RegexOptions.Compiled | RegexOptions.Multiline; @@ -79,14 +98,14 @@ public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher } /// - public virtual double IsMatch(string input) + public virtual double IsMatch(string? input) { double match = MatchScores.Mismatch; if (input != null) { try { - match = MatchScores.ToScore(_expressions.Select(e => e.IsMatch(input))); + match = MatchScores.ToScore(_expressions.Select(e => e.IsMatch(input)).ToArray(), MatchOperator); } catch (Exception) { @@ -111,4 +130,8 @@ public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher /// public bool IgnoreCase { get; } + + /// + public MatchOperator MatchOperator { get; } + } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs index a6fd558d..e55e820a 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs @@ -1,211 +1,226 @@ -using JetBrains.Annotations; using System; using System.Linq; +using AnyOfTypes; +using Stef.Validation; +using WireMock.Models; using WireMock.Types; using WireMock.Util; -using Stef.Validation; -namespace WireMock.Matchers.Request +namespace WireMock.Matchers.Request; + +/// +/// The request body matcher. +/// +public class RequestMessageBodyMatcher : IRequestMatcher { /// - /// The request body matcher. + /// The body function /// - public class RequestMessageBodyMatcher : IRequestMatcher + public Func? Func { get; } + + /// + /// The body data function for byte[] + /// + public Func? DataFunc { get; } + + /// + /// The body data function for json + /// + public Func? JsonFunc { get; } + + /// + /// The body data function for BodyData + /// + public Func? BodyDataFunc { get; } + + /// + /// The matchers. + /// + public IMatcher[]? Matchers { get; } + + /// + /// The + /// + public MatchOperator MatchOperator { get; } = MatchOperator.Or; + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The body. + public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, string body) : + this(new[] { new WildcardMatcher(matchBehaviour, body) }.Cast().ToArray()) { - /// - /// The body function - /// - public Func Func { get; } + } - /// - /// The body data function for byte[] - /// - public Func DataFunc { get; } + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The body. + public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, byte[] body) : + this(new[] { new ExactObjectMatcher(matchBehaviour, body) }.Cast().ToArray()) + { + } - /// - /// The body data function for json - /// - public Func JsonFunc { get; } + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The body. + public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, object body) : + this(new[] { new ExactObjectMatcher(matchBehaviour, body) }.Cast().ToArray()) + { + } - /// - /// The body data function for BodyData - /// - public Func BodyDataFunc { get; } + /// + /// Initializes a new instance of the class. + /// + /// The function. + public RequestMessageBodyMatcher(Func func) + { + Func = Guard.NotNull(func); + } - /// - /// The matchers. - /// - public IMatcher[] Matchers { get; } + /// + /// Initializes a new instance of the class. + /// + /// The function. + public RequestMessageBodyMatcher(Func func) + { + DataFunc = Guard.NotNull(func); + } - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The body. - public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, [NotNull] string body) : this(new[] { new WildcardMatcher(matchBehaviour, body) }.Cast().ToArray()) + /// + /// Initializes a new instance of the class. + /// + /// The function. + public RequestMessageBodyMatcher(Func func) + { + JsonFunc = Guard.NotNull(func); + } + + /// + /// Initializes a new instance of the class. + /// + /// The function. + public RequestMessageBodyMatcher(Func func) + { + BodyDataFunc = Guard.NotNull(func); + } + + /// + /// Initializes a new instance of the class. + /// + /// The matchers. + public RequestMessageBodyMatcher(params IMatcher[] matchers) + { + Matchers = Guard.NotNull(matchers); + } + + /// + /// Initializes a new instance of the class. + /// + /// The matchers. + /// The to use. + public RequestMessageBodyMatcher(MatchOperator matchOperator, params IMatcher[] matchers) + { + Matchers = Guard.NotNull(matchers); + MatchOperator = matchOperator; + } + + /// + public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) + { + double score = CalculateMatchScore(requestMessage); + return requestMatchResult.AddScore(GetType(), score); + } + + private static double CalculateMatchScore(IRequestMessage requestMessage, IMatcher matcher) + { + if (matcher is NotNullOrEmptyMatcher notNullOrEmptyMatcher) { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The body. - public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, [NotNull] byte[] body) : this(new[] { new ExactObjectMatcher(matchBehaviour, body) }.Cast().ToArray()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The body. - public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, [NotNull] object body) : this(new[] { new ExactObjectMatcher(matchBehaviour, body) }.Cast().ToArray()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The function. - public RequestMessageBodyMatcher([NotNull] Func func) - { - Guard.NotNull(func, nameof(func)); - Func = func; - } - - /// - /// Initializes a new instance of the class. - /// - /// The function. - public RequestMessageBodyMatcher([NotNull] Func func) - { - Guard.NotNull(func, nameof(func)); - DataFunc = func; - } - - /// - /// Initializes a new instance of the class. - /// - /// The function. - public RequestMessageBodyMatcher([NotNull] Func func) - { - Guard.NotNull(func, nameof(func)); - JsonFunc = func; - } - - /// - /// Initializes a new instance of the class. - /// - /// The function. - public RequestMessageBodyMatcher([NotNull] Func func) - { - Guard.NotNull(func, nameof(func)); - BodyDataFunc = func; - } - - /// - /// Initializes a new instance of the class. - /// - /// The matchers. - public RequestMessageBodyMatcher([NotNull] params IMatcher[] matchers) - { - Guard.NotNull(matchers, nameof(matchers)); - Matchers = matchers; - } - - /// - public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) - { - double score = CalculateMatchScore(requestMessage); - return requestMatchResult.AddScore(GetType(), score); - } - - private double CalculateMatchScore(IRequestMessage requestMessage, IMatcher matcher) - { - if (matcher is NotNullOrEmptyMatcher notNullOrEmptyMatcher) + switch (requestMessage.BodyData?.DetectedBodyType) { - switch (requestMessage?.BodyData?.DetectedBodyType) - { - case BodyType.Json: - case BodyType.String: - return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyData.BodyAsString); + case BodyType.Json: + case BodyType.String: + return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyData.BodyAsString); - case BodyType.Bytes: - return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyData.BodyAsBytes); + case BodyType.Bytes: + return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyData.BodyAsBytes); - default: - return MatchScores.Mismatch; - } + default: + return MatchScores.Mismatch; + } + } + + if (matcher is ExactObjectMatcher exactObjectMatcher) + { + // If the body is a byte array, try to match. + var detectedBodyType = requestMessage.BodyData?.DetectedBodyType; + if (detectedBodyType == BodyType.Bytes || detectedBodyType == BodyType.String) + { + return exactObjectMatcher.IsMatch(requestMessage.BodyData.BodyAsBytes); + } + } + + // Check if the matcher is a IObjectMatcher + if (matcher is IObjectMatcher objectMatcher) + { + // If the body is a JSON object, try to match. + if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json) + { + return objectMatcher.IsMatch(requestMessage.BodyData.BodyAsJson); } - if (matcher is ExactObjectMatcher exactObjectMatcher) + // If the body is a byte array, try to match. + if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Bytes) { - // If the body is a byte array, try to match. - var detectedBodyType = requestMessage?.BodyData?.DetectedBodyType; - if (detectedBodyType == BodyType.Bytes || detectedBodyType == BodyType.String) - { - return exactObjectMatcher.IsMatch(requestMessage.BodyData.BodyAsBytes); - } + return objectMatcher.IsMatch(requestMessage.BodyData.BodyAsBytes); } - - // Check if the matcher is a IObjectMatcher - if (matcher is IObjectMatcher objectMatcher) - { - // If the body is a JSON object, try to match. - if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json) - { - return objectMatcher.IsMatch(requestMessage.BodyData.BodyAsJson); - } - - // If the body is a byte array, try to match. - if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Bytes) - { - return objectMatcher.IsMatch(requestMessage.BodyData.BodyAsBytes); - } - } - - // Check if the matcher is a IStringMatcher - if (matcher is IStringMatcher stringMatcher) - { - // If the body is a Json or a String, use the BodyAsString to match on. - if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json || requestMessage?.BodyData?.DetectedBodyType == BodyType.String) - { - return stringMatcher.IsMatch(requestMessage.BodyData.BodyAsString); - } - } - - return MatchScores.Mismatch; } - private double CalculateMatchScore(IRequestMessage requestMessage) + // Check if the matcher is a IStringMatcher + if (matcher is IStringMatcher stringMatcher) { - if (Matchers != null && Matchers.Any()) + // If the body is a Json or a String, use the BodyAsString to match on. + if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json || requestMessage?.BodyData?.DetectedBodyType == BodyType.String) { - return Matchers.Max(matcher => CalculateMatchScore(requestMessage, matcher)); + return stringMatcher.IsMatch(requestMessage.BodyData.BodyAsString); } - - if (Func != null) - { - return MatchScores.ToScore(Func(requestMessage?.BodyData?.BodyAsString)); - } - - if (JsonFunc != null) - { - return MatchScores.ToScore(JsonFunc(requestMessage?.BodyData?.BodyAsJson)); - } - - if (DataFunc != null) - { - return MatchScores.ToScore(DataFunc(requestMessage?.BodyData?.BodyAsBytes)); - } - - if (BodyDataFunc != null) - { - return MatchScores.ToScore(BodyDataFunc(requestMessage?.BodyData)); - } - - return MatchScores.Mismatch; } + + return MatchScores.Mismatch; + } + + private double CalculateMatchScore(IRequestMessage requestMessage) + { + if (Matchers != null) + { + var matchersResult = Matchers.Select(matcher => CalculateMatchScore(requestMessage, matcher)).ToArray(); + return MatchScores.ToScore(matchersResult, MatchOperator); + } + + if (Func != null) + { + return MatchScores.ToScore(Func(requestMessage.BodyData?.BodyAsString)); + } + + if (JsonFunc != null) + { + return MatchScores.ToScore(JsonFunc(requestMessage.BodyData?.BodyAsJson)); + } + + if (DataFunc != null) + { + return MatchScores.ToScore(DataFunc(requestMessage.BodyData?.BodyAsBytes)); + } + + if (BodyDataFunc != null) + { + return MatchScores.ToScore(BodyDataFunc(requestMessage.BodyData)); + } + + return MatchScores.Mismatch; } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageClientIPMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageClientIPMatcher.cs index a6eacaa6..264c054f 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageClientIPMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageClientIPMatcher.cs @@ -1,75 +1,98 @@ using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; +using AnyOfTypes; using Stef.Validation; +using WireMock.Models; -namespace WireMock.Matchers.Request +namespace WireMock.Matchers.Request; + +/// +/// The request clientIP matcher. +/// +public class RequestMessageClientIPMatcher : IRequestMatcher { /// - /// The request ClientIP matcher. + /// The matchers /// - public class RequestMessageClientIPMatcher : IRequestMatcher + public IReadOnlyList? Matchers { get; } + + /// + /// The clientIP functions + /// + public Func[]? Funcs { get; } + + /// + /// The + /// + public MatchBehaviour Behaviour { get; } + + /// + /// The + /// + public MatchOperator MatchOperator { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The to use. + /// The clientIPs. + public RequestMessageClientIPMatcher( + MatchBehaviour matchBehaviour, + MatchOperator matchOperator, + params string[] clientIPs) : + this(matchBehaviour, matchOperator, clientIPs + .Select(clientIP => new WildcardMatcher(matchBehaviour, new AnyOf[] { clientIP }, false, false, matchOperator)) + .Cast().ToArray()) { - /// - /// The matchers. - /// - public IReadOnlyList Matchers { get; } + Behaviour = matchBehaviour; + MatchOperator = matchOperator; + } - /// - /// The ClientIP functions. - /// - public Func[] Funcs { get; } + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The to use. + /// The matchers. + public RequestMessageClientIPMatcher(MatchBehaviour matchBehaviour, MatchOperator matchOperator, params IStringMatcher[] matchers) + { + Matchers = Guard.NotNull(matchers); + Behaviour = matchBehaviour; + MatchOperator = matchOperator; + } - /// - /// Initializes a new instance of the class. - /// - /// The clientIPs. - /// The match behaviour. - public RequestMessageClientIPMatcher(MatchBehaviour matchBehaviour, [NotNull] params string[] clientIPs) : this(clientIPs.Select(ip => new WildcardMatcher(matchBehaviour, ip)).Cast().ToArray()) + /// + /// Initializes a new instance of the class. + /// + /// The clientIP functions. + public RequestMessageClientIPMatcher(params Func[] funcs) + { + Funcs = Guard.NotNull(funcs); + } + + /// + public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) + { + double score = IsMatch(requestMessage); + return requestMatchResult.AddScore(GetType(), score); + } + + private double IsMatch(IRequestMessage requestMessage) + { + if (Matchers != null) { + var results = Matchers.Select(m => m.IsMatch(requestMessage.ClientIP)).ToArray(); + return MatchScores.ToScore(results, MatchOperator); } - /// - /// Initializes a new instance of the class. - /// - /// The matchers. - public RequestMessageClientIPMatcher([NotNull] params IStringMatcher[] matchers) + if (Funcs != null) { - Guard.NotNull(matchers, nameof(matchers)); - Matchers = matchers; + var results = Funcs.Select(func => func(requestMessage.ClientIP)).ToArray(); + return MatchScores.ToScore(results, MatchOperator); } - /// - /// Initializes a new instance of the class. - /// - /// The clientIP functions. - public RequestMessageClientIPMatcher([NotNull] params Func[] funcs) - { - Guard.NotNull(funcs, nameof(funcs)); - Funcs = funcs; - } - - /// - public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) - { - double score = IsMatch(requestMessage); - return requestMatchResult.AddScore(GetType(), score); - } - - private double IsMatch(IRequestMessage requestMessage) - { - if (Matchers != null) - { - return Matchers.Max(matcher => matcher.IsMatch(requestMessage.ClientIP)); - } - - if (Funcs != null) - { - return MatchScores.ToScore(requestMessage.ClientIP != null && Funcs.Any(func => func(requestMessage.ClientIP))); - } - - return MatchScores.Mismatch; - } + return MatchScores.Mismatch; } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageHeaderMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageHeaderMatcher.cs index b0c7484a..67e4f3b8 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageHeaderMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageHeaderMatcher.cs @@ -1,130 +1,142 @@ -using JetBrains.Annotations; using System; using System.Collections.Generic; using System.Linq; -using WireMock.Types; using Stef.Validation; +using WireMock.Types; -namespace WireMock.Matchers.Request +namespace WireMock.Matchers.Request; + +/// +/// The request header matcher. +/// +/// +public class RequestMessageHeaderMatcher : IRequestMatcher { + private readonly MatchBehaviour _matchBehaviour; + private readonly bool _ignoreCase; + /// - /// The request header matcher. + /// The functions /// - /// - public class RequestMessageHeaderMatcher : IRequestMatcher + public Func, bool>[]? Funcs { get; } + + /// + /// The name + /// + public string? Name { get; } + + /// + /// The matchers. + /// + public IStringMatcher[]? Matchers { get; } + + /// + /// The + /// + public MatchOperator MatchOperator { get; } = MatchOperator.Or; + + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The pattern. + /// Ignore the case from the pattern. + /// The match behaviour. + public RequestMessageHeaderMatcher(MatchBehaviour matchBehaviour, string name, string pattern, bool ignoreCase) { - private readonly MatchBehaviour _matchBehaviour; - private readonly bool _ignoreCase; + Guard.NotNull(name); + Guard.NotNull(pattern); - /// - /// The functions - /// - public Func, bool>[] Funcs { get; } + _matchBehaviour = matchBehaviour; + _ignoreCase = ignoreCase; + Name = name; + Matchers = new IStringMatcher[] { new WildcardMatcher(matchBehaviour, pattern, ignoreCase) }; + } - /// - /// The name - /// - public string Name { get; } + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The to use. + /// The name. + /// The patterns. + /// Ignore the case from the pattern. + public RequestMessageHeaderMatcher(MatchBehaviour matchBehaviour, MatchOperator matchOperator, string name, bool ignoreCase, params string[] patterns) : + this(matchBehaviour, matchOperator, name, ignoreCase, patterns.Select(pattern => new WildcardMatcher(matchBehaviour, pattern, ignoreCase)).Cast().ToArray()) + { + Guard.NotNull(patterns); + } - /// - /// The matchers. - /// - public IStringMatcher[] Matchers { get; } + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The to use. + /// The name. + /// The matchers. + /// Ignore the case from the pattern. + public RequestMessageHeaderMatcher(MatchBehaviour matchBehaviour, MatchOperator matchOperator, string name, bool ignoreCase, params IStringMatcher[] matchers) + { + Guard.NotNull(name); + Guard.NotNull(matchers); - /// - /// Initializes a new instance of the class. - /// - /// The name. - /// The pattern. - /// Ignore the case from the pattern. - /// The match behaviour. - public RequestMessageHeaderMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, [NotNull] string pattern, bool ignoreCase) + _matchBehaviour = matchBehaviour; + MatchOperator = matchOperator; + Name = name; + Matchers = matchers; + _ignoreCase = ignoreCase; + } + + /// + /// Initializes a new instance of the class. + /// + /// The funcs. + public RequestMessageHeaderMatcher(params Func, bool>[] funcs) + { + Funcs = Guard.NotNull(funcs); + } + + /// + public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) + { + double score = IsMatch(requestMessage); + return requestMatchResult.AddScore(GetType(), score); + } + + private double IsMatch(IRequestMessage requestMessage) + { + if (requestMessage.Headers == null) { - Guard.NotNull(name, nameof(name)); - Guard.NotNull(pattern, nameof(pattern)); - - _matchBehaviour = matchBehaviour; - _ignoreCase = ignoreCase; - Name = name; - Matchers = new IStringMatcher[] { new WildcardMatcher(matchBehaviour, pattern, ignoreCase) }; + return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch); } - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The name. - /// The patterns. - /// Ignore the case from the pattern. - public RequestMessageHeaderMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, bool ignoreCase, [NotNull] params string[] patterns) : - this(matchBehaviour, name, ignoreCase, patterns.Select(pattern => new WildcardMatcher(matchBehaviour, pattern, ignoreCase)).Cast().ToArray()) + // Check if we want to use IgnoreCase to compare the Header-Name and Header-Value(s) + var headers = !_ignoreCase ? requestMessage.Headers : new Dictionary>(requestMessage.Headers, StringComparer.OrdinalIgnoreCase); + + if (Funcs != null) { - Guard.NotNull(patterns, nameof(patterns)); + var funcResults = Funcs.Select(f => f(headers.ToDictionary(entry => entry.Key, entry => entry.Value.ToArray()))).ToArray(); + return MatchScores.ToScore(funcResults, MatchOperator); } - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The name. - /// The matchers. - /// Ignore the case from the pattern. - public RequestMessageHeaderMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, bool ignoreCase, [NotNull] params IStringMatcher[] matchers) + if (Matchers != null) { - Guard.NotNull(name, nameof(name)); - Guard.NotNull(matchers, nameof(matchers)); - - _matchBehaviour = matchBehaviour; - Name = name; - Matchers = matchers; - _ignoreCase = ignoreCase; - } - - /// - /// Initializes a new instance of the class. - /// - /// The funcs. - public RequestMessageHeaderMatcher([NotNull] params Func, bool>[] funcs) - { - Guard.NotNull(funcs, nameof(funcs)); - - Funcs = funcs; - } - - /// - public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) - { - double score = IsMatch(requestMessage); - return requestMatchResult.AddScore(GetType(), score); - } - - private double IsMatch(IRequestMessage requestMessage) - { - if (requestMessage.Headers == null) + if (!headers.ContainsKey(Name!)) { 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>(requestMessage.Headers, StringComparer.OrdinalIgnoreCase); - - if (Funcs != null) + var results = new List(); + foreach (var matcher in Matchers) { - return MatchScores.ToScore(Funcs.Any(f => f(headers.ToDictionary(entry => entry.Key, entry => entry.Value.ToArray())))); + var resultsPerMatcher = headers[Name!].Select(v => matcher.IsMatch(v)).ToArray(); + + results.Add(MatchScores.ToScore(resultsPerMatcher, MatchOperator.And)); } - if (Matchers == null) - { - return MatchScores.Mismatch; - } - - if (!headers.ContainsKey(Name)) - { - return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch); - } - - WireMockList list = headers[Name]; - return Matchers.Max(m => list.Max(m.IsMatch)); // TODO : is this correct ? + return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.ToScore(results, MatchOperator)); } + + return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch); } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageMethodMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageMethodMatcher.cs index 8ce1fc2b..55864d2c 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageMethodMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageMethodMatcher.cs @@ -1,45 +1,52 @@ using System; using System.Linq; -using JetBrains.Annotations; using Stef.Validation; -namespace WireMock.Matchers.Request +namespace WireMock.Matchers.Request; + +/// +/// The request method matcher. +/// +internal class RequestMessageMethodMatcher : IRequestMatcher { /// - /// The request verb matcher. + /// The /// - internal class RequestMessageMethodMatcher : IRequestMatcher + public MatchBehaviour MatchBehaviour { get; } + + /// + /// The + /// + public MatchOperator MatchOperator { get; } + + /// + /// The methods + /// + public string[] Methods { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The to use. + /// The methods. + public RequestMessageMethodMatcher(MatchBehaviour matchBehaviour, MatchOperator matchOperator, params string[] methods) { - private readonly MatchBehaviour _matchBehaviour; + Methods = Guard.NotNull(methods); + MatchBehaviour = matchBehaviour; + MatchOperator = matchOperator; + } - /// - /// The methods - /// - public string[] Methods { get; } + /// + public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) + { + double score = MatchBehaviourHelper.Convert(MatchBehaviour, IsMatch(requestMessage)); + return requestMatchResult.AddScore(GetType(), score); + } - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The methods. - public RequestMessageMethodMatcher(MatchBehaviour matchBehaviour, [NotNull] params string[] methods) - { - Guard.NotNull(methods, nameof(methods)); - _matchBehaviour = matchBehaviour; - - Methods = methods; - } - - /// - public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) - { - double score = MatchBehaviourHelper.Convert(_matchBehaviour, IsMatch(requestMessage)); - return requestMatchResult.AddScore(GetType(), score); - } - - private double IsMatch(IRequestMessage requestMessage) - { - return MatchScores.ToScore(Methods.Contains(requestMessage.Method, StringComparer.OrdinalIgnoreCase)); - } + private double IsMatch(IRequestMessage requestMessage) + { + var scores = Methods.Select(m => string.Equals(m, requestMessage.Method, StringComparison.OrdinalIgnoreCase)).ToArray(); + return MatchScores.ToScore(scores, MatchOperator); } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs index ac6bf23e..e351995a 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs @@ -1,152 +1,146 @@ -using JetBrains.Annotations; using System; using System.Collections.Generic; using System.Linq; -using WireMock.Types; using Stef.Validation; +using WireMock.Types; -namespace WireMock.Matchers.Request +namespace WireMock.Matchers.Request; + +/// +/// The request parameters matcher. +/// +public class RequestMessageParamMatcher : IRequestMatcher { + private readonly MatchBehaviour _matchBehaviour; + /// - /// The request parameters matcher. + /// The funcs /// - public class RequestMessageParamMatcher : IRequestMatcher + public Func>, bool>[]? Funcs { get; } + + /// + /// The key + /// + public string? Key { get; } + + /// + /// Defines if the key should be matched using case-ignore. + /// + public bool? IgnoreCase { get; } + + /// + /// The matchers. + /// + public IReadOnlyList? Matchers { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The key. + /// Defines if the key should be matched using case-ignore. + public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, string key, bool ignoreCase) : this(matchBehaviour, key, ignoreCase, (IStringMatcher[]?)null) { - private readonly MatchBehaviour _matchBehaviour; + } - /// - /// The funcs - /// - public Func>, bool>[] Funcs { get; } + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The key. + /// Defines if the key should be matched using case-ignore. + /// The values. + public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, string key, bool ignoreCase, string[]? values) : this(matchBehaviour, key, ignoreCase, values?.Select(value => new ExactMatcher(matchBehaviour, false, MatchOperator.And, value)).Cast().ToArray()) + { + } - /// - /// The key - /// - public string Key { get; } + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The key. + /// Defines if the key should be matched using case-ignore. + /// The matchers. + public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, string key, bool ignoreCase, IStringMatcher[]? matchers) + { + _matchBehaviour = matchBehaviour; + Key = Guard.NotNull(key); + IgnoreCase = ignoreCase; + Matchers = matchers; + } - /// - /// Defines if the key should be matched using case-ignore. - /// - public bool? IgnoreCase { get; private set; } + /// + /// Initializes a new instance of the class. + /// + /// The funcs. + public RequestMessageParamMatcher(params Func>, bool>[] funcs) + { + Funcs = Guard.NotNull(funcs); + } - /// - /// The matchers. - /// - public IReadOnlyList Matchers { get; } + /// + public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) + { + double score = MatchBehaviourHelper.Convert(_matchBehaviour, IsMatch(requestMessage)); + return requestMatchResult.AddScore(GetType(), score); + } - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The key. - /// Defines if the key should be matched using case-ignore. - public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, [NotNull] string key, bool ignoreCase) : this(matchBehaviour, key, ignoreCase, (IStringMatcher[])null) + private double IsMatch(IRequestMessage requestMessage) + { + if (Funcs != null) { + return MatchScores.ToScore(requestMessage.Query != null && Funcs.Any(f => f(requestMessage.Query))); } - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The key. - /// Defines if the key should be matched using case-ignore. - /// The values. - public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, [NotNull] string key, bool ignoreCase, [CanBeNull] string[] values) : this(matchBehaviour, key, ignoreCase, values?.Select(value => new ExactMatcher(matchBehaviour, false, value)).Cast().ToArray()) + WireMockList valuesPresentInRequestMessage = ((RequestMessage)requestMessage).GetParameter(Key, IgnoreCase ?? false); + if (valuesPresentInRequestMessage == null) { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The key. - /// Defines if the key should be matched using case-ignore. - /// The matchers. - public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, [NotNull] string key, bool ignoreCase, [CanBeNull] IStringMatcher[] matchers) - { - Guard.NotNull(key, nameof(key)); - - _matchBehaviour = matchBehaviour; - Key = key; - IgnoreCase = ignoreCase; - Matchers = matchers; - } - - /// - /// Initializes a new instance of the class. - /// - /// The funcs. - public RequestMessageParamMatcher([NotNull] params Func>, bool>[] funcs) - { - Guard.NotNull(funcs, nameof(funcs)); - - Funcs = funcs; - } - - /// - public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) - { - double score = MatchBehaviourHelper.Convert(_matchBehaviour, IsMatch(requestMessage)); - return requestMatchResult.AddScore(GetType(), score); - } - - private double IsMatch(IRequestMessage requestMessage) - { - if (Funcs != null) - { - return MatchScores.ToScore(requestMessage.Query != null && Funcs.Any(f => f(requestMessage.Query))); - } - - WireMockList valuesPresentInRequestMessage = ((RequestMessage) requestMessage).GetParameter(Key, IgnoreCase ?? false); - if (valuesPresentInRequestMessage == null) - { - // Key is not present at all, just return Mismatch - return MatchScores.Mismatch; - } - - if (Matchers != null && Matchers.Any()) - { - // Return the score based on Matchers and valuesPresentInRequestMessage - return CalculateScore(valuesPresentInRequestMessage); - } - - if (Matchers == null || !Matchers.Any()) - { - // Matchers are null or not defined, and Key is present, just return Perfect. - return MatchScores.Perfect; - } - + // Key is not present at all, just return Mismatch return MatchScores.Mismatch; } - private double CalculateScore(WireMockList valuesPresentInRequestMessage) + if (Matchers != null && Matchers.Any()) { - var total = new List(); + // Return the score based on Matchers and valuesPresentInRequestMessage + return CalculateScore(valuesPresentInRequestMessage); + } - // If the total patterns in all matchers > values in message, use the matcher as base - if (Matchers.Sum(m => m.GetPatterns().Length) > valuesPresentInRequestMessage.Count) - { - foreach (var matcher in Matchers) - { - double score = 0d; - foreach (string valuePresentInRequestMessage in valuesPresentInRequestMessage) - { - score += matcher.IsMatch(valuePresentInRequestMessage) / matcher.GetPatterns().Length; - } + if (Matchers == null || !Matchers.Any()) + { + // Matchers are null or not defined, and Key is present, just return Perfect. + return MatchScores.Perfect; + } - total.Add(score); - } - } - else + return MatchScores.Mismatch; + } + + private double CalculateScore(WireMockList valuesPresentInRequestMessage) + { + var total = new List(); + + // If the total patterns in all matchers > values in message, use the matcher as base + if (Matchers.Sum(m => m.GetPatterns().Length) > valuesPresentInRequestMessage.Count) + { + foreach (var matcher in Matchers) { + double score = 0d; foreach (string valuePresentInRequestMessage in valuesPresentInRequestMessage) { - double score = Matchers.Max(m => m.IsMatch(valuePresentInRequestMessage)); - total.Add(score); + score += matcher.IsMatch(valuePresentInRequestMessage) / matcher.GetPatterns().Length; } - } - return total.Any() ? MatchScores.ToScore(total) : MatchScores.Mismatch; + total.Add(score); + } } + else + { + foreach (string valuePresentInRequestMessage in valuesPresentInRequestMessage) + { + double score = Matchers.Max(m => m.IsMatch(valuePresentInRequestMessage)); + total.Add(score); + } + } + + return total.Any() ? MatchScores.ToScore(total, MatchOperator.Average) : MatchScores.Mismatch; } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/RequestMessagePathMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessagePathMatcher.cs index fbd0a38c..aa61807c 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessagePathMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessagePathMatcher.cs @@ -1,77 +1,98 @@ using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; +using AnyOfTypes; using Stef.Validation; +using WireMock.Models; -namespace WireMock.Matchers.Request +namespace WireMock.Matchers.Request; + +/// +/// The request path matcher. +/// +public class RequestMessagePathMatcher : IRequestMatcher { /// - /// The request path matcher. + /// The matchers /// - public class RequestMessagePathMatcher : IRequestMatcher + public IReadOnlyList? Matchers { get; } + + /// + /// The path functions + /// + public Func[]? Funcs { get; } + + /// + /// The + /// + public MatchBehaviour Behaviour { get; } + + /// + /// The + /// + public MatchOperator MatchOperator { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The to use. + /// The paths. + public RequestMessagePathMatcher( + MatchBehaviour matchBehaviour, + MatchOperator matchOperator, + params string[] paths) : + this(matchBehaviour, matchOperator, paths + .Select(path => new WildcardMatcher(matchBehaviour, new AnyOf[] { path }, false, false, matchOperator)) + .Cast().ToArray()) { - /// - /// The matchers - /// - public IReadOnlyList Matchers { get; } + Behaviour = matchBehaviour; + MatchOperator = matchOperator; + } - /// - /// The path functions - /// - public Func[] Funcs { get; } + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The to use. + /// The matchers. + public RequestMessagePathMatcher(MatchBehaviour matchBehaviour, MatchOperator matchOperator, params IStringMatcher[] matchers) + { + Matchers = Guard.NotNull(matchers); + Behaviour = matchBehaviour; + MatchOperator = matchOperator; + } - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The paths. - public RequestMessagePathMatcher(MatchBehaviour matchBehaviour, [NotNull] params string[] paths) : this(paths.Select(path => new WildcardMatcher(matchBehaviour, path)).Cast().ToArray()) + /// + /// Initializes a new instance of the class. + /// + /// The path functions. + public RequestMessagePathMatcher(params Func[] funcs) + { + Funcs = Guard.NotNull(funcs); + } + + /// + public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) + { + double score = IsMatch(requestMessage); + return requestMatchResult.AddScore(GetType(), score); + } + + private double IsMatch(IRequestMessage requestMessage) + { + if (Matchers != null) { + var results = Matchers.Select(m => m.IsMatch(requestMessage.Path)).ToArray(); + return MatchScores.ToScore(results, MatchOperator); } - /// - /// Initializes a new instance of the class. - /// - /// The matchers. - public RequestMessagePathMatcher([NotNull] params IStringMatcher[] matchers) + if (Funcs != null) { - Guard.NotNull(matchers, nameof(matchers)); - - Matchers = matchers; + var results = Funcs.Select(func => func(requestMessage.Path)).ToArray(); + return MatchScores.ToScore(results, MatchOperator); } - /// - /// Initializes a new instance of the class. - /// - /// The path functions. - public RequestMessagePathMatcher([NotNull] params Func[] funcs) - { - Guard.NotNull(funcs, nameof(funcs)); - - Funcs = funcs; - } - - /// - public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) - { - double score = IsMatch(requestMessage); - return requestMatchResult.AddScore(GetType(), score); - } - - private double IsMatch(IRequestMessage requestMessage) - { - if (Matchers != null) - { - return Matchers.Max(m => m.IsMatch(requestMessage.Path)); - } - - if (Funcs != null) - { - return MatchScores.ToScore(requestMessage.Path != null && Funcs.Any(func => func(requestMessage.Path))); - } - - return MatchScores.Mismatch; - } + return MatchScores.Mismatch; } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageUrlMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageUrlMatcher.cs index 9935eb5f..762275ff 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageUrlMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageUrlMatcher.cs @@ -1,75 +1,98 @@ using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; +using AnyOfTypes; using Stef.Validation; +using WireMock.Models; -namespace WireMock.Matchers.Request +namespace WireMock.Matchers.Request; + +/// +/// The request url matcher. +/// +public class RequestMessageUrlMatcher : IRequestMatcher { /// - /// The request url matcher. + /// The matchers /// - public class RequestMessageUrlMatcher : IRequestMatcher + public IReadOnlyList? Matchers { get; } + + /// + /// The url functions + /// + public Func[]? Funcs { get; } + + /// + /// The + /// + public MatchBehaviour Behaviour { get; } + + /// + /// The + /// + public MatchOperator MatchOperator { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The to use. + /// The urls. + public RequestMessageUrlMatcher( + MatchBehaviour matchBehaviour, + MatchOperator matchOperator, + params string[] urls) : + this(matchBehaviour, matchOperator, urls + .Select(url => new WildcardMatcher(matchBehaviour, new AnyOf[] { url }, false, false, matchOperator)) + .Cast().ToArray()) { - /// - /// The matchers. - /// - public IReadOnlyList Matchers { get; } + Behaviour = matchBehaviour; + MatchOperator = matchOperator; + } - /// - /// The url functions. - /// - public Func[] Funcs { get; } + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The to use. (default = "Or") + /// The matchers. + public RequestMessageUrlMatcher(MatchBehaviour matchBehaviour, MatchOperator matchOperator, params IStringMatcher[] matchers) + { + Matchers = Guard.NotNull(matchers); + Behaviour = matchBehaviour; + MatchOperator = matchOperator; + } - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The urls. - public RequestMessageUrlMatcher(MatchBehaviour matchBehaviour, [NotNull] params string[] urls) : this(urls.Select(url => new WildcardMatcher(matchBehaviour, url)).Cast().ToArray()) + /// + /// Initializes a new instance of the class. + /// + /// The url functions. + public RequestMessageUrlMatcher(params Func[] funcs) + { + Funcs = Guard.NotNull(funcs); + } + + /// + public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) + { + double score = IsMatch(requestMessage); + return requestMatchResult.AddScore(GetType(), score); + } + + private double IsMatch(IRequestMessage requestMessage) + { + if (Matchers != null) { + var results = Matchers.Select(m => m.IsMatch(requestMessage.Url)).ToArray(); + return MatchScores.ToScore(results, MatchOperator); } - /// - /// Initializes a new instance of the class. - /// - /// The matchers. - public RequestMessageUrlMatcher([NotNull] params IStringMatcher[] matchers) + if (Funcs != null) { - Guard.NotNull(matchers, nameof(matchers)); - Matchers = matchers; + var results = Funcs.Select(func => func(requestMessage.Url)).ToArray(); + return MatchScores.ToScore(results, MatchOperator); } - /// - /// Initializes a new instance of the class. - /// - /// The url functions. - public RequestMessageUrlMatcher([NotNull] params Func[] funcs) - { - Guard.NotNull(funcs, nameof(funcs)); - Funcs = funcs; - } - - /// - public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) - { - double score = IsMatch(requestMessage); - return requestMatchResult.AddScore(GetType(), score); - } - - private double IsMatch(IRequestMessage requestMessage) - { - if (Matchers != null) - { - return Matchers.Max(matcher => matcher.IsMatch(requestMessage.Url)); - } - - if (Funcs != null) - { - return MatchScores.ToScore(requestMessage.Url != null && Funcs.Any(func => func(requestMessage.Url))); - } - - return MatchScores.Mismatch; - } + return MatchScores.Mismatch; } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/SimMetricsMatcher.cs b/src/WireMock.Net/Matchers/SimMetricsMatcher.cs index 94e30581..76085cae 100644 --- a/src/WireMock.Net/Matchers/SimMetricsMatcher.cs +++ b/src/WireMock.Net/Matchers/SimMetricsMatcher.cs @@ -1,143 +1,149 @@ using System.Linq; using AnyOfTypes; -using JetBrains.Annotations; using SimMetrics.Net; using SimMetrics.Net.API; using SimMetrics.Net.Metric; +using Stef.Validation; using WireMock.Extensions; using WireMock.Models; -using Stef.Validation; -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// SimMetricsMatcher +/// +/// +public class SimMetricsMatcher : IStringMatcher { + private readonly AnyOf[] _patterns; + private readonly SimMetricType _simMetricType; + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + public bool ThrowException { get; } + /// - /// SimMetricsMatcher + /// Initializes a new instance of the class. /// - /// - public class SimMetricsMatcher : IStringMatcher + /// The pattern. + /// The SimMetric Type + public SimMetricsMatcher(AnyOf pattern, SimMetricType simMetricType = SimMetricType.Levenstein) : this(new[] { pattern }, simMetricType) { - private readonly AnyOf[] _patterns; - private readonly SimMetricType _simMetricType; - - /// - public MatchBehaviour MatchBehaviour { get; } - - /// - public bool ThrowException { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The pattern. - /// The SimMetric Type - public SimMetricsMatcher([NotNull] AnyOf pattern, SimMetricType simMetricType = SimMetricType.Levenstein) : this(new[] { pattern }, simMetricType) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The pattern. - /// The SimMetric Type - public SimMetricsMatcher(MatchBehaviour matchBehaviour, [NotNull] AnyOf pattern, SimMetricType simMetricType = SimMetricType.Levenstein) : this(matchBehaviour, new[] { pattern }, simMetricType) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - /// The SimMetric Type - public SimMetricsMatcher([NotNull] string[] patterns, SimMetricType simMetricType = SimMetricType.Levenstein) : this(MatchBehaviour.AcceptOnMatch, patterns.ToAnyOfPatterns(), simMetricType) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - /// The SimMetric Type - public SimMetricsMatcher([NotNull] AnyOf[] patterns, SimMetricType simMetricType = SimMetricType.Levenstein) : this(MatchBehaviour.AcceptOnMatch, patterns, simMetricType) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The patterns. - /// The SimMetric Type - /// Throw an exception when the internal matching fails because of invalid input. - public SimMetricsMatcher(MatchBehaviour matchBehaviour, [NotNull] AnyOf[] patterns, SimMetricType simMetricType = SimMetricType.Levenstein, bool throwException = false) - { - Guard.NotNull(patterns, nameof(patterns)); - - MatchBehaviour = matchBehaviour; - ThrowException = throwException; - - _patterns = patterns; - _simMetricType = simMetricType; - } - - /// - public double IsMatch(string input) - { - IStringMetric stringMetricType = GetStringMetricType(); - - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(_patterns.Select(p => stringMetricType.GetSimilarity(p.GetPattern(), input)))); - } - - private IStringMetric GetStringMetricType() - { - switch (_simMetricType) - { - 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(); - } - } - - /// - public AnyOf[] GetPatterns() - { - return _patterns; - } - - /// - public string Name => $"SimMetricsMatcher.{_simMetricType}"; } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The pattern. + /// The SimMetric Type + public SimMetricsMatcher(MatchBehaviour matchBehaviour, AnyOf pattern, SimMetricType simMetricType = SimMetricType.Levenstein) : this(matchBehaviour, new[] { pattern }, simMetricType) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + /// The SimMetric Type + public SimMetricsMatcher(string[] patterns, SimMetricType simMetricType = SimMetricType.Levenstein) : this(MatchBehaviour.AcceptOnMatch, patterns.ToAnyOfPatterns(), simMetricType) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + /// The SimMetric Type + public SimMetricsMatcher(AnyOf[] patterns, SimMetricType simMetricType = SimMetricType.Levenstein) : this(MatchBehaviour.AcceptOnMatch, patterns, simMetricType) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The patterns. + /// The SimMetric Type + /// Throw an exception when the internal matching fails because of invalid input. + /// The to use. (default = "Or") + public SimMetricsMatcher( + MatchBehaviour matchBehaviour, + AnyOf[] patterns, + SimMetricType simMetricType = SimMetricType.Levenstein, + bool throwException = false, + MatchOperator matchOperator = MatchOperator.Average) + { + _patterns = Guard.NotNull(patterns); + _simMetricType = simMetricType; + MatchBehaviour = matchBehaviour; + ThrowException = throwException; + MatchOperator = matchOperator; + } + + /// + public double IsMatch(string input) + { + IStringMetric stringMetricType = GetStringMetricType(); + + var score = MatchScores.ToScore(_patterns.Select(p => stringMetricType.GetSimilarity(p.GetPattern(), input)).ToArray(), MatchOperator); + return MatchBehaviourHelper.Convert(MatchBehaviour, score); + } + + private IStringMetric GetStringMetricType() + { + switch (_simMetricType) + { + 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(); + } + } + + /// + public AnyOf[] GetPatterns() + { + return _patterns; + } + + /// + public MatchOperator MatchOperator { get; } = MatchOperator.Average; + + /// + public string Name => $"SimMetricsMatcher.{_simMetricType}"; } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/WildcardMatcher.cs b/src/WireMock.Net/Matchers/WildcardMatcher.cs index 2ebea047..58593422 100644 --- a/src/WireMock.Net/Matchers/WildcardMatcher.cs +++ b/src/WireMock.Net/Matchers/WildcardMatcher.cs @@ -1,7 +1,7 @@ using System.Linq; using System.Text.RegularExpressions; using AnyOfTypes; -using JetBrains.Annotations; +using Stef.Validation; using WireMock.Extensions; using WireMock.Models; @@ -20,7 +20,7 @@ public class WildcardMatcher : RegexMatcher /// /// The pattern. /// IgnoreCase - public WildcardMatcher([NotNull] AnyOf pattern, bool ignoreCase = false) : this(new[] { pattern }, ignoreCase) + public WildcardMatcher(AnyOf pattern, bool ignoreCase = false) : this(new[] { pattern }, ignoreCase) { } @@ -30,7 +30,7 @@ public class WildcardMatcher : RegexMatcher /// The match behaviour. /// The pattern. /// IgnoreCase - public WildcardMatcher(MatchBehaviour matchBehaviour, [NotNull] AnyOf pattern, bool ignoreCase = false) : this(matchBehaviour, new[] { pattern }, ignoreCase) + public WildcardMatcher(MatchBehaviour matchBehaviour, AnyOf pattern, bool ignoreCase = false) : this(matchBehaviour, new[] { pattern }, ignoreCase) { } @@ -39,7 +39,7 @@ public class WildcardMatcher : RegexMatcher /// /// The patterns. /// IgnoreCase - public WildcardMatcher([NotNull] AnyOf[] patterns, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase) + public WildcardMatcher(AnyOf[] patterns, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase) { } @@ -50,10 +50,16 @@ public class WildcardMatcher : RegexMatcher /// The patterns. /// IgnoreCase /// Throw an exception when the internal matching fails because of invalid input. - public WildcardMatcher(MatchBehaviour matchBehaviour, [NotNull] AnyOf[] patterns, bool ignoreCase = false, bool throwException = false) : - base(matchBehaviour, CreateArray(patterns), ignoreCase, throwException) + /// The to use. (default = "Or") + public WildcardMatcher( + MatchBehaviour matchBehaviour, + AnyOf[] patterns, + bool ignoreCase = false, + bool throwException = false, + MatchOperator matchOperator = MatchOperator.Or) : + base(matchBehaviour, CreateArray(patterns), ignoreCase, throwException, true, matchOperator) { - _patterns = patterns; + _patterns = Guard.NotNull(patterns); } /// @@ -67,7 +73,8 @@ public class WildcardMatcher : RegexMatcher private static AnyOf[] CreateArray(AnyOf[] patterns) { - return patterns.Select(pattern => new AnyOf( + return patterns + .Select(pattern => new AnyOf( new StringPattern { Pattern = "^" + Regex.Escape(pattern.GetPattern()).Replace(@"\*", ".*").Replace(@"\?", ".") + "$", diff --git a/src/WireMock.Net/Matchers/XPathMatcher.cs b/src/WireMock.Net/Matchers/XPathMatcher.cs index ab0c6e86..a15a3edd 100644 --- a/src/WireMock.Net/Matchers/XPathMatcher.cs +++ b/src/WireMock.Net/Matchers/XPathMatcher.cs @@ -2,7 +2,6 @@ using System; using System.Linq; using System.Xml; using AnyOfTypes; -using JetBrains.Annotations; using WireMock.Extensions; using WireMock.Models; using Stef.Validation; @@ -30,7 +29,7 @@ namespace WireMock.Matchers /// Initializes a new instance of the class. /// /// The patterns. - public XPathMatcher([NotNull] params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, patterns) + public XPathMatcher(params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns) { } @@ -39,18 +38,22 @@ namespace WireMock.Matchers /// /// The match behaviour. /// Throw an exception when the internal matching fails because of invalid input. + /// The to use. (default = "Or") /// The patterns. - public XPathMatcher(MatchBehaviour matchBehaviour, bool throwException = false, [NotNull] params AnyOf[] patterns) + public XPathMatcher( + MatchBehaviour matchBehaviour, + bool throwException = false, + MatchOperator matchOperator = MatchOperator.Or, + params AnyOf[] patterns) { - Guard.NotNull(patterns, nameof(patterns)); - + _patterns = Guard.NotNull(patterns); MatchBehaviour = matchBehaviour; ThrowException = throwException; - _patterns = patterns; + MatchOperator = matchOperator; } /// - public double IsMatch(string input) + public double IsMatch(string? input) { double match = MatchScores.Mismatch; if (input != null) @@ -59,9 +62,9 @@ namespace WireMock.Matchers { var nav = new XmlDocument { InnerXml = input }.CreateNavigator(); #if NETSTANDARD1_3 - match = MatchScores.ToScore(_patterns.Select(p => true.Equals(nav.Evaluate($"boolean({p.GetPattern()})")))); + match = MatchScores.ToScore(_patterns.Select(p => true.Equals(nav.Evaluate($"boolean({p.GetPattern()})"))).ToArray(), MatchOperator); #else - match = MatchScores.ToScore(_patterns.Select(p => true.Equals(nav.XPath2Evaluate($"boolean({p.GetPattern()})")))); + match = MatchScores.ToScore(_patterns.Select(p => true.Equals(nav.XPath2Evaluate($"boolean({p.GetPattern()})"))).ToArray(), MatchOperator); #endif } catch (Exception) @@ -82,6 +85,9 @@ namespace WireMock.Matchers return _patterns; } + /// + public MatchOperator MatchOperator { get; } + /// public string Name => "XPathMatcher"; } diff --git a/src/WireMock.Net/Models/StringPattern.cs b/src/WireMock.Net/Models/StringPattern.cs index 5a8a7be5..dc4ebd70 100644 --- a/src/WireMock.Net/Models/StringPattern.cs +++ b/src/WireMock.Net/Models/StringPattern.cs @@ -1,18 +1,17 @@ -namespace WireMock.Models +namespace WireMock.Models; + +/// +/// StringPattern which defines the Pattern as a string, and optionally the filepath pattern file. +/// +public struct StringPattern { /// - /// StringPattern which defines the Pattern as a string, and optionally the filepath pattern file. + /// The pattern as string. /// - public struct StringPattern - { - /// - /// The pattern as string. - /// - public string Pattern { get; set; } + public string Pattern { get; set; } - /// - /// The filepath (optionally) - /// - public string PatternAsFile { get; set; } - } + /// + /// The filepath (optionally) + /// + public string? PatternAsFile { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net/Models/WebhookRequest.cs b/src/WireMock.Net/Models/WebhookRequest.cs index bd6a59e8..9dd1401c 100644 --- a/src/WireMock.Net/Models/WebhookRequest.cs +++ b/src/WireMock.Net/Models/WebhookRequest.cs @@ -16,10 +16,10 @@ namespace WireMock.Models public string Method { get; set; } /// - public IDictionary> Headers { get; set; } + public IDictionary>? Headers { get; set; } /// - public IBodyData BodyData { get; set; } + public IBodyData? BodyData { get; set; } /// public bool? UseTransformer { get; set; } diff --git a/src/WireMock.Net/Proxy/ProxyHelper.cs b/src/WireMock.Net/Proxy/ProxyHelper.cs index ccc069c8..a76c655b 100644 --- a/src/WireMock.Net/Proxy/ProxyHelper.cs +++ b/src/WireMock.Net/Proxy/ProxyHelper.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; -using JetBrains.Annotations; +using Stef.Validation; using WireMock.Http; using WireMock.Matchers; using WireMock.RequestBuilders; @@ -11,100 +11,98 @@ using WireMock.ResponseBuilders; using WireMock.Settings; using WireMock.Types; using WireMock.Util; -using Stef.Validation; -namespace WireMock.Proxy +namespace WireMock.Proxy; + +internal class ProxyHelper { - internal class ProxyHelper + private readonly WireMockServerSettings _settings; + + public ProxyHelper(WireMockServerSettings settings) { - private readonly WireMockServerSettings _settings; + _settings = Guard.NotNull(settings); + } - public ProxyHelper([NotNull] WireMockServerSettings settings) + public async Task<(IResponseMessage Message, IMapping? Mapping)> SendAsync( + ProxyAndRecordSettings proxyAndRecordSettings, + HttpClient client, + IRequestMessage requestMessage, + string url) + { + Guard.NotNull(client, nameof(client)); + Guard.NotNull(requestMessage, nameof(requestMessage)); + Guard.NotNull(url, nameof(url)); + + var originalUri = new Uri(requestMessage.Url); + var requiredUri = new Uri(url); + + // Create HttpRequestMessage + var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, url); + + // Call the URL + var httpResponseMessage = await client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseContentRead).ConfigureAwait(false); + + // Create ResponseMessage + bool deserializeJson = !_settings.DisableJsonBodyParsing.GetValueOrDefault(false); + bool decompressGzipAndDeflate = !_settings.DisableRequestBodyDecompressing.GetValueOrDefault(false); + + var responseMessage = await HttpResponseMessageHelper.CreateAsync(httpResponseMessage, requiredUri, originalUri, deserializeJson, decompressGzipAndDeflate).ConfigureAwait(false); + + IMapping? mapping = null; + if (HttpStatusRangeParser.IsMatch(proxyAndRecordSettings.SaveMappingForStatusCodePattern, responseMessage.StatusCode) && + (proxyAndRecordSettings.SaveMapping || proxyAndRecordSettings.SaveMappingToFile)) { - _settings = Guard.NotNull(settings, nameof(settings)); + mapping = ToMapping(proxyAndRecordSettings, requestMessage, responseMessage); } - public async Task<(IResponseMessage Message, IMapping Mapping)> SendAsync( - [NotNull] ProxyAndRecordSettings proxyAndRecordSettings, - [NotNull] HttpClient client, - [NotNull] IRequestMessage requestMessage, - [NotNull] string url) + return (responseMessage, mapping); + } + + private IMapping ToMapping(ProxyAndRecordSettings proxyAndRecordSettings, IRequestMessage requestMessage, ResponseMessage responseMessage) + { + var excludedHeaders = proxyAndRecordSettings.ExcludedHeaders ?? new string[] { }; + var excludedCookies = proxyAndRecordSettings.ExcludedCookies ?? new string[] { }; + + var request = Request.Create(); + request.WithPath(requestMessage.Path); + request.UsingMethod(requestMessage.Method); + + requestMessage.Query?.Loop((key, value) => request.WithParam(key, false, value.ToArray())); + requestMessage.Cookies?.Loop((key, value) => { - Guard.NotNull(client, nameof(client)); - Guard.NotNull(requestMessage, nameof(requestMessage)); - Guard.NotNull(url, nameof(url)); - - var originalUri = new Uri(requestMessage.Url); - var requiredUri = new Uri(url); - - // Create HttpRequestMessage - var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, url); - - // Call the URL - var httpResponseMessage = await client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseContentRead).ConfigureAwait(false); - - // Create ResponseMessage - bool deserializeJson = !_settings.DisableJsonBodyParsing.GetValueOrDefault(false); - bool decompressGzipAndDeflate = !_settings.DisableRequestBodyDecompressing.GetValueOrDefault(false); - - var responseMessage = await HttpResponseMessageHelper.CreateAsync(httpResponseMessage, requiredUri, originalUri, deserializeJson, decompressGzipAndDeflate).ConfigureAwait(false); - - IMapping mapping = null; - if (HttpStatusRangeParser.IsMatch(proxyAndRecordSettings.SaveMappingForStatusCodePattern, responseMessage.StatusCode) && - (proxyAndRecordSettings.SaveMapping || proxyAndRecordSettings.SaveMappingToFile)) + if (!excludedCookies.Contains(key, StringComparer.OrdinalIgnoreCase)) { - mapping = ToMapping(proxyAndRecordSettings, requestMessage, responseMessage); + request.WithCookie(key, value); } + }); - return (responseMessage, mapping); - } - - private IMapping ToMapping(ProxyAndRecordSettings proxyAndRecordSettings, IRequestMessage requestMessage, ResponseMessage responseMessage) + var allExcludedHeaders = new List(excludedHeaders) { "Cookie" }; + requestMessage.Headers?.Loop((key, value) => { - string[] excludedHeaders = proxyAndRecordSettings.ExcludedHeaders ?? new string[] { }; - string[] excludedCookies = proxyAndRecordSettings.ExcludedCookies ?? new string[] { }; - - var request = Request.Create(); - request.WithPath(requestMessage.Path); - request.UsingMethod(requestMessage.Method); - - requestMessage.Query.Loop((key, value) => request.WithParam(key, false, value.ToArray())); - requestMessage.Cookies.Loop((key, value) => + if (!allExcludedHeaders.Contains(key, StringComparer.OrdinalIgnoreCase)) { - if (!excludedCookies.Contains(key, StringComparer.OrdinalIgnoreCase)) - { - request.WithCookie(key, value); - } - }); - - var allExcludedHeaders = new List(excludedHeaders) { "Cookie" }; - requestMessage.Headers.Loop((key, value) => - { - if (!allExcludedHeaders.Contains(key, StringComparer.OrdinalIgnoreCase)) - { - request.WithHeader(key, value.ToArray()); - } - }); - - bool throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true; - switch (requestMessage.BodyData?.DetectedBodyType) - { - case BodyType.Json: - request.WithBody(new JsonMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsJson, true, throwExceptionWhenMatcherFails)); - break; - - case BodyType.String: - request.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, throwExceptionWhenMatcherFails, requestMessage.BodyData.BodyAsString)); - break; - - case BodyType.Bytes: - request.WithBody(new ExactObjectMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsBytes, throwExceptionWhenMatcherFails)); - break; + request.WithHeader(key, value.ToArray()); } + }); - var response = Response.Create(responseMessage); + bool throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true; + switch (requestMessage.BodyData?.DetectedBodyType) + { + case BodyType.Json: + request.WithBody(new JsonMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsJson!, true, throwExceptionWhenMatcherFails)); + break; - return new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 0, null, null, null, null, null, null); + case BodyType.String: + request.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, throwExceptionWhenMatcherFails, MatchOperator.Or, requestMessage.BodyData.BodyAsString)); + break; + + case BodyType.Bytes: + request.WithBody(new ExactObjectMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsBytes, throwExceptionWhenMatcherFails)); + break; } + + var response = Response.Create(responseMessage); + + return new Mapping(Guid.NewGuid(), string.Empty, string.Empty, null, _settings, request, response, 0, null, null, null, null, null, null); } } \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs b/src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs index 29cb632c..e586dbf9 100644 --- a/src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs +++ b/src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs @@ -1,80 +1,79 @@ -using JetBrains.Annotations; using System; using WireMock.Matchers; using WireMock.Matchers.Request; using WireMock.Util; -namespace WireMock.RequestBuilders +namespace WireMock.RequestBuilders; + +/// +/// The BodyRequestBuilder interface. +/// +public interface IBodyRequestBuilder : IRequestMatcher { /// - /// The BodyRequestBuilder interface. + /// WithBody: IMatcher /// - public interface IBodyRequestBuilder : IRequestMatcher - { - /// - /// WithBody: IMatcher - /// - /// The matcher. - /// The . - IRequestBuilder WithBody([NotNull] IMatcher matcher); + /// The matcher. + /// The . + IRequestBuilder WithBody(IMatcher matcher); - /// - /// WithBody: IMatcher[] - /// - /// The matchers. - /// The . - IRequestBuilder WithBody([NotNull] IMatcher[] matchers); + /// + /// WithBody: IMatcher[] + /// + /// The matchers. + /// The to use. + /// The . + IRequestBuilder WithBody(IMatcher[] matchers, MatchOperator matchOperator = MatchOperator.Or); - /// - /// WithBody: Body as string - /// - /// The body. - /// The match behaviour. - /// The . - IRequestBuilder WithBody(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + /// + /// WithBody: Body as string + /// + /// The body. + /// The match behaviour. + /// The . + IRequestBuilder WithBody(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - /// - /// WithBody: Body as byte[] - /// - /// The body. - /// The match behaviour. - /// The . - IRequestBuilder WithBody(byte[] body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + /// + /// WithBody: Body as byte[] + /// + /// The body. + /// The match behaviour. + /// The . + IRequestBuilder WithBody(byte[] body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - /// - /// WithBody: Body as object - /// - /// The body. - /// The match behaviour. - /// The . - IRequestBuilder WithBody(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + /// + /// WithBody: Body as object + /// + /// The body. + /// The match behaviour. + /// The . + IRequestBuilder WithBody(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - /// - /// WithBody: func (string) - /// - /// The function. - /// The . - IRequestBuilder WithBody([NotNull] Func func); + /// + /// WithBody: func (string) + /// + /// The function. + /// The . + IRequestBuilder WithBody(Func func); - /// - /// WithBody: func (byte[]) - /// - /// The function. - /// The . - IRequestBuilder WithBody([NotNull] Func func); + /// + /// WithBody: func (byte[]) + /// + /// The function. + /// The . + IRequestBuilder WithBody(Func func); - /// - /// WithBody: func (json object) - /// - /// The function. - /// The . - IRequestBuilder WithBody([NotNull] Func func); + /// + /// WithBody: func (json object) + /// + /// The function. + /// The . + IRequestBuilder WithBody(Func func); - /// - /// WithBody: func (BodyData object) - /// - /// The function. - /// The . - IRequestBuilder WithBody([NotNull] Func func); - } + /// + /// WithBody: func (BodyData object) + /// + /// The function. + /// The . + IRequestBuilder WithBody(Func func); } \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/IClientIPRequestBuilder.cs b/src/WireMock.Net/RequestBuilders/IClientIPRequestBuilder.cs index a9bb8e4f..1d4fb133 100644 --- a/src/WireMock.Net/RequestBuilders/IClientIPRequestBuilder.cs +++ b/src/WireMock.Net/RequestBuilders/IClientIPRequestBuilder.cs @@ -1,41 +1,47 @@ -using System; -using JetBrains.Annotations; +using System; using WireMock.Matchers; -namespace WireMock.RequestBuilders +namespace WireMock.RequestBuilders; + +/// +/// The IClientIPRequestBuilder interface. +/// +public interface IClientIPRequestBuilder : IUrlAndPathRequestBuilder { /// - /// The IClientIPRequestBuilder interface. + /// WithClientIP: add clientIP matching based on IStringMatchers. /// - public interface IClientIPRequestBuilder : IUrlAndPathRequestBuilder - { - /// - /// WithClientIP: add matching on ClientIP matchers. - /// - /// The matchers. - /// The . - IRequestBuilder WithClientIP([NotNull] params IStringMatcher[] matchers); + /// The matchers. + /// The . + IRequestBuilder WithClientIP(params IStringMatcher[] matchers); - /// - /// WithClientIP: add matching on clientIPs. - /// - /// The clientIPs. - /// The . - IRequestBuilder WithClientIP([NotNull] params string[] clientIPs); + /// + /// WithClientIP: add clientIP matching based on MatchOperator and IStringMatchers. + /// + /// The to use. + /// The matchers. + /// The . + IRequestBuilder WithClientIP(MatchOperator matchOperator, params IStringMatcher[] matchers); - /// - /// WithClientIP: add matching on clientIPs and matchBehaviour. - /// - /// The match behaviour. - /// The clientIPs. - /// The . - IRequestBuilder WithClientIP(MatchBehaviour matchBehaviour, [NotNull] params string[] clientIPs); + /// + /// WithClientIP: add clientIP matching based on clientIPs. + /// + /// The clientIPs. + /// The . + IRequestBuilder WithClientIP(params string[] clientIPs); - /// - /// WithClientIP: add matching on ClientIP funcs. - /// - /// The path funcs. - /// The . - IRequestBuilder WithClientIP([NotNull] params Func[] funcs); - } + /// + /// WithClientIP: add clientIP matching based on clientIPs , matchBehaviour and MatchOperator. + /// + /// The to use. + /// The clientIPs. + /// The . + IRequestBuilder WithClientIP(MatchOperator matchOperator, params string[] clientIPs); + + /// + /// WithClientIP: add clientIP matching based on functions. + /// + /// The clientIP funcs. + /// The . + IRequestBuilder WithClientIP(params Func[] funcs); } \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/IHeadersRequestBuilder.cs b/src/WireMock.Net/RequestBuilders/IHeadersRequestBuilder.cs index 8ec6c15a..24ad8f80 100644 --- a/src/WireMock.Net/RequestBuilders/IHeadersRequestBuilder.cs +++ b/src/WireMock.Net/RequestBuilders/IHeadersRequestBuilder.cs @@ -1,85 +1,86 @@ -using JetBrains.Annotations; using System; using System.Collections.Generic; using WireMock.Matchers; -namespace WireMock.RequestBuilders +namespace WireMock.RequestBuilders; + +/// +/// The HeadersRequestBuilder interface. +/// +public interface IHeadersRequestBuilder : ICookiesRequestBuilder { /// - /// The HeadersRequestBuilder interface. + /// WithHeader: matching based on name, pattern and matchBehaviour. /// - public interface IHeadersRequestBuilder : ICookiesRequestBuilder - { - /// - /// WithHeader: matching based on name, pattern and matchBehaviour. - /// - /// The name. - /// The pattern. - /// The match behaviour. - /// The . - IRequestBuilder WithHeader([NotNull] string name, string pattern, MatchBehaviour matchBehaviour); + /// The name. + /// The pattern. + /// The match behaviour. + /// The . + IRequestBuilder WithHeader(string name, string pattern, MatchBehaviour matchBehaviour); - /// - /// WithHeader: matching based on name, pattern, ignoreCase and matchBehaviour. - /// - /// The name. - /// The pattern. - /// Ignore the case from the pattern. - /// The match behaviour. - /// The . - IRequestBuilder WithHeader([NotNull] string name, string pattern, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + /// + /// WithHeader: matching based on name, pattern, ignoreCase and matchBehaviour. + /// + /// The name. + /// The pattern. + /// Ignore the case from the pattern. + /// The match behaviour. + /// The . + IRequestBuilder WithHeader(string name, string pattern, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - /// - /// WithHeader: matching based on name, patterns and matchBehaviour. - /// - /// The name. - /// The patterns. - /// The match behaviour. - /// The . - IRequestBuilder WithHeader([NotNull] string name, string[] patterns, MatchBehaviour matchBehaviour); + /// + /// WithHeader: matching based on name, patterns and matchBehaviour. + /// + /// The name. + /// The patterns. + /// The match behaviour. + /// The to use. + /// The . + IRequestBuilder WithHeader(string name, string[] patterns, MatchBehaviour matchBehaviour, MatchOperator matchOperator = MatchOperator.Or); - /// - /// WithHeader: matching based on name, patterns, ignoreCase and matchBehaviour. - /// - /// The name. - /// The patterns. - /// Ignore the case from the pattern. - /// The match behaviour. - /// The . - IRequestBuilder WithHeader([NotNull] string name, string[] patterns, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + /// + /// WithHeader: matching based on name, patterns, ignoreCase and matchBehaviour. + /// + /// The name. + /// The patterns. + /// Ignore the case from the pattern. + /// The match behaviour. + /// The to use. + /// The . + IRequestBuilder WithHeader(string name, string[] patterns, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.Or); - /// - /// WithHeader: matching based on name and IStringMatcher[]. - /// - /// The name. - /// The matchers. - /// The . - IRequestBuilder WithHeader([NotNull] string name, [NotNull] params IStringMatcher[] matchers); + /// + /// WithHeader: matching based on name and IStringMatcher[]. + /// + /// The name. + /// The matchers. + /// The . + IRequestBuilder WithHeader(string name, params IStringMatcher[] matchers); - /// - /// WithHeader: matching based on name, ignoreCase and IStringMatcher[]. - /// - /// The name. - /// Ignore the case from the header-keys. - /// The matchers. - /// The . - IRequestBuilder WithHeader([NotNull] string name, bool ignoreCase, [NotNull] params IStringMatcher[] matchers); + /// + /// WithHeader: matching based on name, ignoreCase and IStringMatcher[]. + /// + /// The name. + /// Ignore the case from the header-keys. + /// The matchers. + /// The . + IRequestBuilder WithHeader(string name, bool ignoreCase, params IStringMatcher[] matchers); - /// - /// WithHeader: matching based on name, ignoreCase, matchBehaviour and IStringMatcher[]. - /// - /// The name. - /// Ignore the case from the header-keys. - /// The match behaviour. - /// The matchers. - /// The . - IRequestBuilder WithHeader([NotNull] string name, bool ignoreCase, MatchBehaviour matchBehaviour, [NotNull] params IStringMatcher[] matchers); + /// + /// WithHeader: matching based on name, ignoreCase, matchBehaviour and IStringMatcher[]. + /// + /// The name. + /// Ignore the case from the header-keys. + /// The match behaviour. + /// The to use. + /// The matchers. + /// The . + IRequestBuilder WithHeader(string name, bool ignoreCase, MatchBehaviour matchBehaviour, MatchOperator matchOperator = MatchOperator.Or, params IStringMatcher[] matchers); - /// - /// WithHeader: matching based on functions. - /// - /// The headers funcs. - /// The . - IRequestBuilder WithHeader([NotNull] params Func, bool>[] funcs); - } + /// + /// WithHeader: matching based on functions. + /// + /// The headers funcs. + /// The . + IRequestBuilder WithHeader(params Func, bool>[] funcs); } \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/IMethodRequestBuilder.cs b/src/WireMock.Net/RequestBuilders/IMethodRequestBuilder.cs index 3c8cc8b6..59dd1675 100644 --- a/src/WireMock.Net/RequestBuilders/IMethodRequestBuilder.cs +++ b/src/WireMock.Net/RequestBuilders/IMethodRequestBuilder.cs @@ -1,111 +1,96 @@ -using System; +using System; using JetBrains.Annotations; using WireMock.Matchers; -namespace WireMock.RequestBuilders +namespace WireMock.RequestBuilders; + +/// +/// The MethodRequestBuilder interface. +/// +public interface IMethodRequestBuilder : IHeadersRequestBuilder { /// - /// The MethodRequestBuilder interface. + /// UsingConnect: add HTTP Method matching on `CONNECT` and matchBehaviour (optional). /// - public interface IMethodRequestBuilder : IHeadersRequestBuilder - { - /// - /// UsingConnect: add HTTP Method matching on `CONNECT` and matchBehaviour (optional). - /// - /// The match behaviour. - /// The . - IRequestBuilder UsingConnect(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + /// The match behaviour. + /// The . + IRequestBuilder UsingConnect(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - /// - /// UsingDelete: add HTTP Method matching on `DELETE` and matchBehaviour (optional). - /// - /// The match behaviour. - /// The . - IRequestBuilder UsingDelete(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + /// + /// UsingDelete: add HTTP Method matching on `DELETE` and matchBehaviour (optional). + /// + /// The match behaviour. + /// The . + IRequestBuilder UsingDelete(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - /// - /// UsingGet: add HTTP Method matching on `GET` and matchBehaviour (optional). - /// - /// The match behaviour. - /// The . - IRequestBuilder UsingGet(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + /// + /// UsingGet: add HTTP Method matching on `GET` and matchBehaviour (optional). + /// + /// The match behaviour. + /// The . + IRequestBuilder UsingGet(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - /// - /// UsingHead: Add HTTP Method matching on `HEAD` and matchBehaviour (optional). - /// - /// The match behaviour. - /// The . - IRequestBuilder UsingHead(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + /// + /// UsingHead: Add HTTP Method matching on `HEAD` and matchBehaviour (optional). + /// + /// The match behaviour. + /// The . + IRequestBuilder UsingHead(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - /// - /// UsingPost: add HTTP Method matching on `POST` and matchBehaviour (optional). - /// - /// The match behaviour. - /// The . - IRequestBuilder UsingPost(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + /// + /// UsingPost: add HTTP Method matching on `POST` and matchBehaviour (optional). + /// + /// The match behaviour. + /// The . + IRequestBuilder UsingPost(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - /// - /// UsingPatch: add HTTP Method matching on `PATCH` and matchBehaviour (optional). - /// - /// The match behaviour. - /// The . - IRequestBuilder UsingPatch(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + /// + /// UsingPatch: add HTTP Method matching on `PATCH` and matchBehaviour (optional). + /// + /// The match behaviour. + /// The . + IRequestBuilder UsingPatch(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - /// - /// UsingPut: add HTTP Method matching on `OPTIONS` and matchBehaviour (optional). - /// - /// The match behaviour. - /// The . - IRequestBuilder UsingOptions(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + /// + /// UsingPut: add HTTP Method matching on `OPTIONS` and matchBehaviour (optional). + /// + /// The match behaviour. + /// The . + IRequestBuilder UsingOptions(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - /// - /// UsingPut: add HTTP Method matching on `PUT` and matchBehaviour (optional). - /// - /// The match behaviour. - /// The . - IRequestBuilder UsingPut(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + /// + /// UsingPut: add HTTP Method matching on `PUT` and matchBehaviour (optional). + /// + /// The match behaviour. + /// The . + IRequestBuilder UsingPut(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - /// - /// UsingTrace: add HTTP Method matching on `TRACE` and matchBehaviour (optional). - /// - /// The match behaviour. - /// The . - IRequestBuilder UsingTrace(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + /// + /// UsingTrace: add HTTP Method matching on `TRACE` and matchBehaviour (optional). + /// + /// The match behaviour. + /// The . + IRequestBuilder UsingTrace(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - /// - /// UsingAnyMethod: add HTTP Method matching on any method. - /// - /// The . - IRequestBuilder UsingAnyMethod(); + /// + /// UsingAnyMethod: add HTTP Method matching on any method. + /// + /// The . + IRequestBuilder UsingAnyMethod(); - /// - /// UsingAnyVerb: add HTTP Method matching on any method. - /// - /// The . - [Obsolete("Use the method UsingAnyMethod().")] - IRequestBuilder UsingAnyVerb(); + /// + /// UsingMethod: add HTTP Method matching on any methods and matchBehaviour. + /// + /// The match behaviour. + /// The to use. + /// The method or methods. + /// The . + IRequestBuilder UsingMethod(MatchBehaviour matchBehaviour, MatchOperator matchOperator, params string[] methods); - /// - /// UsingMethod: add HTTP Method matching on any methods and matchBehaviour. - /// - /// The match behaviour. - /// The method or methods. - /// The . - IRequestBuilder UsingMethod(MatchBehaviour matchBehaviour, [NotNull] params string[] methods); - - /// - /// UsingMethod: add HTTP Method matching on any methods. - /// - /// The method or methods. - /// The . - IRequestBuilder UsingMethod([NotNull] params string[] methods); - - /// - /// UsingVerb: add HTTP Method matching on any methods. - /// - /// The method or methods. - /// The . - [Obsolete("Use the method UsingMethod(...).")] - IRequestBuilder UsingVerb([NotNull] params string[] verbs); - } + /// + /// UsingMethod: add HTTP Method matching on any methods. + /// + /// The method or methods. + /// The . + IRequestBuilder UsingMethod(params string[] methods); } \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/IParamsRequestBuilder.cs b/src/WireMock.Net/RequestBuilders/IParamsRequestBuilder.cs index f5822294..763e5a1a 100644 --- a/src/WireMock.Net/RequestBuilders/IParamsRequestBuilder.cs +++ b/src/WireMock.Net/RequestBuilders/IParamsRequestBuilder.cs @@ -1,110 +1,108 @@ -using JetBrains.Annotations; using System; using System.Collections.Generic; using WireMock.Matchers; using WireMock.Types; -namespace WireMock.RequestBuilders +namespace WireMock.RequestBuilders; + +/// +/// The ParamsRequestBuilder interface. +/// +public interface IParamsRequestBuilder : IBodyRequestBuilder { /// - /// The ParamsRequestBuilder interface. + /// WithParam: matching on key only. /// - public interface IParamsRequestBuilder : IBodyRequestBuilder - { - /// - /// WithParam: matching on key only. - /// - /// The key. - /// The match behaviour (optional). - /// The . - IRequestBuilder WithParam([NotNull] string key, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + /// The key. + /// The match behaviour (optional). + /// The . + IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - /// - /// WithParam: matching on key only. - /// - /// The key. - /// Defines if the key should be matched using case-ignore. - /// The match behaviour (optional). - /// The . - IRequestBuilder WithParam([NotNull] string key, bool ignoreCase, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + /// + /// WithParam: matching on key only. + /// + /// The key. + /// Defines if the key should be matched using case-ignore. + /// The match behaviour (optional). + /// The . + IRequestBuilder WithParam(string key, bool ignoreCase, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - /// - /// WithParam: matching on key and values. - /// - /// The key. - /// The values. - /// The . - IRequestBuilder WithParam([NotNull] string key, [CanBeNull] params string[] values); + /// + /// WithParam: matching on key and values. + /// + /// The key. + /// The values. + /// The . + IRequestBuilder WithParam(string key, params string[] values); - /// - /// WithParam: matching on key and values. - /// - /// The key. - /// Defines if the key should be matched using case-ignore. - /// The values. - /// The . - IRequestBuilder WithParam([NotNull] string key, bool ignoreCase, [CanBeNull] params string[] values); + /// + /// WithParam: matching on key and values. + /// + /// The key. + /// Defines if the key should be matched using case-ignore. + /// The values. + /// The . + IRequestBuilder WithParam(string key, bool ignoreCase, params string[] values); - /// - /// WithParam: matching on key and matchers. - /// - /// The key. - /// The matchers. - /// The . - IRequestBuilder WithParam([NotNull] string key, [CanBeNull] params IStringMatcher[] matchers); + /// + /// WithParam: matching on key and matchers. + /// + /// The key. + /// The matchers. + /// The . + IRequestBuilder WithParam(string key, params IStringMatcher[] matchers); - /// - /// WithParam: matching on key and matchers. - /// - /// The key. - /// Defines if the key should be matched using case-ignore. - /// The matchers. - /// The . - IRequestBuilder WithParam([NotNull] string key, bool ignoreCase, [CanBeNull] params IStringMatcher[] matchers); + /// + /// WithParam: matching on key and matchers. + /// + /// The key. + /// Defines if the key should be matched using case-ignore. + /// The matchers. + /// The . + IRequestBuilder WithParam(string key, bool ignoreCase, params IStringMatcher[] matchers); - /// - /// WithParam: matching on key, values and matchBehaviour. - /// - /// The key. - /// The values. - /// The match behaviour. - /// The . - IRequestBuilder WithParam([NotNull] string key, MatchBehaviour matchBehaviour, [CanBeNull] params string[] values); + /// + /// WithParam: matching on key, values and matchBehaviour. + /// + /// The key. + /// The values. + /// The match behaviour. + /// The . + IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, params string[] values); - /// - /// WithParam: matching on key, values and matchBehaviour. - /// - /// The key. - /// Defines if the key should be matched using case-ignore. - /// The values. - /// The match behaviour. - /// The . - IRequestBuilder WithParam([NotNull] string key, MatchBehaviour matchBehaviour, bool ignoreCase = false, [CanBeNull] params string[] values); + /// + /// WithParam: matching on key, values and matchBehaviour. + /// + /// The key. + /// Defines if the key should be matched using case-ignore. + /// The values. + /// The match behaviour. + /// The . + IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, bool ignoreCase = false, params string[] values); - /// - /// WithParam: matching on key, matchers and matchBehaviour. - /// - /// The key. - /// The matchers. - /// The match behaviour. - /// The . - IRequestBuilder WithParam([NotNull] string key, MatchBehaviour matchBehaviour, [CanBeNull] params IStringMatcher[] matchers); + /// + /// WithParam: matching on key, matchers and matchBehaviour. + /// + /// The key. + /// The matchers. + /// The match behaviour. + /// The . + IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, params IStringMatcher[] matchers); - /// - /// WithParam: matching on key, matchers and matchBehaviour. - /// - /// The key. - /// Defines if the key should be matched using case-ignore. - /// The matchers. - /// The match behaviour. - /// The . - IRequestBuilder WithParam([NotNull] string key, MatchBehaviour matchBehaviour, bool ignoreCase = false, [CanBeNull] params IStringMatcher[] matchers); + /// + /// WithParam: matching on key, matchers and matchBehaviour. + /// + /// The key. + /// Defines if the key should be matched using case-ignore. + /// The matchers. + /// The match behaviour. + /// The . + IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, bool ignoreCase = false, params IStringMatcher[] matchers); - /// - /// WithParam: matching on functions. - /// - /// The funcs. - /// The . - IRequestBuilder WithParam([NotNull] params Func>, bool>[] funcs); - } + /// + /// WithParam: matching on functions. + /// + /// The funcs. + /// The . + IRequestBuilder WithParam(params Func>, bool>[] funcs); } \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/IUrlAndPathRequestBuilder.cs b/src/WireMock.Net/RequestBuilders/IUrlAndPathRequestBuilder.cs index 1c62a4f8..863535f2 100644 --- a/src/WireMock.Net/RequestBuilders/IUrlAndPathRequestBuilder.cs +++ b/src/WireMock.Net/RequestBuilders/IUrlAndPathRequestBuilder.cs @@ -1,70 +1,84 @@ -using System; -using JetBrains.Annotations; +using System; using WireMock.Matchers; -namespace WireMock.RequestBuilders +namespace WireMock.RequestBuilders; + +/// +/// IUrlAndPathRequestBuilder +/// +public interface IUrlAndPathRequestBuilder : IMethodRequestBuilder { /// - /// IUrlAndPathRequestBuilder + /// WithPath: add path matching based on IStringMatchers. /// - public interface IUrlAndPathRequestBuilder : IMethodRequestBuilder - { - /// - /// WithPath: add path matching based on IStringMatchers. - /// - /// The matchers. - /// The . - IRequestBuilder WithPath([NotNull] params IStringMatcher[] matchers); + /// The matchers. + /// The . + IRequestBuilder WithPath(params IStringMatcher[] matchers); - /// - /// WithPath: add path matching based on paths. - /// - /// The paths. - /// The . - IRequestBuilder WithPath([NotNull] params string[] paths); + /// + /// WithPath: add path matching based on MatchOperator and IStringMatchers. + /// + /// The to use. + /// The matchers. + /// The . + IRequestBuilder WithPath(MatchOperator matchOperator, params IStringMatcher[] matchers); - /// - /// WithPath: add path matching based on paths and matchBehaviour. - /// - /// The match behaviour. - /// The paths. - /// The . - IRequestBuilder WithPath(MatchBehaviour matchBehaviour, [NotNull] params string[] paths); + /// + /// WithPath: add path matching based on paths. + /// + /// The paths. + /// The . + IRequestBuilder WithPath(params string[] paths); - /// - /// WithPath: add path matching based on functions. - /// - /// The path funcs. - /// The . - IRequestBuilder WithPath([NotNull] params Func[] funcs); + /// + /// WithPath: add path matching based on paths , matchBehaviour and MatchOperator. + /// + /// The to use. + /// The paths. + /// The . + IRequestBuilder WithPath(MatchOperator matchOperator, params string[] paths); - /// - /// WithUrl: add url matching based on IStringMatcher[]. - /// - /// The matchers. - /// The . - IRequestBuilder WithUrl([NotNull] params IStringMatcher[] matchers); + /// + /// WithPath: add path matching based on functions. + /// + /// The path funcs. + /// The . + IRequestBuilder WithPath(params Func[] funcs); - /// - /// WithUrl: add url matching based on urls. - /// - /// The urls. - /// The . - IRequestBuilder WithUrl([NotNull] params string[] urls); + /// + /// WithUrl: add url matching based on IStringMatcher[]. + /// + /// The matchers. + /// The . + IRequestBuilder WithUrl(params IStringMatcher[] matchers); - /// - /// WithUrl: add url matching based on urls. - /// - /// The match behaviour. - /// The urls. - /// The . - IRequestBuilder WithUrl(MatchBehaviour matchBehaviour, [NotNull] params string[] urls); + /// + /// WithUrl: add url matching based on MatchOperator and IStringMatchers. + /// + /// The to use. + /// The matchers. + /// The . + IRequestBuilder WithUrl(MatchOperator matchOperator, params IStringMatcher[] matchers); - /// - /// WithUrl: add url matching based on functions. - /// - /// The url functions. - /// The . - IRequestBuilder WithUrl([NotNull] params Func[] funcs); - } + /// + /// WithUrl: add url matching based on urls. + /// + /// The urls. + /// The . + IRequestBuilder WithUrl(params string[] urls); + + /// + /// WithUrl: add url matching based on urls. + /// + /// The to use. + /// The urls. + /// The . + IRequestBuilder WithUrl(MatchOperator matchOperator, params string[] urls); + + /// + /// WithUrl: add url matching based on functions. + /// + /// The url functions. + /// The . + IRequestBuilder WithUrl(params Func[] funcs); } \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/Request.ClientIP.cs b/src/WireMock.Net/RequestBuilders/Request.ClientIP.cs new file mode 100644 index 00000000..df9e5471 --- /dev/null +++ b/src/WireMock.Net/RequestBuilders/Request.ClientIP.cs @@ -0,0 +1,48 @@ +using System; +using Stef.Validation; +using WireMock.Matchers; +using WireMock.Matchers.Request; + +namespace WireMock.RequestBuilders; + +public partial class Request +{ + /// + public IRequestBuilder WithClientIP(params IStringMatcher[] matchers) + { + return WithClientIP(MatchOperator.Or, matchers); + } + + /// + public IRequestBuilder WithClientIP(MatchOperator matchOperator, params IStringMatcher[] matchers) + { + Guard.NotNullOrEmpty(matchers); + + _requestMatchers.Add(new RequestMessageClientIPMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, matchers)); + return this; + } + + /// + public IRequestBuilder WithClientIP(params string[] paths) + { + return WithClientIP(MatchOperator.Or, paths); + } + + /// + public IRequestBuilder WithClientIP(MatchOperator matchOperator, params string[] paths) + { + Guard.NotNullOrEmpty(paths); + + _requestMatchers.Add(new RequestMessageClientIPMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, paths)); + return this; + } + + /// + public IRequestBuilder WithClientIP(params Func[] funcs) + { + Guard.NotNullOrEmpty(funcs); + + _requestMatchers.Add(new RequestMessageClientIPMatcher(funcs)); + return this; + } +} \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/Request.UsingMethods.cs b/src/WireMock.Net/RequestBuilders/Request.UsingMethods.cs index b2039624..a380c5b5 100644 --- a/src/WireMock.Net/RequestBuilders/Request.UsingMethods.cs +++ b/src/WireMock.Net/RequestBuilders/Request.UsingMethods.cs @@ -1,4 +1,4 @@ -// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. +// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. // For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. using System.Linq; using WireMock.Http; @@ -6,110 +6,97 @@ using WireMock.Matchers; using WireMock.Matchers.Request; using Stef.Validation; -namespace WireMock.RequestBuilders +namespace WireMock.RequestBuilders; + +public partial class Request { - public partial class Request + /// + public IRequestBuilder UsingConnect(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) { - /// - public IRequestBuilder UsingConnect(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethods.CONNECT)); + return this; + } + + /// + public IRequestBuilder UsingDelete(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethods.DELETE)); + return this; + } + + /// + public IRequestBuilder UsingGet(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethods.GET)); + return this; + } + + /// + public IRequestBuilder UsingHead(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethods.HEAD)); + return this; + } + + /// + public IRequestBuilder UsingOptions(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethods.OPTIONS)); + return this; + } + + /// + public IRequestBuilder UsingPost(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethods.POST)); + return this; + } + + /// + public IRequestBuilder UsingPatch(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethods.PATCH)); + return this; + } + + /// + public IRequestBuilder UsingPut(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethods.PUT)); + return this; + } + + /// + public IRequestBuilder UsingTrace(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, MatchOperator.Or, HttpRequestMethods.TRACE)); + return this; + } + + /// + public IRequestBuilder UsingAnyMethod() + { + var matchers = _requestMatchers.Where(m => m is RequestMessageMethodMatcher).ToList(); + foreach (var matcher in matchers) { - _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, HttpRequestMethods.CONNECT)); - return this; + _requestMatchers.Remove(matcher); } - /// - public IRequestBuilder UsingDelete(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, HttpRequestMethods.DELETE)); - return this; - } + return this; + } - /// - public IRequestBuilder UsingGet(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, HttpRequestMethods.GET)); - return this; - } + /// + public IRequestBuilder UsingMethod(params string[] methods) + { + return UsingMethod(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, methods); + } - /// - public IRequestBuilder UsingHead(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, HttpRequestMethods.HEAD)); - return this; - } + /// + public IRequestBuilder UsingMethod(MatchBehaviour matchBehaviour, MatchOperator matchOperator, params string[] methods) + { + Guard.NotNullOrEmpty(methods); - /// - public IRequestBuilder UsingOptions(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, HttpRequestMethods.OPTIONS)); - return this; - } - - /// - public IRequestBuilder UsingPost(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, HttpRequestMethods.POST)); - return this; - } - - /// - public IRequestBuilder UsingPatch(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, HttpRequestMethods.PATCH)); - return this; - } - - /// - public IRequestBuilder UsingPut(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, HttpRequestMethods.PUT)); - return this; - } - - /// - public IRequestBuilder UsingTrace(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, HttpRequestMethods.TRACE)); - return this; - } - - /// - public IRequestBuilder UsingAnyMethod() - { - var matchers = _requestMatchers.Where(m => m is RequestMessageMethodMatcher).ToList(); - foreach (var matcher in matchers) - { - _requestMatchers.Remove(matcher); - } - - return this; - } - - /// - public IRequestBuilder UsingAnyVerb() - { - return UsingAnyMethod(); - } - - /// - public IRequestBuilder UsingMethod(params string[] methods) - { - return UsingMethod(MatchBehaviour.AcceptOnMatch, methods); - } - - /// - public IRequestBuilder UsingVerb(params string[] verbs) - { - return UsingMethod(verbs); - } - - /// - public IRequestBuilder UsingMethod(MatchBehaviour matchBehaviour, params string[] methods) - { - Guard.NotNullOrEmpty(methods, nameof(methods)); - - _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, methods)); - return this; - } + _requestMatchers.Add(new RequestMessageMethodMatcher(matchBehaviour, matchOperator, methods)); + return this; } } \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/Request.WithBody.cs b/src/WireMock.Net/RequestBuilders/Request.WithBody.cs index a11c7bab..ca1ca6a2 100644 --- a/src/WireMock.Net/RequestBuilders/Request.WithBody.cs +++ b/src/WireMock.Net/RequestBuilders/Request.WithBody.cs @@ -1,4 +1,4 @@ -// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. +// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. // For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. using System; using WireMock.Matchers; @@ -6,80 +6,79 @@ using WireMock.Matchers.Request; using WireMock.Util; using Stef.Validation; -namespace WireMock.RequestBuilders +namespace WireMock.RequestBuilders; + +public partial class Request { - public partial class Request + /// + public IRequestBuilder WithBody(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) { - /// - public IRequestBuilder WithBody(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - _requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body)); - return this; - } - - /// - public IRequestBuilder WithBody(byte[] body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - _requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body)); - return this; - } - - /// - public IRequestBuilder WithBody(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - _requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body)); - return this; - } - - /// - public IRequestBuilder WithBody(IMatcher matcher) - { - return WithBody(new[] { matcher }); - } - - /// - public IRequestBuilder WithBody(IMatcher[] matchers) - { - Guard.NotNull(matchers, nameof(matchers)); - - _requestMatchers.Add(new RequestMessageBodyMatcher(matchers)); - return this; - } - - /// - public IRequestBuilder WithBody(Func func) - { - Guard.NotNull(func, nameof(func)); - - _requestMatchers.Add(new RequestMessageBodyMatcher(func)); - return this; - } - - /// - public IRequestBuilder WithBody(Func func) - { - Guard.NotNull(func, nameof(func)); - - _requestMatchers.Add(new RequestMessageBodyMatcher(func)); - return this; - } - - /// - public IRequestBuilder WithBody(Func func) - { - Guard.NotNull(func, nameof(func)); - - _requestMatchers.Add(new RequestMessageBodyMatcher(func)); - return this; - } - - /// - public IRequestBuilder WithBody(Func func) - { - Guard.NotNull(func, nameof(func)); - - _requestMatchers.Add(new RequestMessageBodyMatcher(func)); - return this; - } + _requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body)); + return this; } -} + + /// + public IRequestBuilder WithBody(byte[] body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + _requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body)); + return this; + } + + /// + public IRequestBuilder WithBody(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + _requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body)); + return this; + } + + /// + public IRequestBuilder WithBody(IMatcher matcher) + { + return WithBody(new[] { matcher }); + } + + /// + public IRequestBuilder WithBody(IMatcher[] matchers, MatchOperator matchOperator = MatchOperator.Or) + { + Guard.NotNull(matchers); + + _requestMatchers.Add(new RequestMessageBodyMatcher(matchOperator, matchers)); + return this; + } + + /// + public IRequestBuilder WithBody(Func func) + { + Guard.NotNull(func, nameof(func)); + + _requestMatchers.Add(new RequestMessageBodyMatcher(func)); + return this; + } + + /// + public IRequestBuilder WithBody(Func func) + { + Guard.NotNull(func, nameof(func)); + + _requestMatchers.Add(new RequestMessageBodyMatcher(func)); + return this; + } + + /// + public IRequestBuilder WithBody(Func func) + { + Guard.NotNull(func, nameof(func)); + + _requestMatchers.Add(new RequestMessageBodyMatcher(func)); + return this; + } + + /// + public IRequestBuilder WithBody(Func func) + { + Guard.NotNull(func, nameof(func)); + + _requestMatchers.Add(new RequestMessageBodyMatcher(func)); + return this; + } +} \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/Request.WithHeaders.cs b/src/WireMock.Net/RequestBuilders/Request.WithHeaders.cs index 481b7524..41f07c06 100644 --- a/src/WireMock.Net/RequestBuilders/Request.WithHeaders.cs +++ b/src/WireMock.Net/RequestBuilders/Request.WithHeaders.cs @@ -1,4 +1,4 @@ -// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. +// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. // For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. using System; using System.Collections.Generic; @@ -6,79 +6,78 @@ using WireMock.Matchers; using WireMock.Matchers.Request; using Stef.Validation; -namespace WireMock.RequestBuilders +namespace WireMock.RequestBuilders; + +public partial class Request { - public partial class Request + /// + public IRequestBuilder WithHeader(string name, string pattern, MatchBehaviour matchBehaviour) { - /// - public IRequestBuilder WithHeader(string name, string pattern, MatchBehaviour matchBehaviour) - { - return WithHeader(name, pattern, true, matchBehaviour); - } + return WithHeader(name, pattern, true, matchBehaviour); + } - /// - public IRequestBuilder WithHeader(string name, string pattern, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - Guard.NotNull(name, nameof(name)); - Guard.NotNull(pattern, nameof(pattern)); + /// + public IRequestBuilder WithHeader(string name, string pattern, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + Guard.NotNull(name); + Guard.NotNull(pattern); - _requestMatchers.Add(new RequestMessageHeaderMatcher(matchBehaviour, name, pattern, ignoreCase)); - return this; - } + _requestMatchers.Add(new RequestMessageHeaderMatcher(matchBehaviour, name, pattern, ignoreCase)); + return this; + } - /// - public IRequestBuilder WithHeader(string name, string[] patterns, MatchBehaviour matchBehaviour) - { - return WithHeader(name, patterns, true, matchBehaviour); - } + /// + public IRequestBuilder WithHeader(string name, string[] patterns, MatchBehaviour matchBehaviour, MatchOperator matchOperator = MatchOperator.Or) + { + return WithHeader(name, patterns, true, matchBehaviour, matchOperator); + } - /// - public IRequestBuilder WithHeader(string name, string[] patterns, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - Guard.NotNull(name, nameof(name)); - Guard.NotNull(patterns, nameof(patterns)); + /// + public IRequestBuilder WithHeader(string name, string[] patterns, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.Or) + { + Guard.NotNull(name); + Guard.NotNull(patterns); - _requestMatchers.Add(new RequestMessageHeaderMatcher(matchBehaviour, name, ignoreCase, patterns)); - return this; - } + _requestMatchers.Add(new RequestMessageHeaderMatcher(matchBehaviour, matchOperator, name, ignoreCase, patterns)); + return this; + } - /// - public IRequestBuilder WithHeader(string name, params IStringMatcher[] matchers) - { - Guard.NotNull(name, nameof(name)); - Guard.NotNullOrEmpty(matchers, nameof(matchers)); + /// + public IRequestBuilder WithHeader(string name, params IStringMatcher[] matchers) + { + Guard.NotNull(name); + Guard.NotNullOrEmpty(matchers); - _requestMatchers.Add(new RequestMessageHeaderMatcher(MatchBehaviour.AcceptOnMatch, name, false, matchers)); - return this; - } + _requestMatchers.Add(new RequestMessageHeaderMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, name, false, matchers)); + return this; + } - /// - public IRequestBuilder WithHeader(string name, bool ignoreCase, params IStringMatcher[] matchers) - { - Guard.NotNull(name, nameof(name)); - Guard.NotNullOrEmpty(matchers, nameof(matchers)); + /// + public IRequestBuilder WithHeader(string name, bool ignoreCase, params IStringMatcher[] matchers) + { + Guard.NotNull(name); + Guard.NotNullOrEmpty(matchers); - _requestMatchers.Add(new RequestMessageHeaderMatcher(MatchBehaviour.AcceptOnMatch, name, ignoreCase, matchers)); - return this; - } + _requestMatchers.Add(new RequestMessageHeaderMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, name, ignoreCase, matchers)); + return this; + } - /// - public IRequestBuilder WithHeader(string name, bool ignoreCase, MatchBehaviour matchBehaviour, params IStringMatcher[] matchers) - { - Guard.NotNull(name, nameof(name)); - Guard.NotNullOrEmpty(matchers, nameof(matchers)); + /// + public IRequestBuilder WithHeader(string name, bool ignoreCase, MatchBehaviour matchBehaviour, MatchOperator matchOperator, params IStringMatcher[] matchers) + { + Guard.NotNull(name); + Guard.NotNullOrEmpty(matchers); - _requestMatchers.Add(new RequestMessageHeaderMatcher(matchBehaviour, name, ignoreCase, matchers)); - return this; - } + _requestMatchers.Add(new RequestMessageHeaderMatcher(matchBehaviour, matchOperator, name, ignoreCase, matchers)); + return this; + } - /// - public IRequestBuilder WithHeader(params Func, bool>[] funcs) - { - Guard.NotNullOrEmpty(funcs, nameof(funcs)); + /// + public IRequestBuilder WithHeader(params Func, bool>[] funcs) + { + Guard.NotNullOrEmpty(funcs); - _requestMatchers.Add(new RequestMessageHeaderMatcher(funcs)); - return this; - } + _requestMatchers.Add(new RequestMessageHeaderMatcher(funcs)); + return this; } } \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/Request.WithParam.cs b/src/WireMock.Net/RequestBuilders/Request.WithParam.cs index d6605070..0b300f22 100644 --- a/src/WireMock.Net/RequestBuilders/Request.WithParam.cs +++ b/src/WireMock.Net/RequestBuilders/Request.WithParam.cs @@ -1,4 +1,4 @@ -// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. +// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. // For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. using System; using System.Collections.Generic; @@ -59,7 +59,7 @@ namespace WireMock.RequestBuilders /// public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, bool ignoreCase = false, params string[] values) { - Guard.NotNull(key, nameof(key)); + Guard.NotNull(key); _requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase, values)); return this; @@ -74,7 +74,7 @@ namespace WireMock.RequestBuilders /// public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, bool ignoreCase, params IStringMatcher[] matchers) { - Guard.NotNull(key, nameof(key)); + Guard.NotNull(key); _requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase, matchers)); return this; diff --git a/src/WireMock.Net/RequestBuilders/Request.WithPath.cs b/src/WireMock.Net/RequestBuilders/Request.WithPath.cs new file mode 100644 index 00000000..24f3d200 --- /dev/null +++ b/src/WireMock.Net/RequestBuilders/Request.WithPath.cs @@ -0,0 +1,48 @@ +using System; +using Stef.Validation; +using WireMock.Matchers; +using WireMock.Matchers.Request; + +namespace WireMock.RequestBuilders; + +public partial class Request +{ + /// + public IRequestBuilder WithPath(params IStringMatcher[] matchers) + { + return WithPath(MatchOperator.Or, matchers); + } + + /// + public IRequestBuilder WithPath(MatchOperator matchOperator, params IStringMatcher[] matchers) + { + Guard.NotNullOrEmpty(matchers); + + _requestMatchers.Add(new RequestMessagePathMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, matchers)); + return this; + } + + /// + public IRequestBuilder WithPath(params string[] paths) + { + return WithPath(MatchOperator.Or, paths); + } + + /// + public IRequestBuilder WithPath(MatchOperator matchOperator, params string[] paths) + { + Guard.NotNullOrEmpty(paths); + + _requestMatchers.Add(new RequestMessagePathMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, paths)); + return this; + } + + /// + public IRequestBuilder WithPath(params Func[] funcs) + { + Guard.NotNullOrEmpty(funcs); + + _requestMatchers.Add(new RequestMessagePathMatcher(funcs)); + return this; + } +} \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/Request.WithUrl.cs b/src/WireMock.Net/RequestBuilders/Request.WithUrl.cs new file mode 100644 index 00000000..6954623d --- /dev/null +++ b/src/WireMock.Net/RequestBuilders/Request.WithUrl.cs @@ -0,0 +1,49 @@ +using System; +using Stef.Validation; +using WireMock.Matchers; +using WireMock.Matchers.Request; + +namespace WireMock.RequestBuilders +{ + public partial class Request + { + /// + public IRequestBuilder WithUrl(params IStringMatcher[] matchers) + { + return WithUrl(MatchOperator.Or, matchers); + } + + /// + public IRequestBuilder WithUrl(MatchOperator matchOperator, params IStringMatcher[] matchers) + { + Guard.NotNullOrEmpty(matchers); + + _requestMatchers.Add(new RequestMessageUrlMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, matchers)); + return this; + } + + /// + public IRequestBuilder WithUrl(params string[] urls) + { + return WithUrl(MatchOperator.Or, urls); + } + + /// + public IRequestBuilder WithUrl(MatchOperator matchOperator, params string[] urls) + { + Guard.NotNullOrEmpty(urls); + + _requestMatchers.Add(new RequestMessageUrlMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, urls)); + return this; + } + + /// + public IRequestBuilder WithUrl(params Func[] funcs) + { + Guard.NotNullOrEmpty(funcs); + + _requestMatchers.Add(new RequestMessageUrlMatcher(funcs)); + return this; + } + } +} diff --git a/src/WireMock.Net/RequestBuilders/Request.cs b/src/WireMock.Net/RequestBuilders/Request.cs index 114faba4..189c4a20 100644 --- a/src/WireMock.Net/RequestBuilders/Request.cs +++ b/src/WireMock.Net/RequestBuilders/Request.cs @@ -1,167 +1,67 @@ -// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. +// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. // For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using WireMock.Matchers; -using WireMock.Matchers.Request; using Stef.Validation; +using WireMock.Matchers.Request; -namespace WireMock.RequestBuilders +namespace WireMock.RequestBuilders; + +/// +/// The Request Builder +/// +public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder { + private readonly IList _requestMatchers; + /// - /// The requests. + /// Creates this instance. /// - public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder + /// The . + public static IRequestBuilder Create() { - private readonly IList _requestMatchers; - - /// - /// Creates this instance. - /// - /// The . - public static IRequestBuilder Create() - { - return new Request(new List()); - } - - /// - /// Initializes a new instance of the class. - /// - /// The request matchers. - private Request(IList requestMatchers) : base(requestMatchers) - { - _requestMatchers = requestMatchers; - } - - /// - /// Gets the request message matchers. - /// - /// Type of IRequestMatcher - /// A List{T} - public IList GetRequestMessageMatchers() where T : IRequestMatcher - { - return new ReadOnlyCollection(_requestMatchers.OfType().ToList()); - } - - /// - /// Gets the request message matcher. - /// - /// Type of IRequestMatcher - /// A RequestMatcher - public T GetRequestMessageMatcher() where T : IRequestMatcher - { - return _requestMatchers.OfType().FirstOrDefault(); - } - - /// - /// Gets the request message matcher. - /// - /// Type of IRequestMatcher - /// A RequestMatcher - public T GetRequestMessageMatcher(Func func) where T : IRequestMatcher - { - return _requestMatchers.OfType().FirstOrDefault(func); - } - - /// - public IRequestBuilder WithClientIP(params IStringMatcher[] matchers) - { - Guard.NotNullOrEmpty(matchers, nameof(matchers)); - - _requestMatchers.Add(new RequestMessageClientIPMatcher(matchers)); - return this; - } - - /// - public IRequestBuilder WithClientIP(params string[] clientIPs) - { - return WithClientIP(MatchBehaviour.AcceptOnMatch, clientIPs); - } - - /// - public IRequestBuilder WithClientIP(MatchBehaviour matchBehaviour, params string[] clientIPs) - { - Guard.NotNullOrEmpty(clientIPs, nameof(clientIPs)); - - _requestMatchers.Add(new RequestMessageClientIPMatcher(matchBehaviour, clientIPs)); - return this; - } - - /// - public IRequestBuilder WithClientIP(params Func[] funcs) - { - Guard.NotNullOrEmpty(funcs, nameof(funcs)); - - _requestMatchers.Add(new RequestMessageClientIPMatcher(funcs)); - return this; - } - - /// - public IRequestBuilder WithPath(params IStringMatcher[] matchers) - { - Guard.NotNullOrEmpty(matchers, nameof(matchers)); - - _requestMatchers.Add(new RequestMessagePathMatcher(matchers)); - return this; - } - - /// - public IRequestBuilder WithPath(params string[] paths) - { - return WithPath(MatchBehaviour.AcceptOnMatch, paths); - } - - /// - public IRequestBuilder WithPath(MatchBehaviour matchBehaviour, params string[] paths) - { - Guard.NotNullOrEmpty(paths, nameof(paths)); - - _requestMatchers.Add(new RequestMessagePathMatcher(matchBehaviour, paths)); - return this; - } - - /// - public IRequestBuilder WithPath(params Func[] funcs) - { - Guard.NotNullOrEmpty(funcs, nameof(funcs)); - - _requestMatchers.Add(new RequestMessagePathMatcher(funcs)); - return this; - } - - /// - public IRequestBuilder WithUrl(params IStringMatcher[] matchers) - { - Guard.NotNullOrEmpty(matchers, nameof(matchers)); - - _requestMatchers.Add(new RequestMessageUrlMatcher(matchers)); - return this; - } - - /// - public IRequestBuilder WithUrl(params string[] urls) - { - return WithUrl(MatchBehaviour.AcceptOnMatch, urls); - } - - /// - public IRequestBuilder WithUrl(MatchBehaviour matchBehaviour, params string[] urls) - { - Guard.NotNullOrEmpty(urls, nameof(urls)); - - _requestMatchers.Add(new RequestMessageUrlMatcher(matchBehaviour, urls)); - return this; - } - - /// - public IRequestBuilder WithUrl(params Func[] funcs) - { - Guard.NotNullOrEmpty(funcs, nameof(funcs)); - - _requestMatchers.Add(new RequestMessageUrlMatcher(funcs)); - return this; - } + return new Request(new List()); } + + /// + /// Initializes a new instance of the class. + /// + /// The request matchers. + private Request(IList requestMatchers) : base(requestMatchers) + { + _requestMatchers = Guard.NotNull(requestMatchers); + } + + /// + /// Gets the request message matchers. + /// + /// Type of IRequestMatcher + /// A List{T} + public IList GetRequestMessageMatchers() where T : IRequestMatcher + { + return new ReadOnlyCollection(_requestMatchers.OfType().ToList()); + } + + /// + /// Gets the request message matcher. + /// + /// Type of IRequestMatcher + /// A RequestMatcher + public T? GetRequestMessageMatcher() where T : IRequestMatcher + { + return _requestMatchers.OfType().FirstOrDefault(); + } + + /// + /// Gets the request message matcher. + /// + /// Type of IRequestMatcher + /// A RequestMatcher + public T? GetRequestMessageMatcher(Func func) where T : IRequestMatcher + { + return _requestMatchers.OfType().FirstOrDefault(func); + } + } \ No newline at end of file diff --git a/src/WireMock.Net/RequestMessage.cs b/src/WireMock.Net/RequestMessage.cs index b8945482..b2fba215 100644 --- a/src/WireMock.Net/RequestMessage.cs +++ b/src/WireMock.Net/RequestMessage.cs @@ -1,4 +1,4 @@ -// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. +// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. // For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. using JetBrains.Annotations; using System; @@ -48,19 +48,19 @@ namespace WireMock public string Method { get; } /// - public IDictionary> Headers { get; } + public IDictionary>? Headers { get; } /// - public IDictionary Cookies { get; } + public IDictionary? Cookies { get; } /// - public IDictionary> Query { get; } + public IDictionary>? Query { get; } /// public string RawQuery { get; } /// - public IBodyData BodyData { get; } + public IBodyData? BodyData { get; } /// public string Body { get; } @@ -101,7 +101,7 @@ namespace WireMock /// The BodyData. /// The headers. /// The cookies. - public RequestMessage([NotNull] UrlDetails urlDetails, [NotNull] string method, [NotNull] string clientIP, [CanBeNull] IBodyData bodyData = null, [CanBeNull] IDictionary headers = null, [CanBeNull] IDictionary cookies = null) + public RequestMessage(UrlDetails urlDetails, string method, string clientIP, IBodyData? bodyData = null, IDictionary? headers = null, IDictionary? cookies = null) { Guard.NotNull(urlDetails, nameof(urlDetails)); Guard.NotNull(method, nameof(method)); @@ -144,7 +144,7 @@ namespace WireMock /// The key. /// Defines if the key should be matched using case-ignore. /// The query parameter. - public WireMockList GetParameter(string key, bool ignoreCase = false) + public WireMockList? GetParameter(string? key, bool ignoreCase = false) { if (Query == null) { diff --git a/src/WireMock.Net/ResponseBuilders/Response.WithProxy.cs b/src/WireMock.Net/ResponseBuilders/Response.WithProxy.cs index 69951adf..72fc5342 100644 --- a/src/WireMock.Net/ResponseBuilders/Response.WithProxy.cs +++ b/src/WireMock.Net/ResponseBuilders/Response.WithProxy.cs @@ -3,40 +3,39 @@ using WireMock.Http; using WireMock.Settings; using Stef.Validation; -namespace WireMock.ResponseBuilders +namespace WireMock.ResponseBuilders; + +public partial class Response { - public partial class Response + private HttpClient _httpClientForProxy; + + /// + /// The WebProxy settings. + /// + public ProxyAndRecordSettings? ProxyAndRecordSettings { get; private set; } + + /// + public IResponseBuilder WithProxy(string proxyUrl, string? clientX509Certificate2ThumbprintOrSubjectName = null) { - private HttpClient _httpClientForProxy; + Guard.NotNullOrEmpty(proxyUrl); - /// - /// The WebProxy settings. - /// - public ProxyAndRecordSettings ProxyAndRecordSettings { get; private set; } - - /// - public IResponseBuilder WithProxy(string proxyUrl, string clientX509Certificate2ThumbprintOrSubjectName = null) + var settings = new ProxyAndRecordSettings { - Guard.NotNullOrEmpty(proxyUrl, nameof(proxyUrl)); + Url = proxyUrl, + ClientX509Certificate2ThumbprintOrSubjectName = clientX509Certificate2ThumbprintOrSubjectName + }; - var settings = new ProxyAndRecordSettings - { - Url = proxyUrl, - ClientX509Certificate2ThumbprintOrSubjectName = clientX509Certificate2ThumbprintOrSubjectName - }; + return WithProxy(settings); + } - return WithProxy(settings); - } + /// + public IResponseBuilder WithProxy(ProxyAndRecordSettings settings) + { + Guard.NotNull(settings); - /// - public IResponseBuilder WithProxy(ProxyAndRecordSettings settings) - { - Guard.NotNull(settings, nameof(settings)); + ProxyAndRecordSettings = settings; - ProxyAndRecordSettings = settings; - - _httpClientForProxy = HttpClientBuilder.Build(settings); - return this; - } + _httpClientForProxy = HttpClientBuilder.Build(settings); + return this; } } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseMessage.cs b/src/WireMock.Net/ResponseMessage.cs index c4300b3e..98b3647a 100644 --- a/src/WireMock.Net/ResponseMessage.cs +++ b/src/WireMock.Net/ResponseMessage.cs @@ -15,7 +15,7 @@ namespace WireMock public class ResponseMessage : IResponseMessage { /// - public IDictionary> Headers { get; set; } = new Dictionary>(); + public IDictionary>? Headers { get; set; } = new Dictionary>(); /// public object StatusCode { get; set; } diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs index 3e23db77..2ebb08d4 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net/Serialization/MappingConverter.cs @@ -4,234 +4,254 @@ using System.Linq; using System.Threading; using Stef.Validation; using WireMock.Admin.Mappings; +using WireMock.Matchers; using WireMock.Matchers.Request; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using WireMock.Settings; using WireMock.Types; -namespace WireMock.Serialization -{ - internal class MappingConverter - { - private readonly MatcherMapper _mapper; +namespace WireMock.Serialization; - public MappingConverter(MatcherMapper mapper) +internal class MappingConverter +{ + private readonly MatcherMapper _mapper; + + public MappingConverter(MatcherMapper mapper) + { + _mapper = Guard.NotNull(mapper, nameof(mapper)); + } + + public MappingModel ToMappingModel(IMapping mapping) + { + var request = (Request)mapping.RequestMatcher; + var response = (Response)mapping.Provider; + + var clientIPMatcher = request.GetRequestMessageMatcher(); + var pathMatcher = request.GetRequestMessageMatcher(); + var urlMatcher = request.GetRequestMessageMatcher(); + var headerMatchers = request.GetRequestMessageMatchers(); + var cookieMatchers = request.GetRequestMessageMatchers(); + var paramsMatchers = request.GetRequestMessageMatchers(); + var methodMatcher = request.GetRequestMessageMatcher(); + var bodyMatcher = request.GetRequestMessageMatcher(); + + var mappingModel = new MappingModel { - _mapper = Guard.NotNull(mapper, nameof(mapper)); + Guid = mapping.Guid, + TimeSettings = TimeSettingsMapper.Map(mapping.TimeSettings), + Title = mapping.Title, + Description = mapping.Description, + Priority = mapping.Priority != 0 ? mapping.Priority : null, + Scenario = mapping.Scenario, + WhenStateIs = mapping.ExecutionConditionState, + SetStateTo = mapping.NextState, + Request = new RequestModel + { + Headers = headerMatchers.Any() ? headerMatchers.Select(hm => new HeaderModel + { + Name = hm.Name, + Matchers = _mapper.Map(hm.Matchers) + }).ToList() : null, + + Cookies = cookieMatchers.Any() ? cookieMatchers.Select(cm => new CookieModel + { + Name = cm.Name, + Matchers = _mapper.Map(cm.Matchers) + }).ToList() : null, + + Params = paramsMatchers.Any() ? paramsMatchers.Select(pm => new ParamModel + { + Name = pm.Key, + IgnoreCase = pm.IgnoreCase == true ? true : null, + Matchers = _mapper.Map(pm.Matchers) + }).ToList() : null + }, + Response = new ResponseModel() + }; + + if (methodMatcher is { Methods: { } }) + { + mappingModel.Request.Methods = methodMatcher.Methods; + mappingModel.Request.MethodsRejectOnMatch = methodMatcher.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : null; + mappingModel.Request.MethodsMatchOperator = methodMatcher.Methods.Length > 1 ? methodMatcher.MatchOperator.ToString() : null; } - public MappingModel ToMappingModel(IMapping mapping) + if (clientIPMatcher is { Matchers: { } }) { - var request = (Request)mapping.RequestMatcher; - var response = (Response)mapping.Provider; - - var clientIPMatchers = request.GetRequestMessageMatchers().Where(m => m.Matchers != null).SelectMany(m => m.Matchers).ToList(); - var pathMatchers = request.GetRequestMessageMatchers().Where(m => m.Matchers != null).SelectMany(m => m.Matchers).ToList(); - var urlMatchers = request.GetRequestMessageMatchers().Where(m => m.Matchers != null).SelectMany(m => m.Matchers).ToList(); - var headerMatchers = request.GetRequestMessageMatchers(); - var cookieMatchers = request.GetRequestMessageMatchers(); - var paramsMatchers = request.GetRequestMessageMatchers(); - var methodMatcher = request.GetRequestMessageMatcher(); - var bodyMatcher = request.GetRequestMessageMatcher(); - - var mappingModel = new MappingModel + var clientIPMatchers = _mapper.Map(clientIPMatcher.Matchers); + mappingModel.Request.Path = new ClientIPModel { - Guid = mapping.Guid, - TimeSettings = TimeSettingsMapper.Map(mapping.TimeSettings), - Title = mapping.Title, - Description = mapping.Description, - Priority = mapping.Priority != 0 ? mapping.Priority : (int?)null, - Scenario = mapping.Scenario, - WhenStateIs = mapping.ExecutionConditionState, - SetStateTo = mapping.NextState, - Request = new RequestModel - { - ClientIP = clientIPMatchers.Any() ? new ClientIPModel - { - Matchers = _mapper.Map(clientIPMatchers) - } : null, - - Path = pathMatchers.Any() ? new PathModel - { - Matchers = _mapper.Map(pathMatchers) - } : null, - - Url = urlMatchers.Any() ? new UrlModel - { - Matchers = _mapper.Map(urlMatchers) - } : null, - - Methods = methodMatcher?.Methods, - - Headers = headerMatchers.Any() ? headerMatchers.Select(hm => new HeaderModel - { - Name = hm.Name, - Matchers = _mapper.Map(hm.Matchers) - }).ToList() : null, - - Cookies = cookieMatchers.Any() ? cookieMatchers.Select(cm => new CookieModel - { - Name = cm.Name, - Matchers = _mapper.Map(cm.Matchers) - }).ToList() : null, - - Params = paramsMatchers.Any() ? paramsMatchers.Select(pm => new ParamModel - { - Name = pm.Key, - IgnoreCase = pm.IgnoreCase == true ? true : (bool?)null, - Matchers = _mapper.Map(pm.Matchers) - }).ToList() : null - }, - Response = new ResponseModel() + Matchers = clientIPMatchers, + MatchOperator = clientIPMatchers?.Length > 1 ? clientIPMatcher.MatchOperator.ToString() : null }; + } - if (response.MinimumDelayMilliseconds >= 0 || response.MaximumDelayMilliseconds > 0) + if (pathMatcher is { Matchers: { } }) + { + var pathMatchers = _mapper.Map(pathMatcher.Matchers); + mappingModel.Request.Path = new PathModel { - mappingModel.Response.MinimumRandomDelay = response.MinimumDelayMilliseconds; - mappingModel.Response.MaximumRandomDelay = response.MaximumDelayMilliseconds; + Matchers = pathMatchers, + MatchOperator = pathMatchers?.Length > 1 ? pathMatcher.MatchOperator.ToString() : null + }; + } + else if (urlMatcher is { Matchers: { } }) + { + var urlMatchers = _mapper.Map(urlMatcher.Matchers); + mappingModel.Request.Url = new UrlModel + { + Matchers = urlMatchers, + MatchOperator = urlMatchers?.Length > 1 ? urlMatcher.MatchOperator.ToString() : null + }; + } + + if (response.MinimumDelayMilliseconds >= 0 || response.MaximumDelayMilliseconds > 0) + { + mappingModel.Response.MinimumRandomDelay = response.MinimumDelayMilliseconds; + mappingModel.Response.MaximumRandomDelay = response.MaximumDelayMilliseconds; + } + else + { + mappingModel.Response.Delay = (int?)(response.Delay == Timeout.InfiniteTimeSpan ? TimeSpan.MaxValue.TotalMilliseconds : response.Delay?.TotalMilliseconds); + } + + if (mapping.Webhooks?.Length == 1) + { + mappingModel.Webhook = WebhookMapper.Map(mapping.Webhooks[0]); + } + else if (mapping.Webhooks?.Length > 1) + { + mappingModel.Webhooks = mapping.Webhooks.Select(WebhookMapper.Map).ToArray(); + } + + if (bodyMatcher?.Matchers != null) + { + mappingModel.Request.Body = new BodyModel(); + + if (bodyMatcher.Matchers.Length == 1) + { + mappingModel.Request.Body.Matcher = _mapper.Map(bodyMatcher.Matchers[0]); } - else + else if (bodyMatcher.Matchers.Length > 1) { - mappingModel.Response.Delay = (int?)(response.Delay == Timeout.InfiniteTimeSpan ? TimeSpan.MaxValue.TotalMilliseconds : response.Delay?.TotalMilliseconds); + mappingModel.Request.Body.Matchers = _mapper.Map(bodyMatcher.Matchers); + mappingModel.Request.Body.MatchOperator = bodyMatcher.MatchOperator.ToString(); + } + } + + if (response.ProxyAndRecordSettings != null) + { + mappingModel.Response.StatusCode = null; + mappingModel.Response.Headers = null; + mappingModel.Response.BodyDestination = null; + mappingModel.Response.BodyAsJson = null; + mappingModel.Response.BodyAsJsonIndented = null; + mappingModel.Response.Body = null; + mappingModel.Response.BodyAsBytes = null; + mappingModel.Response.BodyAsFile = null; + mappingModel.Response.BodyAsFileIsCached = null; + mappingModel.Response.UseTransformer = null; + mappingModel.Response.TransformerType = null; + mappingModel.Response.UseTransformerForBodyAsFile = null; + mappingModel.Response.TransformerReplaceNodeOptions = null; + mappingModel.Response.BodyEncoding = null; + mappingModel.Response.ProxyUrl = response.ProxyAndRecordSettings.Url; + mappingModel.Response.Fault = null; + mappingModel.Response.WebProxy = MapWebProxy(response.ProxyAndRecordSettings.WebProxySettings); + } + else + { + mappingModel.Response.WebProxy = null; + mappingModel.Response.BodyDestination = response.ResponseMessage.BodyDestination; + mappingModel.Response.StatusCode = response.ResponseMessage.StatusCode; + + if (response.ResponseMessage.Headers != null && response.ResponseMessage.Headers.Count > 0) + { + mappingModel.Response.Headers = MapHeaders(response.ResponseMessage.Headers); } - if (mapping.Webhooks?.Length == 1) + if (response.UseTransformer) { - mappingModel.Webhook = WebhookMapper.Map(mapping.Webhooks[0]); - } - else if (mapping.Webhooks?.Length > 1) - { - mappingModel.Webhooks = mapping.Webhooks.Select(WebhookMapper.Map).ToArray(); + mappingModel.Response.UseTransformer = response.UseTransformer; + mappingModel.Response.TransformerType = response.TransformerType.ToString(); + mappingModel.Response.TransformerReplaceNodeOptions = response.TransformerReplaceNodeOptions.ToString(); } - if (bodyMatcher?.Matchers != null) + if (response.UseTransformerForBodyAsFile) { - mappingModel.Request.Body = new BodyModel(); - - if (bodyMatcher.Matchers.Length == 1) - { - mappingModel.Request.Body.Matcher = _mapper.Map(bodyMatcher.Matchers[0]); - } - else if (bodyMatcher.Matchers.Length > 1) - { - mappingModel.Request.Body.Matchers = _mapper.Map(bodyMatcher.Matchers); - } + mappingModel.Response.UseTransformerForBodyAsFile = response.UseTransformerForBodyAsFile; } - if (response.ProxyAndRecordSettings != null) + if (response.ResponseMessage.BodyData != null) { - mappingModel.Response.StatusCode = null; - mappingModel.Response.Headers = null; - mappingModel.Response.BodyDestination = null; - mappingModel.Response.BodyAsJson = null; - mappingModel.Response.BodyAsJsonIndented = null; - mappingModel.Response.Body = null; - mappingModel.Response.BodyAsBytes = null; - mappingModel.Response.BodyAsFile = null; - mappingModel.Response.BodyAsFileIsCached = null; - mappingModel.Response.UseTransformer = null; - mappingModel.Response.TransformerType = null; - mappingModel.Response.UseTransformerForBodyAsFile = null; - mappingModel.Response.TransformerReplaceNodeOptions = null; - mappingModel.Response.BodyEncoding = null; - mappingModel.Response.ProxyUrl = response.ProxyAndRecordSettings.Url; - mappingModel.Response.Fault = null; - mappingModel.Response.WebProxy = MapWebProxy(response.ProxyAndRecordSettings.WebProxySettings); - } - else - { - mappingModel.Response.WebProxy = null; - mappingModel.Response.BodyDestination = response.ResponseMessage.BodyDestination; - mappingModel.Response.StatusCode = response.ResponseMessage.StatusCode; - - if (response.ResponseMessage.Headers != null && response.ResponseMessage.Headers.Count > 0) + switch (response.ResponseMessage.BodyData?.DetectedBodyType) { - mappingModel.Response.Headers = MapHeaders(response.ResponseMessage.Headers); - } + case BodyType.String: + mappingModel.Response.Body = response.ResponseMessage.BodyData.BodyAsString; + break; - if (response.UseTransformer) - { - mappingModel.Response.UseTransformer = response.UseTransformer; - mappingModel.Response.TransformerType = response.TransformerType.ToString(); - mappingModel.Response.TransformerReplaceNodeOptions = response.TransformerReplaceNodeOptions.ToString(); - } - - if (response.UseTransformerForBodyAsFile) - { - mappingModel.Response.UseTransformerForBodyAsFile = response.UseTransformerForBodyAsFile; - } - - if (response.ResponseMessage.BodyData != null) - { - switch (response.ResponseMessage.BodyData?.DetectedBodyType) - { - case BodyType.String: - mappingModel.Response.Body = response.ResponseMessage.BodyData.BodyAsString; - break; - - case BodyType.Json: - mappingModel.Response.BodyAsJson = response.ResponseMessage.BodyData.BodyAsJson; - if (response.ResponseMessage.BodyData.BodyAsJsonIndented == true) - { - mappingModel.Response.BodyAsJsonIndented = response.ResponseMessage.BodyData.BodyAsJsonIndented; - } - break; - - case BodyType.Bytes: - mappingModel.Response.BodyAsBytes = response.ResponseMessage.BodyData.BodyAsBytes; - break; - - case BodyType.File: - mappingModel.Response.BodyAsFile = response.ResponseMessage.BodyData.BodyAsFile; - mappingModel.Response.BodyAsFileIsCached = response.ResponseMessage.BodyData.BodyAsFileIsCached; - break; - } - - if (response.ResponseMessage.BodyData.Encoding != null && response.ResponseMessage.BodyData.Encoding.WebName != "utf-8") - { - mappingModel.Response.BodyEncoding = new EncodingModel + case BodyType.Json: + mappingModel.Response.BodyAsJson = response.ResponseMessage.BodyData.BodyAsJson; + if (response.ResponseMessage.BodyData.BodyAsJsonIndented == true) { - EncodingName = response.ResponseMessage.BodyData.Encoding.EncodingName, - CodePage = response.ResponseMessage.BodyData.Encoding.CodePage, - WebName = response.ResponseMessage.BodyData.Encoding.WebName - }; - } + mappingModel.Response.BodyAsJsonIndented = response.ResponseMessage.BodyData.BodyAsJsonIndented; + } + break; + + case BodyType.Bytes: + mappingModel.Response.BodyAsBytes = response.ResponseMessage.BodyData.BodyAsBytes; + break; + + case BodyType.File: + mappingModel.Response.BodyAsFile = response.ResponseMessage.BodyData.BodyAsFile; + mappingModel.Response.BodyAsFileIsCached = response.ResponseMessage.BodyData.BodyAsFileIsCached; + break; } - if (response.ResponseMessage.FaultType != FaultType.NONE) + if (response.ResponseMessage.BodyData.Encoding != null && response.ResponseMessage.BodyData.Encoding.WebName != "utf-8") { - mappingModel.Response.Fault = new FaultModel + mappingModel.Response.BodyEncoding = new EncodingModel { - Type = response.ResponseMessage.FaultType.ToString(), - Percentage = response.ResponseMessage.FaultPercentage + EncodingName = response.ResponseMessage.BodyData.Encoding.EncodingName, + CodePage = response.ResponseMessage.BodyData.Encoding.CodePage, + WebName = response.ResponseMessage.BodyData.Encoding.WebName }; } } - return mappingModel; - } - - private static WebProxyModel MapWebProxy(WebProxySettings settings) - { - return settings != null ? new WebProxyModel + if (response.ResponseMessage.FaultType != FaultType.NONE) { - Address = settings.Address, - UserName = settings.UserName, - Password = settings.Password - } : null; - } - - private static IDictionary MapHeaders(IDictionary> dictionary) - { - var newDictionary = new Dictionary(); - foreach (var entry in dictionary) - { - object value = entry.Value.Count == 1 ? (object)entry.Value.ToString() : entry.Value; - newDictionary.Add(entry.Key, value); + mappingModel.Response.Fault = new FaultModel + { + Type = response.ResponseMessage.FaultType.ToString(), + Percentage = response.ResponseMessage.FaultPercentage + }; } - - return newDictionary; } + + return mappingModel; + } + + private static WebProxyModel? MapWebProxy(WebProxySettings? settings) + { + return settings != null ? new WebProxyModel + { + Address = settings.Address, + UserName = settings.UserName, + Password = settings.Password + } : null; + } + + private static IDictionary MapHeaders(IDictionary> dictionary) + { + var newDictionary = new Dictionary(); + foreach (var entry in dictionary) + { + object value = entry.Value.Count == 1 ? entry.Value.ToString() : entry.Value; + newDictionary.Add(entry.Key, value); + } + + return newDictionary; } } \ No newline at end of file diff --git a/src/WireMock.Net/Serialization/MatcherMapper.cs b/src/WireMock.Net/Serialization/MatcherMapper.cs index 87b860fc..b4df5ea1 100644 --- a/src/WireMock.Net/Serialization/MatcherMapper.cs +++ b/src/WireMock.Net/Serialization/MatcherMapper.cs @@ -9,208 +9,220 @@ using WireMock.Matchers; using WireMock.Models; using WireMock.Plugin; using WireMock.Settings; +using WireMock.Util; -namespace WireMock.Serialization +namespace WireMock.Serialization; + +internal class MatcherMapper { - internal class MatcherMapper + private readonly WireMockServerSettings _settings; + + public MatcherMapper(WireMockServerSettings settings) { - private readonly WireMockServerSettings _settings; + _settings = settings ?? throw new ArgumentNullException(nameof(settings)); + } - public MatcherMapper(WireMockServerSettings settings) + public IMatcher[]? Map(IEnumerable? matchers) + { + if (matchers == null) { - _settings = settings ?? throw new ArgumentNullException(nameof(settings)); + return null; + } + return matchers.Select(Map).Where(m => m != null).ToArray()!; + } + + public IMatcher? Map(MatcherModel? matcher) + { + if (matcher == null) + { + return null; } - public IMatcher[] Map(IEnumerable? matchers) + string[] parts = matcher.Name.Split('.'); + string matcherName = parts[0]; + string? matcherType = parts.Length > 1 ? parts[1] : null; + var stringPatterns = ParseStringPatterns(matcher); + 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; + + switch (matcherName) { - return matchers?.Select(Map).Where(m => m != null).ToArray(); + case nameof(NotNullOrEmptyMatcher): + return new NotNullOrEmptyMatcher(matchBehaviour); + + case "CSharpCodeMatcher": + if (_settings.AllowCSharpCodeMatcher == true) + { + return PluginLoader.Load(matchBehaviour, matchOperator, stringPatterns); + } + + 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); + + case nameof(ExactMatcher): + return new ExactMatcher(matchBehaviour, throwExceptionWhenMatcherFails, matchOperator, stringPatterns); + + case nameof(ExactObjectMatcher): + return CreateExactObjectMatcher(matchBehaviour, stringPatterns[0], throwExceptionWhenMatcherFails); + + case nameof(RegexMatcher): + return new RegexMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails, useRegexExtended, matchOperator); + + case nameof(JsonMatcher): + var valueForJsonMatcher = matcher.Pattern ?? matcher.Patterns; + return new JsonMatcher(matchBehaviour, valueForJsonMatcher!, ignoreCase, throwExceptionWhenMatcherFails); + + case nameof(JsonPartialMatcher): + var valueForJsonPartialMatcher = matcher.Pattern ?? matcher.Patterns; + return new JsonPartialMatcher(matchBehaviour, valueForJsonPartialMatcher!, ignoreCase, throwExceptionWhenMatcherFails); + + case nameof(JsonPartialWildcardMatcher): + var valueForJsonPartialWildcardMatcher = matcher.Pattern ?? matcher.Patterns; + return new JsonPartialWildcardMatcher(matchBehaviour, valueForJsonPartialWildcardMatcher!, ignoreCase, throwExceptionWhenMatcherFails); + + case nameof(JsonPathMatcher): + return new JsonPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, matchOperator, stringPatterns); + + case nameof(JmesPathMatcher): + return new JmesPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, matchOperator, stringPatterns); + + case nameof(XPathMatcher): + return new XPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, matchOperator, stringPatterns); + + case nameof(WildcardMatcher): + return new WildcardMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails, matchOperator); + + case nameof(ContentTypeMatcher): + return new ContentTypeMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails); + + case nameof(SimMetricsMatcher): + SimMetricType type = SimMetricType.Levenstein; + if (!string.IsNullOrEmpty(matcherType) && !Enum.TryParse(matcherType, out type)) + { + throw new NotSupportedException($"Matcher '{matcherName}' with Type '{matcherType}' is not supported."); + } + + return new SimMetricsMatcher(matchBehaviour, stringPatterns, type, throwExceptionWhenMatcherFails); + + default: + if (_settings.CustomMatcherMappings != null && _settings.CustomMatcherMappings.ContainsKey(matcherName)) + { + return _settings.CustomMatcherMappings[matcherName](matcher); + } + + throw new NotSupportedException($"Matcher '{matcherName}' is not supported."); + } + } + + public MatcherModel[]? Map(IEnumerable? matchers) + { + if (matchers == null) + { + return null; } - public IMatcher? Map(MatcherModel? matcher) + return matchers.Where(m => m != null).Select(Map).ToArray()!; + } + + public MatcherModel? Map(IMatcher? matcher) + { + if (matcher == null) { - if (matcher == null) - { - return null; - } - - string[] parts = matcher.Name.Split('.'); - string matcherName = parts[0]; - string? matcherType = parts.Length > 1 ? parts[1] : null; - var stringPatterns = ParseStringPatterns(matcher); - var matchBehaviour = matcher.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch; - bool ignoreCase = matcher.IgnoreCase == true; - bool throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true; - bool useRegexExtended = _settings.UseRegexExtended == true; - - switch (matcherName) - { - case nameof(NotNullOrEmptyMatcher): - return new NotNullOrEmptyMatcher(matchBehaviour); - - case "CSharpCodeMatcher": - if (_settings.AllowCSharpCodeMatcher == true) - { - return PluginLoader.Load(matchBehaviour, stringPatterns); - } - - 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, stringPatterns); - - case nameof(ExactMatcher): - return new ExactMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns); - - case nameof(ExactObjectMatcher): - return CreateExactObjectMatcher(matchBehaviour, stringPatterns[0], throwExceptionWhenMatcherFails); - - case nameof(RegexMatcher): - return new RegexMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails, useRegexExtended); - - case nameof(JsonMatcher): - var valueForJsonMatcher = matcher.Pattern ?? matcher.Patterns; - return new JsonMatcher(matchBehaviour, valueForJsonMatcher!, ignoreCase, throwExceptionWhenMatcherFails); - - case nameof(JsonPartialMatcher): - var valueForJsonPartialMatcher = matcher.Pattern ?? matcher.Patterns; - return new JsonPartialMatcher(matchBehaviour, valueForJsonPartialMatcher!, ignoreCase, throwExceptionWhenMatcherFails); - - case nameof(JsonPartialWildcardMatcher): - var valueForJsonPartialWildcardMatcher = matcher.Pattern ?? matcher.Patterns; - return new JsonPartialWildcardMatcher(matchBehaviour, valueForJsonPartialWildcardMatcher!, ignoreCase, throwExceptionWhenMatcherFails); - - case nameof(JsonPathMatcher): - return new JsonPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns); - - case nameof(JmesPathMatcher): - return new JmesPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns); - - case nameof(XPathMatcher): - return new XPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns); - - case nameof(WildcardMatcher): - return new WildcardMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails); - - case nameof(ContentTypeMatcher): - return new ContentTypeMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails); - - case nameof(SimMetricsMatcher): - SimMetricType type = SimMetricType.Levenstein; - if (!string.IsNullOrEmpty(matcherType) && !Enum.TryParse(matcherType, out type)) - { - throw new NotSupportedException($"Matcher '{matcherName}' with Type '{matcherType}' is not supported."); - } - - return new SimMetricsMatcher(matchBehaviour, stringPatterns, type, throwExceptionWhenMatcherFails); - - default: - if (_settings.CustomMatcherMappings != null && _settings.CustomMatcherMappings.ContainsKey(matcherName)) - { - return _settings.CustomMatcherMappings[matcherName](matcher); - } - - throw new NotSupportedException($"Matcher '{matcherName}' is not supported."); - } + return null; } - public MatcherModel[] Map(IEnumerable? matchers) + bool? ignoreCase = matcher is IIgnoreCaseMatcher ignoreCaseMatcher ? ignoreCaseMatcher.IgnoreCase : null; + bool? rejectOnMatch = matcher.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : null; + + var model = new MatcherModel { - return matchers?.Select(Map).Where(m => m != null).ToArray(); - } + RejectOnMatch = rejectOnMatch, + IgnoreCase = ignoreCase, + Name = matcher.Name + }; - public MatcherModel? Map(IMatcher? matcher) + switch (matcher) { - if (matcher == null) - { - return null; - } - - bool? ignoreCase = matcher is IIgnoreCaseMatcher ignoreCaseMatcher ? ignoreCaseMatcher.IgnoreCase : (bool?)null; - bool? rejectOnMatch = matcher.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : (bool?)null; - - var model = new MatcherModel - { - RejectOnMatch = rejectOnMatch, - IgnoreCase = ignoreCase, - Name = matcher.Name - }; - - switch (matcher) - { - // If the matcher is a IStringMatcher, get the patterns. - case IStringMatcher stringMatcher: - var stringPatterns = stringMatcher.GetPatterns(); - if (stringPatterns.Length == 1) + // If the matcher is a IStringMatcher, get the operator & patterns. + case IStringMatcher stringMatcher: + var stringPatterns = stringMatcher.GetPatterns(); + if (stringPatterns.Length == 1) + { + if (stringPatterns[0].IsFirst) { - if (stringPatterns[0].IsFirst) - { - model.Pattern = stringPatterns[0].First; - } - else - { - model.Pattern = stringPatterns[0].Second.Pattern; - model.PatternAsFile = stringPatterns[0].Second.PatternAsFile; - } + model.Pattern = stringPatterns[0].First; } else { - model.Patterns = stringPatterns.Select(p => p.GetPattern()).Cast().ToArray(); + model.Pattern = stringPatterns[0].Second.Pattern; + model.PatternAsFile = stringPatterns[0].Second.PatternAsFile; } - break; + } + else + { + model.Patterns = stringPatterns.Select(p => p.GetPattern()).Cast().ToArray(); + model.MatchOperator = stringMatcher.MatchOperator.ToString(); + } + break; - // If the matcher is a IValueMatcher, get the value (can be string or object). - case IValueMatcher valueMatcher: - model.Pattern = valueMatcher.Value; - break; + // If the matcher is a IValueMatcher, get the value (can be string or object). + case IValueMatcher valueMatcher: + model.Pattern = valueMatcher.Value; + break; - // If the matcher is a ExactObjectMatcher, get the ValueAsObject or ValueAsBytes. - case ExactObjectMatcher exactObjectMatcher: - model.Pattern = exactObjectMatcher.ValueAsObject ?? exactObjectMatcher.ValueAsBytes; - break; - } - - return model; + // If the matcher is a ExactObjectMatcher, get the ValueAsObject or ValueAsBytes. + case ExactObjectMatcher exactObjectMatcher: + model.Pattern = exactObjectMatcher.ValueAsObject ?? exactObjectMatcher.ValueAsBytes; + break; } - private AnyOf[] ParseStringPatterns(MatcherModel matcher) + return model; + } + + private AnyOf[] ParseStringPatterns(MatcherModel matcher) + { + if (matcher.Pattern is string patternAsString) { - if (matcher.Pattern is string patternAsString) - { - return new[] { new AnyOf(patternAsString) }; - } - - if (matcher.Pattern is IEnumerable patternAsStringArray) - { - return patternAsStringArray.ToAnyOfPatterns(); - } - - if (matcher.Patterns?.OfType() is IEnumerable patternsAsStringArray) - { - return patternsAsStringArray.ToAnyOfPatterns(); - } - - if (!string.IsNullOrEmpty(matcher.PatternAsFile)) - { - var pattern = _settings.FileSystemHandler.ReadFileAsString(matcher.PatternAsFile); - return new[] { new AnyOf(new StringPattern { Pattern = pattern, PatternAsFile = matcher.PatternAsFile }) }; - } - - return new AnyOf[0]; + return new[] { new AnyOf(patternAsString) }; } - private ExactObjectMatcher CreateExactObjectMatcher(MatchBehaviour matchBehaviour, AnyOf stringPattern, bool throwException) + if (matcher.Pattern is IEnumerable patternAsStringArray) { - byte[] bytePattern; - try - { - bytePattern = Convert.FromBase64String(stringPattern.GetPattern()); - } - catch - { - 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 patternAsStringArray.ToAnyOfPatterns(); } + + if (matcher.Patterns?.OfType() is IEnumerable patternsAsStringArray) + { + return patternsAsStringArray.ToAnyOfPatterns(); + } + + if (!string.IsNullOrEmpty(matcher.PatternAsFile)) + { + var patternAsFile = matcher.PatternAsFile!; + var pattern = _settings.FileSystemHandler.ReadFileAsString(patternAsFile); + return new[] { new AnyOf(new StringPattern { Pattern = pattern, PatternAsFile = patternAsFile }) }; + } + + return new AnyOf[0]; + } + + private static ExactObjectMatcher CreateExactObjectMatcher(MatchBehaviour matchBehaviour, AnyOf stringPattern, bool throwException) + { + byte[] bytePattern; + try + { + bytePattern = Convert.FromBase64String(stringPattern.GetPattern()); + } + catch + { + throw new ArgumentException($"Matcher 'ExactObjectMatcher' has invalid pattern. The pattern value '{stringPattern}' is not a Base64String.", nameof(stringPattern)); + } + + return new ExactObjectMatcher(matchBehaviour, bytePattern, throwException); } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/RespondWithAProvider.cs b/src/WireMock.Net/Server/RespondWithAProvider.cs index 4240e49e..508d8478 100644 --- a/src/WireMock.Net/Server/RespondWithAProvider.cs +++ b/src/WireMock.Net/Server/RespondWithAProvider.cs @@ -2,251 +2,254 @@ // For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. using System; using System.Collections.Generic; -using JetBrains.Annotations; +using Stef.Validation; using WireMock.Matchers.Request; using WireMock.Models; using WireMock.ResponseProviders; using WireMock.Settings; using WireMock.Types; using WireMock.Util; -using Stef.Validation; -using WireMock.Constants; -namespace WireMock.Server +namespace WireMock.Server; + +/// +/// The respond with a provider. +/// +internal class RespondWithAProvider : IRespondWithAProvider { + private int _priority; + private string? _title; + private string? _description; + private string? _path; + private string? _executionConditionState; + private string? _nextState; + private string? _scenario; + private int _timesInSameState = 1; + private readonly RegistrationCallback _registrationCallback; + private readonly IRequestMatcher _requestMatcher; + private readonly WireMockServerSettings _settings; + private readonly bool _saveToFile; + + public Guid Guid { get; private set; } = Guid.NewGuid(); + + public IWebhook[]? Webhooks { get; private set; } + + public ITimeSettings? TimeSettings { get; private set; } + /// - /// The respond with a provider. + /// Initializes a new instance of the class. /// - internal class RespondWithAProvider : IRespondWithAProvider + /// The registration callback. + /// The request matcher. + /// The WireMockServerSettings. + /// Optional boolean to indicate if this mapping should be saved as static mapping file. + public RespondWithAProvider(RegistrationCallback registrationCallback, IRequestMatcher requestMatcher, WireMockServerSettings settings, bool saveToFile = false) { - private int _priority; - private string _title; - private string _description; - private string _path; - private string _executionConditionState; - private string _nextState; - private string _scenario; - private int _timesInSameState = 1; - private readonly RegistrationCallback _registrationCallback; - private readonly IRequestMatcher _requestMatcher; - private readonly WireMockServerSettings _settings; - private readonly bool _saveToFile; + _registrationCallback = registrationCallback; + _requestMatcher = requestMatcher; + _settings = settings; + _saveToFile = saveToFile; + } - public Guid Guid { get; private set; } = Guid.NewGuid(); + /// + /// The respond with. + /// + /// The provider. + public void RespondWith(IResponseProvider provider) + { + _registrationCallback(new Mapping(Guid, _title, _description, _path, _settings, _requestMatcher, provider, _priority, _scenario, _executionConditionState, _nextState, _timesInSameState, Webhooks, TimeSettings), _saveToFile); + } - public IWebhook[] Webhooks { get; private set; } + /// + public IRespondWithAProvider WithGuid(string guid) + { + return WithGuid(Guid.Parse(guid)); + } - public ITimeSettings TimeSettings { get; private set; } + /// + public IRespondWithAProvider WithGuid(Guid guid) + { + Guid = guid; - /// - /// Initializes a new instance of the class. - /// - /// The registration callback. - /// The request matcher. - /// The WireMockServerSettings. - /// Optional boolean to indicate if this mapping should be saved as static mapping file. - public RespondWithAProvider(RegistrationCallback registrationCallback, IRequestMatcher requestMatcher, WireMockServerSettings settings, bool saveToFile = false) + return this; + } + + /// + public IRespondWithAProvider WithTitle(string title) + { + _title = title; + + return this; + } + + /// + public IRespondWithAProvider WithDescription(string description) + { + _description = description; + + return this; + } + + /// + public IRespondWithAProvider WithPath(string path) + { + _path = path; + + return this; + } + + /// + public IRespondWithAProvider AtPriority(int priority) + { + _priority = priority; + + return this; + } + + /// + public IRespondWithAProvider InScenario(string scenario) + { + _scenario = scenario; + + return this; + } + + /// + public IRespondWithAProvider InScenario(int scenario) + { + return InScenario(scenario.ToString()); + } + + /// + public IRespondWithAProvider WhenStateIs(string state) + { + if (string.IsNullOrEmpty(_scenario)) { - _registrationCallback = registrationCallback; - _requestMatcher = requestMatcher; - _settings = settings; - _saveToFile = saveToFile; + throw new NotSupportedException("Unable to set state condition when no scenario is defined."); } - /// - /// The respond with. - /// - /// The provider. - public void RespondWith(IResponseProvider provider) + _executionConditionState = state; + + return this; + } + + /// + public IRespondWithAProvider WhenStateIs(int state) + { + return WhenStateIs(state.ToString()); + } + + /// + public IRespondWithAProvider WillSetStateTo(string state, int? times = 1) + { + if (string.IsNullOrEmpty(_scenario)) { - _registrationCallback(new Mapping(Guid, _title, _description, _path, _settings, _requestMatcher, provider, _priority, _scenario, _executionConditionState, _nextState, _timesInSameState, Webhooks, TimeSettings), _saveToFile); + throw new NotSupportedException("Unable to set next state when no scenario is defined."); } - /// - public IRespondWithAProvider WithGuid(string guid) + _nextState = state; + _timesInSameState = times ?? 1; + + return this; + } + + /// + public IRespondWithAProvider WillSetStateTo(int state, int? times = 1) + { + return WillSetStateTo(state.ToString(), times); + } + + /// + public IRespondWithAProvider WithTimeSettings(ITimeSettings timeSettings) + { + Guard.NotNull(timeSettings, nameof(timeSettings)); + + TimeSettings = timeSettings; + + return this; + } + + /// + public IRespondWithAProvider WithWebhook(params IWebhook[] webhooks) + { + Guard.HasNoNulls(webhooks, nameof(webhooks)); + + Webhooks = webhooks; + + return this; + } + + /// + public IRespondWithAProvider WithWebhook( + string url, + string method = "post", + IDictionary>? headers = null, + string? body = null, + bool useTransformer = true, + TransformerType transformerType = TransformerType.Handlebars) + { + Guard.NotNull(url); + Guard.NotNull(method); + + Webhooks = new[] { InitWebhook(url, method, headers, useTransformer, transformerType) }; + + if (body != null) { - return WithGuid(Guid.Parse(guid)); - } - - /// - public IRespondWithAProvider WithGuid(Guid guid) - { - Guid = guid; - - return this; - } - - /// - public IRespondWithAProvider WithTitle(string title) - { - _title = title; - - return this; - } - - /// - public IRespondWithAProvider WithDescription(string description) - { - _description = description; - - return this; - } - - /// - public IRespondWithAProvider WithPath(string path) - { - _path = path; - - return this; - } - - /// - public IRespondWithAProvider AtPriority(int priority) - { - _priority = priority; - - return this; - } - - /// - public IRespondWithAProvider InScenario(string scenario) - { - _scenario = scenario; - - return this; - } - - /// - public IRespondWithAProvider InScenario(int scenario) - { - return InScenario(scenario.ToString()); - } - - /// - public IRespondWithAProvider WhenStateIs(string state) - { - if (string.IsNullOrEmpty(_scenario)) + Webhooks[0].Request.BodyData = new BodyData { - throw new NotSupportedException("Unable to set state condition when no scenario is defined."); - } - - _executionConditionState = state; - - return this; - } - - /// - public IRespondWithAProvider WhenStateIs(int state) - { - return WhenStateIs(state.ToString()); - } - - /// - public IRespondWithAProvider WillSetStateTo(string state, int? times = 1) - { - if (string.IsNullOrEmpty(_scenario)) - { - throw new NotSupportedException("Unable to set next state when no scenario is defined."); - } - - _nextState = state; - _timesInSameState = times ?? 1; - - return this; - } - - /// - public IRespondWithAProvider WillSetStateTo(int state, int? times = 1) - { - return WillSetStateTo(state.ToString(), times); - } - - /// - public IRespondWithAProvider WithTimeSettings(ITimeSettings timeSettings) - { - Guard.NotNull(timeSettings, nameof(timeSettings)); - - TimeSettings = timeSettings; - - return this; - } - - /// - public IRespondWithAProvider WithWebhook(params IWebhook[] webhooks) - { - Guard.HasNoNulls(webhooks, nameof(webhooks)); - - Webhooks = webhooks; - - return this; - } - - /// - public IRespondWithAProvider WithWebhook( - [NotNull] string url, - [CanBeNull] string method = "post", - [CanBeNull] IDictionary> headers = null, - [CanBeNull] string body = null, - bool useTransformer = true, - TransformerType transformerType = TransformerType.Handlebars) - { - Webhooks = new[] { InitWebhook(url, method, headers, useTransformer, transformerType) }; - - if (body != null) - { - Webhooks[0].Request.BodyData = new BodyData - { - BodyAsString = body, - DetectedBodyType = BodyType.String, - DetectedBodyTypeFromContentType = BodyType.String - }; - } - - return this; - } - - /// - public IRespondWithAProvider WithWebhook( - [NotNull] string url, - [CanBeNull] string method = "post", - [CanBeNull] IDictionary> headers = null, - [CanBeNull] object body = null, - bool useTransformer = true, - TransformerType transformerType = TransformerType.Handlebars) - { - Webhooks = new[] { InitWebhook(url, method, headers, useTransformer, transformerType) }; - - if (body != null) - { - Webhooks[0].Request.BodyData = new BodyData - { - BodyAsJson = body, - DetectedBodyType = BodyType.Json, - DetectedBodyTypeFromContentType = BodyType.Json - }; - } - - return this; - } - - private static IWebhook InitWebhook( - string url, - string method, - IDictionary> headers, - bool useTransformer, - TransformerType transformerType) - { - return new Webhook - { - Request = new WebhookRequest - { - Url = url, - Method = method ?? "post", - Headers = headers, - UseTransformer = useTransformer, - TransformerType = transformerType - } + BodyAsString = body, + DetectedBodyType = BodyType.String, + DetectedBodyTypeFromContentType = BodyType.String }; } + + return this; + } + + /// + public IRespondWithAProvider WithWebhook( + string url, + string method = "post", + IDictionary>? headers = null, + object? body = null, + bool useTransformer = true, + TransformerType transformerType = TransformerType.Handlebars) + { + Guard.NotNull(url); + Guard.NotNull(method); + + Webhooks = new[] { InitWebhook(url, method, headers, useTransformer, transformerType) }; + + if (body != null) + { + Webhooks[0].Request.BodyData = new BodyData + { + BodyAsJson = body, + DetectedBodyType = BodyType.Json, + DetectedBodyTypeFromContentType = BodyType.Json + }; + } + + return this; + } + + private static IWebhook InitWebhook( + string url, + string method, + IDictionary>? headers, + bool useTransformer, + TransformerType transformerType) + { + return new Webhook + { + Request = new WebhookRequest + { + Url = url, + Method = method, + Headers = headers, + UseTransformer = useTransformer, + TransformerType = transformerType + } + }; } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs index 89884584..3dc02fce 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs @@ -234,14 +234,14 @@ public partial class WireMockServer private async Task ProxyAndRecordAsync(IRequestMessage requestMessage, WireMockServerSettings settings) { var requestUri = new Uri(requestMessage.Url); - var proxyUri = new Uri(settings.ProxyAndRecordSettings.Url); + var proxyUri = new Uri(settings.ProxyAndRecordSettings!.Url); var proxyUriWithRequestPathAndQuery = new Uri(proxyUri, requestUri.PathAndQuery); var proxyHelper = new ProxyHelper(settings); var (responseMessage, mapping) = await proxyHelper.SendAsync( - _settings.ProxyAndRecordSettings, - _httpClientForProxy, + _settings.ProxyAndRecordSettings!, + _httpClientForProxy!, requestMessage, proxyUriWithRequestPathAndQuery.AbsoluteUri ).ConfigureAwait(false); @@ -442,73 +442,6 @@ public partial class WireMockServer } } - private Guid? ConvertMappingAndRegisterAsRespondProvider(MappingModel mappingModel, Guid? guid = null, string? path = null) - { - Guard.NotNull(mappingModel, nameof(mappingModel)); - Guard.NotNull(mappingModel.Request, nameof(mappingModel.Request)); - Guard.NotNull(mappingModel.Response, nameof(mappingModel.Response)); - - var requestBuilder = InitRequestBuilder(mappingModel.Request, true); - if (requestBuilder == null) - { - return null; - } - - var responseBuilder = InitResponseBuilder(mappingModel.Response); - - var respondProvider = Given(requestBuilder, mappingModel.SaveToFile == true); - - if (guid != null) - { - respondProvider = respondProvider.WithGuid(guid.Value); - } - else if (mappingModel.Guid != null && mappingModel.Guid != Guid.Empty) - { - respondProvider = respondProvider.WithGuid(mappingModel.Guid.Value); - } - - if (mappingModel.TimeSettings != null) - { - respondProvider = respondProvider.WithTimeSettings(TimeSettingsMapper.Map(mappingModel.TimeSettings)); - } - - if (path != null) - { - respondProvider = respondProvider.WithPath(path); - } - - if (!string.IsNullOrEmpty(mappingModel.Title)) - { - respondProvider = respondProvider.WithTitle(mappingModel.Title); - } - - if (mappingModel.Priority != null) - { - respondProvider = respondProvider.AtPriority(mappingModel.Priority.Value); - } - - if (mappingModel.Scenario != null) - { - respondProvider = respondProvider.InScenario(mappingModel.Scenario); - respondProvider = respondProvider.WhenStateIs(mappingModel.WhenStateIs); - respondProvider = respondProvider.WillSetStateTo(mappingModel.SetStateTo); - } - - if (mappingModel.Webhook != null) - { - respondProvider = respondProvider.WithWebhook(WebhookMapper.Map(mappingModel.Webhook)); - } - else if (mappingModel.Webhooks?.Length > 1) - { - var webhooks = mappingModel.Webhooks.Select(WebhookMapper.Map).ToArray(); - respondProvider = respondProvider.WithWebhook(webhooks); - } - - respondProvider.RespondWith(responseBuilder); - - return respondProvider.Guid; - } - private IResponseMessage MappingsDelete(IRequestMessage requestMessage) { if (!string.IsNullOrEmpty(requestMessage.Body)) @@ -518,23 +451,19 @@ public partial class WireMockServer { return ResponseMessageBuilder.Create($"Mappings deleted. Affected GUIDs: [{string.Join(", ", deletedGuids.ToArray())}]"); } - else - { - // return bad request - return ResponseMessageBuilder.Create("Poorly formed mapping JSON.", 400); - } - } - else - { - ResetMappings(); - ResetScenarios(); - - return ResponseMessageBuilder.Create("Mappings deleted"); + // return bad request + return ResponseMessageBuilder.Create("Poorly formed mapping JSON.", 400); } + + ResetMappings(); + + ResetScenarios(); + + return ResponseMessageBuilder.Create("Mappings deleted"); } - private IEnumerable MappingsDeleteMappingFromBody(IRequestMessage requestMessage) + private IEnumerable? MappingsDeleteMappingFromBody(IRequestMessage requestMessage) { var deletedGuids = new List(); @@ -577,9 +506,10 @@ public partial class WireMockServer ResetScenarios(); string message = "Mappings reset"; - if (requestMessage.Query.ContainsKey(QueryParamReloadStaticMappings) && - bool.TryParse(requestMessage.Query[QueryParamReloadStaticMappings].ToString(), out bool reloadStaticMappings) - && reloadStaticMappings) + if (requestMessage.Query != null && + requestMessage.Query.ContainsKey(QueryParamReloadStaticMappings) && + bool.TryParse(requestMessage.Query[QueryParamReloadStaticMappings].ToString(), out bool reloadStaticMappings) && + reloadStaticMappings) { ReadStaticMappings(); message = $"{message} and static mappings reloaded"; @@ -718,220 +648,6 @@ public partial class WireMockServer return this; } #endregion - private IRequestBuilder? InitRequestBuilder(RequestModel requestModel, bool pathOrUrlRequired) - { - IRequestBuilder requestBuilder = Request.Create(); - - if (requestModel.ClientIP != null) - { - if (requestModel.ClientIP is string clientIP) - { - requestBuilder = requestBuilder.WithClientIP(clientIP); - } - else - { - var clientIPModel = JsonUtils.ParseJTokenToObject(requestModel.ClientIP); - if (clientIPModel?.Matchers != null) - { - requestBuilder = requestBuilder.WithPath(clientIPModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); - } - } - } - - bool pathOrUrlMatchersValid = false; - if (requestModel.Path != null) - { - if (requestModel.Path is string path) - { - requestBuilder = requestBuilder.WithPath(path); - pathOrUrlMatchersValid = true; - } - else - { - var pathModel = JsonUtils.ParseJTokenToObject(requestModel.Path); - if (pathModel?.Matchers != null) - { - requestBuilder = requestBuilder.WithPath(pathModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); - pathOrUrlMatchersValid = true; - } - } - } - else if (requestModel.Url != null) - { - if (requestModel.Url is string url) - { - requestBuilder = requestBuilder.WithUrl(url); - pathOrUrlMatchersValid = true; - } - else - { - var urlModel = JsonUtils.ParseJTokenToObject(requestModel.Url); - if (urlModel?.Matchers != null) - { - requestBuilder = requestBuilder.WithUrl(urlModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); - pathOrUrlMatchersValid = true; - } - } - } - - if (pathOrUrlRequired && !pathOrUrlMatchersValid) - { - _settings.Logger.Error("Path or Url matcher is missing for this mapping, this mapping will not be added."); - return null; - } - - if (requestModel.Methods != null) - { - requestBuilder = requestBuilder.UsingMethod(requestModel.Methods); - } - - if (requestModel.Headers != null) - { - foreach (var headerModel in requestModel.Headers.Where(h => h.Matchers != null)) - { - requestBuilder = requestBuilder.WithHeader( - headerModel.Name, - headerModel.IgnoreCase == true, - headerModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, - headerModel.Matchers!.Select(_matcherMapper.Map).OfType().ToArray() - ); - } - } - - if (requestModel.Cookies != null) - { - foreach (var cookieModel in requestModel.Cookies.Where(c => c.Matchers != null)) - { - requestBuilder = requestBuilder.WithCookie( - cookieModel.Name, - cookieModel.IgnoreCase == true, - cookieModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, - cookieModel.Matchers!.Select(_matcherMapper.Map).OfType().ToArray()); - } - } - - if (requestModel.Params != null) - { - foreach (var paramModel in requestModel.Params.Where(p => p is { Matchers: { } })) - { - bool ignoreCase = paramModel.IgnoreCase == true; - requestBuilder = requestBuilder.WithParam(paramModel.Name, ignoreCase, paramModel.Matchers!.Select(_matcherMapper.Map).OfType().ToArray()); - } - } - - if (requestModel.Body?.Matcher != null) - { - requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matcher)); - } - else if (requestModel.Body?.Matchers != null) - { - requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matchers)); - } - - return requestBuilder; - } - - private IResponseBuilder InitResponseBuilder(ResponseModel responseModel) - { - IResponseBuilder responseBuilder = Response.Create(); - - if (responseModel.Delay > 0) - { - responseBuilder = responseBuilder.WithDelay(responseModel.Delay.Value); - } - else if (responseModel.MinimumRandomDelay >= 0 || responseModel.MaximumRandomDelay > 0) - { - responseBuilder = responseBuilder.WithRandomDelay(responseModel.MinimumRandomDelay ?? 0, responseModel.MaximumRandomDelay ?? 60_000); - } - - if (responseModel.UseTransformer == true) - { - if (!Enum.TryParse(responseModel.TransformerType, out var transformerType)) - { - transformerType = TransformerType.Handlebars; - } - - if (!Enum.TryParse(responseModel.TransformerReplaceNodeOptions, out var option)) - { - option = ReplaceNodeOptions.None; - } - responseBuilder = responseBuilder.WithTransformer( - transformerType, - responseModel.UseTransformerForBodyAsFile == true, - option); - } - - if (!string.IsNullOrEmpty(responseModel.ProxyUrl)) - { - var proxyAndRecordSettings = new ProxyAndRecordSettings - { - Url = responseModel.ProxyUrl, - ClientX509Certificate2ThumbprintOrSubjectName = responseModel.X509Certificate2ThumbprintOrSubjectName, - WebProxySettings = responseModel.WebProxy != null ? new WebProxySettings - { - Address = responseModel.WebProxy.Address, - UserName = responseModel.WebProxy.UserName, - Password = responseModel.WebProxy.Password - } : null - }; - - return responseBuilder.WithProxy(proxyAndRecordSettings); - } - - if (responseModel.StatusCode is string statusCodeAsString) - { - responseBuilder = responseBuilder.WithStatusCode(statusCodeAsString); - } - else if (responseModel.StatusCode != null) - { - // Convert to Int32 because Newtonsoft deserializes an 'object' with a number value to a long. - responseBuilder = responseBuilder.WithStatusCode(Convert.ToInt32(responseModel.StatusCode)); - } - - if (responseModel.Headers != null) - { - foreach (var entry in responseModel.Headers) - { - responseBuilder = entry.Value is string value ? - responseBuilder.WithHeader(entry.Key, value) : - responseBuilder.WithHeader(entry.Key, JsonUtils.ParseJTokenToObject(entry.Value)); - } - } - else if (responseModel.HeadersRaw != null) - { - foreach (string headerLine in responseModel.HeadersRaw.Split(new[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries)) - { - int indexColon = headerLine.IndexOf(":", StringComparison.Ordinal); - string key = headerLine.Substring(0, indexColon).TrimStart(' ', '\t'); - string value = headerLine.Substring(indexColon + 1).TrimStart(' ', '\t'); - responseBuilder = responseBuilder.WithHeader(key, value); - } - } - - if (responseModel.BodyAsBytes != null) - { - responseBuilder = responseBuilder.WithBody(responseModel.BodyAsBytes, responseModel.BodyDestination, ToEncoding(responseModel.BodyEncoding)); - } - else if (responseModel.Body != null) - { - responseBuilder = responseBuilder.WithBody(responseModel.Body, responseModel.BodyDestination, ToEncoding(responseModel.BodyEncoding)); - } - else if (responseModel.BodyAsJson != null) - { - responseBuilder = responseBuilder.WithBodyAsJson(responseModel.BodyAsJson, ToEncoding(responseModel.BodyEncoding), responseModel.BodyAsJsonIndented == true); - } - else if (responseModel.BodyAsFile != null) - { - responseBuilder = responseBuilder.WithBodyFromFile(responseModel.BodyAsFile, responseModel.BodyAsFileIsCached == true); - } - - if (responseModel.Fault != null && Enum.TryParse(responseModel.Fault.Type, out FaultType faultType)) - { - responseBuilder.WithFault(faultType, responseModel.Fault.Percentage); - } - - return responseBuilder; - } private void DisposeEnhancedFileSystemWatcher() { diff --git a/src/WireMock.Net/Server/WireMockServer.ConvertMapping.cs b/src/WireMock.Net/Server/WireMockServer.ConvertMapping.cs new file mode 100644 index 00000000..20bf285e --- /dev/null +++ b/src/WireMock.Net/Server/WireMockServer.ConvertMapping.cs @@ -0,0 +1,318 @@ +using System; +using System.Linq; +using Stef.Validation; +using WireMock.Admin.Mappings; +using WireMock.Matchers; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; +using WireMock.Serialization; +using WireMock.Settings; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock.Server; + +public partial class WireMockServer +{ + private Guid? ConvertMappingAndRegisterAsRespondProvider(MappingModel mappingModel, Guid? guid = null, string? path = null) + { + Guard.NotNull(mappingModel); + Guard.NotNull(mappingModel.Request, nameof(mappingModel.Request)); + Guard.NotNull(mappingModel.Response, nameof(mappingModel.Response)); + + var requestBuilder = InitRequestBuilder(mappingModel.Request, true); + if (requestBuilder == null) + { + return null; + } + + var respondProvider = Given(requestBuilder, mappingModel.SaveToFile == true); + + if (guid != null) + { + respondProvider = respondProvider.WithGuid(guid.Value); + } + else if (mappingModel.Guid != null && mappingModel.Guid != Guid.Empty) + { + respondProvider = respondProvider.WithGuid(mappingModel.Guid.Value); + } + + if (mappingModel.TimeSettings != null) + { + respondProvider = respondProvider.WithTimeSettings(TimeSettingsMapper.Map(mappingModel.TimeSettings)); + } + + if (path != null) + { + respondProvider = respondProvider.WithPath(path); + } + + if (!string.IsNullOrEmpty(mappingModel.Title)) + { + respondProvider = respondProvider.WithTitle(mappingModel.Title!); + } + + if (mappingModel.Priority != null) + { + respondProvider = respondProvider.AtPriority(mappingModel.Priority.Value); + } + + if (mappingModel.Scenario != null) + { + respondProvider = respondProvider.InScenario(mappingModel.Scenario); + + if (!string.IsNullOrEmpty(mappingModel.WhenStateIs)) + { + respondProvider = respondProvider.WhenStateIs(mappingModel.WhenStateIs!); + } + + if (!string.IsNullOrEmpty(mappingModel.SetStateTo)) + { + respondProvider = respondProvider.WillSetStateTo(mappingModel.SetStateTo!); + } + } + + if (mappingModel.Webhook != null) + { + respondProvider = respondProvider.WithWebhook(WebhookMapper.Map(mappingModel.Webhook)); + } + else if (mappingModel.Webhooks?.Length > 1) + { + var webhooks = mappingModel.Webhooks.Select(WebhookMapper.Map).ToArray(); + respondProvider = respondProvider.WithWebhook(webhooks); + } + + var responseBuilder = InitResponseBuilder(mappingModel.Response); + respondProvider.RespondWith(responseBuilder); + + return respondProvider.Guid; + } + + private IRequestBuilder? InitRequestBuilder(RequestModel requestModel, bool pathOrUrlRequired) + { + IRequestBuilder requestBuilder = Request.Create(); + + if (requestModel.ClientIP != null) + { + if (requestModel.ClientIP is string clientIP) + { + requestBuilder = requestBuilder.WithClientIP(clientIP); + } + else + { + var clientIPModel = JsonUtils.ParseJTokenToObject(requestModel.ClientIP); + if (clientIPModel?.Matchers != null) + { + requestBuilder = requestBuilder.WithPath(clientIPModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); + } + } + } + + bool pathOrUrlMatchersValid = false; + if (requestModel.Path != null) + { + if (requestModel.Path is string path) + { + requestBuilder = requestBuilder.WithPath(path); + pathOrUrlMatchersValid = true; + } + else + { + var pathModel = JsonUtils.ParseJTokenToObject(requestModel.Path); + if (pathModel?.Matchers != null) + { + var matchOperator = StringUtils.ParseMatchOperator(pathModel.MatchOperator); + requestBuilder = requestBuilder.WithPath(matchOperator, pathModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); + pathOrUrlMatchersValid = true; + } + } + } + else if (requestModel.Url != null) + { + if (requestModel.Url is string url) + { + requestBuilder = requestBuilder.WithUrl(url); + pathOrUrlMatchersValid = true; + } + else + { + var urlModel = JsonUtils.ParseJTokenToObject(requestModel.Url); + if (urlModel?.Matchers != null) + { + var matchOperator = StringUtils.ParseMatchOperator(urlModel.MatchOperator); + requestBuilder = requestBuilder.WithUrl(matchOperator, urlModel.Matchers.Select(_matcherMapper.Map).OfType().ToArray()); + pathOrUrlMatchersValid = true; + } + } + } + + if (pathOrUrlRequired && !pathOrUrlMatchersValid) + { + _settings.Logger.Error("Path or Url matcher is missing for this mapping, this mapping will not be added."); + return null; + } + + if (requestModel.Methods != null) + { + var matchBehaviour = requestModel.MethodsRejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch; + var matchOperator = StringUtils.ParseMatchOperator(requestModel.MethodsMatchOperator); + requestBuilder = requestBuilder.UsingMethod(matchBehaviour, matchOperator, requestModel.Methods); + } + + if (requestModel.Headers != null) + { + foreach (var headerModel in requestModel.Headers.Where(h => h.Matchers != null)) + { + var matchOperator = StringUtils.ParseMatchOperator(headerModel.MatchOperator); + requestBuilder = requestBuilder.WithHeader( + headerModel.Name, + headerModel.IgnoreCase == true, + headerModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, + matchOperator, + headerModel.Matchers!.Select(_matcherMapper.Map).OfType().ToArray() + ); + } + } + + if (requestModel.Cookies != null) + { + foreach (var cookieModel in requestModel.Cookies.Where(c => c.Matchers != null)) + { + requestBuilder = requestBuilder.WithCookie( + cookieModel.Name, + cookieModel.IgnoreCase == true, + cookieModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, + cookieModel.Matchers!.Select(_matcherMapper.Map).OfType().ToArray()); + } + } + + if (requestModel.Params != null) + { + foreach (var paramModel in requestModel.Params.Where(p => p is { Matchers: { } })) + { + bool ignoreCase = paramModel.IgnoreCase == true; + requestBuilder = requestBuilder.WithParam(paramModel.Name, ignoreCase, paramModel.Matchers!.Select(_matcherMapper.Map).OfType().ToArray()); + } + } + + if (requestModel.Body?.Matcher != null) + { + requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matcher)!); + } + else if (requestModel.Body?.Matchers != null) + { + var matchOperator = StringUtils.ParseMatchOperator(requestModel.Body.MatchOperator); + requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matchers)!, matchOperator); + } + + return requestBuilder; + } + + private static IResponseBuilder InitResponseBuilder(ResponseModel responseModel) + { + IResponseBuilder responseBuilder = Response.Create(); + + if (responseModel.Delay > 0) + { + responseBuilder = responseBuilder.WithDelay(responseModel.Delay.Value); + } + else if (responseModel.MinimumRandomDelay >= 0 || responseModel.MaximumRandomDelay > 0) + { + responseBuilder = responseBuilder.WithRandomDelay(responseModel.MinimumRandomDelay ?? 0, responseModel.MaximumRandomDelay ?? 60_000); + } + + if (responseModel.UseTransformer == true) + { + if (!Enum.TryParse(responseModel.TransformerType, out var transformerType)) + { + transformerType = TransformerType.Handlebars; + } + + if (!Enum.TryParse(responseModel.TransformerReplaceNodeOptions, out var option)) + { + option = ReplaceNodeOptions.None; + } + responseBuilder = responseBuilder.WithTransformer( + transformerType, + responseModel.UseTransformerForBodyAsFile == true, + option); + } + + if (!string.IsNullOrEmpty(responseModel.ProxyUrl)) + { + var proxyAndRecordSettings = new ProxyAndRecordSettings + { + Url = responseModel.ProxyUrl!, + ClientX509Certificate2ThumbprintOrSubjectName = responseModel.X509Certificate2ThumbprintOrSubjectName, + WebProxySettings = responseModel.WebProxy != null ? new WebProxySettings + { + Address = responseModel.WebProxy.Address, + UserName = responseModel.WebProxy.UserName, + Password = responseModel.WebProxy.Password + } : null + }; + + return responseBuilder.WithProxy(proxyAndRecordSettings); + } + + if (responseModel.StatusCode is string statusCodeAsString) + { + responseBuilder = responseBuilder.WithStatusCode(statusCodeAsString); + } + else if (responseModel.StatusCode != null) + { + // Convert to Int32 because Newtonsoft deserializes an 'object' with a number value to a long. + responseBuilder = responseBuilder.WithStatusCode(Convert.ToInt32(responseModel.StatusCode)); + } + + if (responseModel.Headers != null) + { + foreach (var entry in responseModel.Headers) + { + if (entry.Value is string value) + { + responseBuilder.WithHeader(entry.Key, value); + } + else + { + var headers = JsonUtils.ParseJTokenToObject(entry.Value) ?? new string[0]; + responseBuilder.WithHeader(entry.Key, headers); + } + } + } + else if (responseModel.HeadersRaw != null) + { + foreach (string headerLine in responseModel.HeadersRaw.Split(new[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries)) + { + int indexColon = headerLine.IndexOf(":", StringComparison.Ordinal); + string key = headerLine.Substring(0, indexColon).TrimStart(' ', '\t'); + string value = headerLine.Substring(indexColon + 1).TrimStart(' ', '\t'); + responseBuilder = responseBuilder.WithHeader(key, value); + } + } + + if (responseModel.BodyAsBytes != null) + { + responseBuilder = responseBuilder.WithBody(responseModel.BodyAsBytes, responseModel.BodyDestination, ToEncoding(responseModel.BodyEncoding)); + } + else if (responseModel.Body != null) + { + responseBuilder = responseBuilder.WithBody(responseModel.Body, responseModel.BodyDestination, ToEncoding(responseModel.BodyEncoding)); + } + else if (responseModel.BodyAsJson != null) + { + responseBuilder = responseBuilder.WithBodyAsJson(responseModel.BodyAsJson, ToEncoding(responseModel.BodyEncoding), responseModel.BodyAsJsonIndented == true); + } + else if (responseModel.BodyAsFile != null) + { + responseBuilder = responseBuilder.WithBodyFromFile(responseModel.BodyAsFile, responseModel.BodyAsFileIsCached == true); + } + + if (responseModel.Fault != null && Enum.TryParse(responseModel.Fault.Type, out FaultType faultType)) + { + responseBuilder.WithFault(faultType, responseModel.Fault.Percentage); + } + + return responseBuilder; + } +} \ No newline at end of file diff --git a/src/WireMock.Net/Server/WireMockServer.cs b/src/WireMock.Net/Server/WireMockServer.cs index cb8b5950..9cf9d64c 100644 --- a/src/WireMock.Net/Server/WireMockServer.cs +++ b/src/WireMock.Net/Server/WireMockServer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Net.Http; using System.Threading; using JetBrains.Annotations; using Newtonsoft.Json; @@ -12,6 +13,7 @@ using WireMock.Admin.Mappings; using WireMock.Authentication; using WireMock.Exceptions; using WireMock.Handlers; +using WireMock.Http; using WireMock.Logging; using WireMock.Matchers.Request; using WireMock.Owin; @@ -101,6 +103,43 @@ public partial class WireMockServer : IWireMockServer } #endregion + #region HttpClient + /// + /// Create a which can be used to call this instance. + /// + [PublicAPI] + public HttpClient CreateClient() + { + if (!IsStarted) + { + throw new InvalidOperationException("Unable to create HttpClient because the service is not started."); + } + + var client = HttpClientFactory2.Create(); + client.BaseAddress = new Uri(Url!); + return client; + } + + /// + /// Create s (one for each URL) which can be used to call this instance. + /// + [PublicAPI] + public HttpClient[] CreateClients() + { + if (!IsStarted) + { + throw new InvalidOperationException("Unable to create HttpClients because the service is not started."); + } + + return Urls.Select(url => + { + var client = HttpClientFactory2.Create(); + client.BaseAddress = new Uri(url); + return client; + }).ToArray(); + } + #endregion + #region Start/Stop /// /// Starts this WireMockServer with the specified settings. diff --git a/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs b/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs index a31cd1c6..851eba80 100644 --- a/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs +++ b/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs @@ -1,53 +1,52 @@ using JetBrains.Annotations; -namespace WireMock.Settings +namespace WireMock.Settings; + +/// +/// ProxyAndRecordSettings +/// +public class ProxyAndRecordSettings : HttpClientSettings { /// - /// ProxyAndRecordSettings + /// The URL to proxy. /// - public class ProxyAndRecordSettings : HttpClientSettings - { - /// - /// The URL to proxy. - /// - [PublicAPI] - public string Url { get; set; } + [PublicAPI] + public string Url { get; set; } = null!; - /// - /// Save the mapping for each request/response to the internal Mappings. - /// - [PublicAPI] - public bool SaveMapping { get; set; } + /// + /// Save the mapping for each request/response to the internal Mappings. + /// + [PublicAPI] + public bool SaveMapping { get; set; } - /// - /// Save the mapping for each request/response also to a file. (Note that SaveMapping must also be set to true.) - /// - [PublicAPI] - public bool SaveMappingToFile { get; set; } + /// + /// Save the mapping for each request/response also to a file. (Note that SaveMapping must also be set to true.) + /// + [PublicAPI] + public bool SaveMappingToFile { get; set; } - /// - /// Only save request/response to the internal Mappings if the status code is included in this pattern. (Note that SaveMapping must also be set to true.) - /// The pattern can contain a single value like "200", but also ranges like "2xx", "100,300,600" or "100-299,6xx" are supported. - /// - [PublicAPI] - public string SaveMappingForStatusCodePattern { get; set; } = "*"; + /// + /// Only save request/response to the internal Mappings if the status code is included in this pattern. (Note that SaveMapping must also be set to true.) + /// The pattern can contain a single value like "200", but also ranges like "2xx", "100,300,600" or "100-299,6xx" are supported. + /// + [PublicAPI] + public string SaveMappingForStatusCodePattern { get; set; } = "*"; - /// - /// Defines a list from headers which will be excluded from the saved mappings. - /// - [PublicAPI] - public string[] ExcludedHeaders { get; set; } + /// + /// Defines a list from headers which will be excluded from the saved mappings. + /// + [PublicAPI] + public string[]? ExcludedHeaders { get; set; } - /// - /// Defines a list of cookies which will be excluded from the saved mappings. - /// - [PublicAPI] - public string[] ExcludedCookies { get; set; } + /// + /// Defines a list of cookies which will be excluded from the saved mappings. + /// + [PublicAPI] + public string[]? ExcludedCookies { get; set; } - /// - /// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to true). - /// - //[PublicAPI] - //public bool PreferProxyMapping { get; set; } - } + /// + /// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to true). + /// + //[PublicAPI] + //public bool PreferProxyMapping { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net/Settings/WebProxySettings.cs b/src/WireMock.Net/Settings/WebProxySettings.cs index b4e47230..ae317a19 100644 --- a/src/WireMock.Net/Settings/WebProxySettings.cs +++ b/src/WireMock.Net/Settings/WebProxySettings.cs @@ -1,28 +1,27 @@ using JetBrains.Annotations; -namespace WireMock.Settings +namespace WireMock.Settings; + +/// +/// WebProxySettings +/// +public class WebProxySettings { /// - /// WebProxySettings + /// A string instance that contains the address of the proxy server. /// - public class WebProxySettings - { - /// - /// A string instance that contains the address of the proxy server. - /// - [PublicAPI] - public string Address { get; set; } + [PublicAPI] + public string Address { get; set; } = null!; - /// - /// The user name associated with the credentials. - /// - [PublicAPI] - public string UserName { get; set; } + /// + /// The user name associated with the credentials. + /// + [PublicAPI] + public string? UserName { get; set; } - /// - /// The password for the user name associated with the credentials. - /// - [PublicAPI] - public string Password { get; set; } - } + /// + /// The password for the user name associated with the credentials. + /// + [PublicAPI] + public string? Password { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net/Transformers/Handlebars/HandlebarsContextFactory.cs b/src/WireMock.Net/Transformers/Handlebars/HandlebarsContextFactory.cs index 8488b3da..d81910aa 100644 --- a/src/WireMock.Net/Transformers/Handlebars/HandlebarsContextFactory.cs +++ b/src/WireMock.Net/Transformers/Handlebars/HandlebarsContextFactory.cs @@ -1,35 +1,33 @@ using System; using HandlebarsDotNet; -using JetBrains.Annotations; using Stef.Validation; using WireMock.Handlers; -namespace WireMock.Transformers.Handlebars +namespace WireMock.Transformers.Handlebars; + +internal class HandlebarsContextFactory : ITransformerContextFactory { - internal class HandlebarsContextFactory : ITransformerContextFactory + private readonly IFileSystemHandler _fileSystemHandler; + private readonly Action? _action; + + public HandlebarsContextFactory(IFileSystemHandler fileSystemHandler, Action? action) { - private readonly IFileSystemHandler _fileSystemHandler; - private readonly Action _action; + _fileSystemHandler = Guard.NotNull(fileSystemHandler); + _action = action; + } - public HandlebarsContextFactory([NotNull] IFileSystemHandler fileSystemHandler, [CanBeNull] Action action) + public ITransformerContext Create() + { + var handlebars = HandlebarsDotNet.Handlebars.Create(); + + WireMockHandlebarsHelpers.Register(handlebars, _fileSystemHandler); + + _action?.Invoke(handlebars, _fileSystemHandler); + + return new HandlebarsContext { - _fileSystemHandler = Guard.NotNull(fileSystemHandler); - _action = action; - } - - public ITransformerContext Create() - { - var handlebars = HandlebarsDotNet.Handlebars.Create(); - - WireMockHandlebarsHelpers.Register(handlebars, _fileSystemHandler); - - _action?.Invoke(handlebars, _fileSystemHandler); - - return new HandlebarsContext - { - Handlebars = handlebars, - FileSystemHandler = _fileSystemHandler - }; - } + Handlebars = handlebars, + FileSystemHandler = _fileSystemHandler + }; } } \ No newline at end of file diff --git a/src/WireMock.Net/Transformers/ITransformer.cs b/src/WireMock.Net/Transformers/ITransformer.cs index cb956505..293b3562 100644 --- a/src/WireMock.Net/Transformers/ITransformer.cs +++ b/src/WireMock.Net/Transformers/ITransformer.cs @@ -1,14 +1,12 @@ -using System; using System.Collections.Generic; using WireMock.Types; using WireMock.Util; -namespace WireMock.Transformers -{ - interface ITransformer - { - ResponseMessage Transform(IRequestMessage requestMessage, IResponseMessage original, bool useTransformerForBodyAsFile, ReplaceNodeOptions options); +namespace WireMock.Transformers; - (IBodyData BodyData, IDictionary> Headers) Transform(IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, IBodyData bodyData, IDictionary> headers, ReplaceNodeOptions options); - } +interface ITransformer +{ + ResponseMessage Transform(IRequestMessage requestMessage, IResponseMessage original, bool useTransformerForBodyAsFile, ReplaceNodeOptions options); + + (IBodyData? BodyData, IDictionary>? Headers) Transform(IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, IBodyData? bodyData, IDictionary>? headers, ReplaceNodeOptions options); } \ No newline at end of file diff --git a/src/WireMock.Net/Transformers/Transformer.cs b/src/WireMock.Net/Transformers/Transformer.cs index ea908148..cac3ca85 100644 --- a/src/WireMock.Net/Transformers/Transformer.cs +++ b/src/WireMock.Net/Transformers/Transformer.cs @@ -1,270 +1,274 @@ using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Stef.Validation; using WireMock.Types; using WireMock.Util; -namespace WireMock.Transformers +namespace WireMock.Transformers; + +internal class Transformer : ITransformer { - internal class Transformer : ITransformer + private readonly ITransformerContextFactory _factory; + + public Transformer(ITransformerContextFactory factory) { - private readonly ITransformerContextFactory _factory; + _factory = Guard.NotNull(factory); + } - public Transformer([NotNull] ITransformerContextFactory factory) + public (IBodyData? BodyData, IDictionary>? Headers) Transform( + IRequestMessage originalRequestMessage, + IResponseMessage originalResponseMessage, + IBodyData? bodyData, + IDictionary>? headers, + ReplaceNodeOptions options) + { + var transformerContext = _factory.Create(); + + var model = new { - _factory = factory ?? throw new ArgumentNullException(nameof(factory)); + request = originalRequestMessage, + response = originalResponseMessage + }; + + IBodyData? newBodyData = null; + if (bodyData?.DetectedBodyType != null) + { + newBodyData = TransformBodyData(transformerContext, options, model, bodyData, false); } - public (IBodyData BodyData, IDictionary> Headers) Transform(IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, IBodyData bodyData, IDictionary> headers, ReplaceNodeOptions options) + return (newBodyData, TransformHeaders(transformerContext, model, headers)); + } + + public ResponseMessage Transform(IRequestMessage requestMessage, IResponseMessage original, bool useTransformerForBodyAsFile, ReplaceNodeOptions options) + { + var transformerContext = _factory.Create(); + + var responseMessage = new ResponseMessage(); + + var model = new { - var transformerContext = _factory.Create(); + request = requestMessage + }; - var model = new - { - request = originalRequestMessage, - response = originalResponseMessage - }; + if (original.BodyData?.DetectedBodyType != null) + { + responseMessage.BodyData = TransformBodyData(transformerContext, options, model, original.BodyData, useTransformerForBodyAsFile); - IBodyData newBodyData = null; - if (bodyData?.DetectedBodyType != null) + if (original.BodyData.DetectedBodyType == BodyType.String) { - newBodyData = TransformBodyData(transformerContext, options, model, bodyData, false); + responseMessage.BodyOriginal = original.BodyData.BodyAsString; } - - return (newBodyData, TransformHeaders(transformerContext, model, headers)); } - public ResponseMessage Transform(IRequestMessage requestMessage, IResponseMessage original, bool useTransformerForBodyAsFile, ReplaceNodeOptions options) + responseMessage.FaultType = original.FaultType; + responseMessage.FaultPercentage = original.FaultPercentage; + + responseMessage.Headers = TransformHeaders(transformerContext, model, original.Headers); + + switch (original.StatusCode) { - var transformerContext = _factory.Create(); + case int statusCodeAsInteger: + responseMessage.StatusCode = statusCodeAsInteger; + break; - var responseMessage = new ResponseMessage(); + case string statusCodeAsString: + responseMessage.StatusCode = transformerContext.ParseAndRender(statusCodeAsString, model); + break; + } - var model = new - { - request = requestMessage - }; + return responseMessage; + } - if (original.BodyData?.DetectedBodyType != null) - { - responseMessage.BodyData = TransformBodyData(transformerContext, options, model, original.BodyData, useTransformerForBodyAsFile); + private static IBodyData TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, object model, IBodyData original, bool useTransformerForBodyAsFile) + { + switch (original?.DetectedBodyType) + { + case BodyType.Json: + return TransformBodyAsJson(transformerContext, options, model, original); - if (original.BodyData.DetectedBodyType == BodyType.String) + case BodyType.File: + return TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile); + + case BodyType.String: + return TransformBodyAsString(transformerContext, model, original); + + default: + return null; + } + } + + private static IDictionary> TransformHeaders(ITransformerContext transformerContext, object model, IDictionary>? original) + { + if (original == null) + { + return new Dictionary>(); + } + + var newHeaders = new Dictionary>(); + foreach (var header in original) + { + var headerKey = transformerContext.ParseAndRender(header.Key, model); + var templateHeaderValues = header.Value.Select(text => transformerContext.ParseAndRender(text, model)).ToArray(); + + newHeaders.Add(headerKey, new WireMockList(templateHeaderValues)); + } + + return newHeaders; + } + + private static IBodyData TransformBodyAsJson(ITransformerContext handlebarsContext, ReplaceNodeOptions options, object model, IBodyData original) + { + JToken? jToken = null; + switch (original.BodyAsJson) + { + case JObject bodyAsJObject: + jToken = bodyAsJObject.DeepClone(); + WalkNode(handlebarsContext, options, jToken, model); + break; + + case JArray bodyAsJArray: + jToken = bodyAsJArray.DeepClone(); + WalkNode(handlebarsContext, options, jToken, model); + break; + + case Array bodyAsArray: + jToken = JArray.FromObject(bodyAsArray); + WalkNode(handlebarsContext, options, jToken, model); + break; + + case string bodyAsString: + jToken = ReplaceSingleNode(handlebarsContext, options, bodyAsString, model); + break; + + case not null: + jToken = JObject.FromObject(original.BodyAsJson); + WalkNode(handlebarsContext, options, jToken, model); + break; + } + + return new BodyData + { + Encoding = original.Encoding, + DetectedBodyType = original.DetectedBodyType, + DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType, + BodyAsJson = jToken + }; + } + + private static JToken ReplaceSingleNode(ITransformerContext handlebarsContext, ReplaceNodeOptions options, string stringValue, object model) + { + string transformedString = handlebarsContext.ParseAndRender(stringValue, model); + + if (!string.Equals(stringValue, transformedString)) + { + const string property = "_"; + JObject dummy = JObject.Parse($"{{ \"{property}\": null }}"); + JToken node = dummy[property]; + + ReplaceNodeValue(options, node, transformedString); + + return dummy[property]; + } + + return stringValue; + } + + private static void WalkNode(ITransformerContext handlebarsContext, ReplaceNodeOptions options, JToken node, object model) + { + switch (node.Type) + { + case JTokenType.Object: + // In case of Object, loop all children. Do a ToArray() to avoid `Collection was modified` exceptions. + foreach (var child in node.Children().ToArray()) { - responseMessage.BodyOriginal = original.BodyData.BodyAsString; + WalkNode(handlebarsContext, options, child.Value, model); } - } + break; - responseMessage.FaultType = original.FaultType; - responseMessage.FaultPercentage = original.FaultPercentage; - - responseMessage.Headers = TransformHeaders(transformerContext, model, original.Headers); - - switch (original.StatusCode) - { - case int statusCodeAsInteger: - responseMessage.StatusCode = statusCodeAsInteger; - break; - - case string statusCodeAsString: - responseMessage.StatusCode = transformerContext.ParseAndRender(statusCodeAsString, model); - break; - } - - return responseMessage; - } - - private static IBodyData TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, object model, IBodyData original, bool useTransformerForBodyAsFile) - { - switch (original?.DetectedBodyType) - { - case BodyType.Json: - return TransformBodyAsJson(transformerContext, options, model, original); - - case BodyType.File: - return TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile); - - case BodyType.String: - return TransformBodyAsString(transformerContext, model, original); - - default: - return null; - } - } - - private static IDictionary> TransformHeaders(ITransformerContext transformerContext, object model, IDictionary> original) - { - if (original == null) - { - return new Dictionary>(); - } - - var newHeaders = new Dictionary>(); - foreach (var header in original) - { - var headerKey = transformerContext.ParseAndRender(header.Key, model); - var templateHeaderValues = header.Value.Select(text => transformerContext.ParseAndRender(text, model)).ToArray(); - - newHeaders.Add(headerKey, new WireMockList(templateHeaderValues)); - } - - return newHeaders; - } - - private static IBodyData TransformBodyAsJson(ITransformerContext handlebarsContext, ReplaceNodeOptions options, object model, IBodyData original) - { - JToken jToken; - switch (original.BodyAsJson) - { - case JObject bodyAsJObject: - jToken = bodyAsJObject.DeepClone(); - WalkNode(handlebarsContext, options, jToken, model); - break; - - case JArray bodyAsJArray: - jToken = bodyAsJArray.DeepClone(); - WalkNode(handlebarsContext, options, jToken, model); - break; - - case Array bodyAsArray: - jToken = JArray.FromObject(bodyAsArray); - WalkNode(handlebarsContext, options, jToken, model); - break; - - case string bodyAsString: - jToken = ReplaceSingleNode(handlebarsContext, options, bodyAsString, model); - break; - - default: - jToken = JObject.FromObject(original.BodyAsJson); - WalkNode(handlebarsContext, options, jToken, model); - break; - } - - return new BodyData - { - Encoding = original.Encoding, - DetectedBodyType = original.DetectedBodyType, - DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType, - BodyAsJson = jToken - }; - } - - private static JToken ReplaceSingleNode(ITransformerContext handlebarsContext, ReplaceNodeOptions options, string stringValue, object model) - { - string transformedString = handlebarsContext.ParseAndRender(stringValue, model); - - if (!string.Equals(stringValue, transformedString)) - { - const string property = "_"; - JObject dummy = JObject.Parse($"{{ \"{property}\": null }}"); - JToken node = dummy[property]; - - ReplaceNodeValue(options, node, transformedString); - - return dummy[property]; - } - - return stringValue; - } - - private static void WalkNode(ITransformerContext handlebarsContext, ReplaceNodeOptions options, JToken node, object model) - { - switch (node.Type) - { - case JTokenType.Object: - // In case of Object, loop all children. Do a ToArray() to avoid `Collection was modified` exceptions. - foreach (var child in node.Children().ToArray()) - { - WalkNode(handlebarsContext, options, child.Value, model); - } - break; - - case JTokenType.Array: - // In case of Array, loop all items. Do a ToArray() to avoid `Collection was modified` exceptions. - foreach (var child in node.Children().ToArray()) - { - WalkNode(handlebarsContext, options, child, model); - } - break; - - case JTokenType.String: - // In case of string, try to transform the value. - string stringValue = node.Value(); - if (string.IsNullOrEmpty(stringValue)) - { - return; - } - - string transformed = handlebarsContext.ParseAndRender(stringValue, model); - if (!string.Equals(stringValue, transformed)) - { - ReplaceNodeValue(options, node, transformed); - } - break; - } - } - - private static void ReplaceNodeValue(ReplaceNodeOptions options, JToken node, string transformedString) - { - StringUtils.TryParseQuotedString(transformedString, out var result, out _); - if (bool.TryParse(result, out var valueAsBoolean) || bool.TryParse(transformedString, out valueAsBoolean)) - { - node.Replace(valueAsBoolean); - return; - } - - JToken value; - try - { - // Try to convert this string into a JsonObject - value = JToken.Parse(transformedString); - } - catch (JsonException) - { - // Ignore JsonException and just keep string value and convert to JToken - value = transformedString; - } - - node.Replace(value); - } - - private static IBodyData TransformBodyAsString(ITransformerContext handlebarsContext, object model, IBodyData original) - { - return new BodyData - { - Encoding = original.Encoding, - DetectedBodyType = original.DetectedBodyType, - DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType, - BodyAsString = handlebarsContext.ParseAndRender(original.BodyAsString, model) - }; - } - - private static IBodyData TransformBodyAsFile(ITransformerContext handlebarsContext, object model, IBodyData original, bool useTransformerForBodyAsFile) - { - string transformedBodyAsFilename = handlebarsContext.ParseAndRender(original.BodyAsFile, model); - - if (!useTransformerForBodyAsFile) - { - return new BodyData + case JTokenType.Array: + // In case of Array, loop all items. Do a ToArray() to avoid `Collection was modified` exceptions. + foreach (var child in node.Children().ToArray()) { - DetectedBodyType = original.DetectedBodyType, - DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType, - BodyAsFile = transformedBodyAsFilename - }; - } + WalkNode(handlebarsContext, options, child, model); + } + break; - string text = handlebarsContext.FileSystemHandler.ReadResponseBodyAsString(transformedBodyAsFilename); + case JTokenType.String: + // In case of string, try to transform the value. + var stringValue = node.Value(); + if (string.IsNullOrEmpty(stringValue)) + { + return; + } + + string transformed = handlebarsContext.ParseAndRender(stringValue!, model); + if (!string.Equals(stringValue, transformed)) + { + ReplaceNodeValue(options, node, transformed); + } + break; + } + } + + private static void ReplaceNodeValue(ReplaceNodeOptions options, JToken node, string transformedString) + { + StringUtils.TryParseQuotedString(transformedString, out var result, out _); + if (bool.TryParse(result, out var valueAsBoolean) || bool.TryParse(transformedString, out valueAsBoolean)) + { + node.Replace(valueAsBoolean); + return; + } + + JToken value; + try + { + // Try to convert this string into a JsonObject + value = JToken.Parse(transformedString); + } + catch (JsonException) + { + // Ignore JsonException and just keep string value and convert to JToken + value = transformedString; + } + + node.Replace(value); + } + + private static IBodyData TransformBodyAsString(ITransformerContext handlebarsContext, object model, IBodyData original) + { + return new BodyData + { + Encoding = original.Encoding, + DetectedBodyType = original.DetectedBodyType, + DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType, + BodyAsString = handlebarsContext.ParseAndRender(original.BodyAsString, model) + }; + } + + private static IBodyData TransformBodyAsFile(ITransformerContext handlebarsContext, object model, IBodyData original, bool useTransformerForBodyAsFile) + { + string transformedBodyAsFilename = handlebarsContext.ParseAndRender(original.BodyAsFile, model); + + if (!useTransformerForBodyAsFile) + { return new BodyData { - DetectedBodyType = BodyType.String, + DetectedBodyType = original.DetectedBodyType, DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType, - BodyAsString = handlebarsContext.ParseAndRender(text, model), BodyAsFile = transformedBodyAsFilename }; } + + string text = handlebarsContext.FileSystemHandler.ReadResponseBodyAsString(transformedBodyAsFilename); + return new BodyData + { + DetectedBodyType = BodyType.String, + DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType, + BodyAsString = handlebarsContext.ParseAndRender(text, model), + BodyAsFile = transformedBodyAsFilename + }; } } \ No newline at end of file diff --git a/src/WireMock.Net/Util/JsonUtils.cs b/src/WireMock.Net/Util/JsonUtils.cs index b464826e..d01cfad5 100644 --- a/src/WireMock.Net/Util/JsonUtils.cs +++ b/src/WireMock.Net/Util/JsonUtils.cs @@ -87,7 +87,7 @@ internal static class JsonUtils /// /// A System.String that contains JSON. /// A Newtonsoft.Json.Linq.JToken populated from the string that contains JSON. - public static JToken Parse(string json) + public static JToken? Parse(string json) { return JsonConvert.DeserializeObject(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone); } @@ -98,7 +98,7 @@ internal static class JsonUtils /// /// A System.String that contains JSON. /// The deserialized object from the JSON string. - public static object DeserializeObject(string json) + public static object? DeserializeObject(string json) { return JsonConvert.DeserializeObject(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone); } @@ -109,21 +109,18 @@ internal static class JsonUtils /// /// A System.String that contains JSON. /// The deserialized object from the JSON string. - public static T DeserializeObject(string json) + public static T? DeserializeObject(string json) { return JsonConvert.DeserializeObject(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone); } - public static T ParseJTokenToObject(object value) + public static T? ParseJTokenToObject(object value) { - switch (value) + return value switch { - case JToken tokenValue: - return tokenValue.ToObject(); - - default: - return default(T); - } + JToken tokenValue => tokenValue.ToObject(), + _ => default + }; } public static string GenerateDynamicLinqStatement(JToken jsonObject) diff --git a/src/WireMock.Net/Util/StringUtils.cs b/src/WireMock.Net/Util/StringUtils.cs index 18c59853..161c4aac 100644 --- a/src/WireMock.Net/Util/StringUtils.cs +++ b/src/WireMock.Net/Util/StringUtils.cs @@ -1,42 +1,51 @@ +using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.RegularExpressions; +using WireMock.Matchers; -namespace WireMock.Util +namespace WireMock.Util; + +internal static class StringUtils { - internal static class StringUtils + public static MatchOperator ParseMatchOperator(string? value) { - public static bool TryParseQuotedString(string value, out string result, out char quote) + return value != null && Enum.TryParse(value, out var matchOperator) + ? matchOperator + : MatchOperator.Or; + } + + public static bool TryParseQuotedString(string? value, [NotNullWhen(true)] out string? result, out char quote) + { + result = null; + quote = '\0'; + + if (value == null || value.Length < 2) { - result = null; - quote = '\0'; - - if (value == null || value.Length < 2) - { - return false; - } - - quote = value[0]; // This can be single or a double quote - if (quote != '"' && quote != '\'') - { - return false; - } - - if (value.Last() != quote) - { - return false; - } - - try - { - result = Regex.Unescape(value.Substring(1, value.Length - 2)); - return true; - } - catch - { - // Ignore Exception, just continue and return false. - } - return false; } + + quote = value[0]; // This can be single or a double quote + if (quote != '"' && quote != '\'') + { + return false; + } + + if (value.Last() != quote) + { + return false; + } + + try + { + result = Regex.Unescape(value.Substring(1, value.Length - 2)); + return true; + } + catch + { + // Ignore Exception, just continue and return false. + } + + return false; } } \ No newline at end of file diff --git a/src/WireMock.Net/WireMock.Net.csproj b/src/WireMock.Net/WireMock.Net.csproj index c114d79e..69787ac9 100644 --- a/src/WireMock.Net/WireMock.Net.csproj +++ b/src/WireMock.Net/WireMock.Net.csproj @@ -79,6 +79,7 @@ + @@ -145,6 +146,12 @@ + + + Request.cs + + + @@ -159,4 +166,4 @@ - + \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Matchers/CSharpCodeMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/CSharpCodeMatcherTests.cs index a63d0c0b..03baab83 100644 --- a/test/WireMock.Net.Tests/Matchers/CSharpCodeMatcherTests.cs +++ b/test/WireMock.Net.Tests/Matchers/CSharpCodeMatcherTests.cs @@ -39,7 +39,7 @@ namespace WireMock.Net.Tests.Matchers string input = "x"; // Act - var matcher = new CSharpCodeMatcher(MatchBehaviour.RejectOnMatch, "return it == \"x\";"); + var matcher = new CSharpCodeMatcher(MatchBehaviour.RejectOnMatch, MatchOperator.Or, "return it == \"x\";"); // Assert Check.That(matcher.IsMatch(input)).IsEqualTo(0.0d); diff --git a/test/WireMock.Net.Tests/Matchers/ExactMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/ExactMatcherTests.cs index 46608a1f..bb02ad12 100644 --- a/test/WireMock.Net.Tests/Matchers/ExactMatcherTests.cs +++ b/test/WireMock.Net.Tests/Matchers/ExactMatcherTests.cs @@ -59,7 +59,7 @@ namespace WireMock.Net.Tests.Matchers } [Fact] - public void ExactMatcher_IsMatch_WithMultiplePatterns_ReturnsMatch0_5() + public void ExactMatcher_IsMatch_WithMultiplePatterns_Or_ReturnsMatch_1_0() { // Assign var matcher = new ExactMatcher("x", "y"); @@ -71,6 +71,35 @@ namespace WireMock.Net.Tests.Matchers Check.That(result).IsEqualTo(1.0); } + [Fact] + public void ExactMatcher_IsMatch_WithMultiplePatterns_And_ReturnsMatch_0_0() + { + // Assign + var matcher = new ExactMatcher("x", "y"); + + // Act + double result = matcher.IsMatch("x"); + + // Assert + Check.That(result).IsEqualTo(1.0); + } + + [Theory] + [InlineData(MatchOperator.Or, 1.0d)] + [InlineData(MatchOperator.And, 0.0d)] + [InlineData(MatchOperator.Average, 0.5d)] + public void ExactMatcher_IsMatch_WithMultiplePatterns_Average_ReturnsMatch(MatchOperator matchOperator, double score) + { + // Assign + var matcher = new ExactMatcher(MatchBehaviour.AcceptOnMatch, false, matchOperator, "x", "y"); + + // Act + double result = matcher.IsMatch("x"); + + // Assert + Check.That(result).IsEqualTo(score); + } + [Fact] public void ExactMatcher_IsMatch_SinglePattern() { @@ -88,7 +117,7 @@ namespace WireMock.Net.Tests.Matchers public void ExactMatcher_IsMatch_SinglePattern_AcceptOnMatch() { // Assign - var matcher = new ExactMatcher(MatchBehaviour.AcceptOnMatch, false, "cat"); + var matcher = new ExactMatcher(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, "cat"); // Act double result = matcher.IsMatch("cat"); @@ -101,7 +130,7 @@ namespace WireMock.Net.Tests.Matchers public void ExactMatcher_IsMatch_SinglePattern_RejectOnMatch() { // Assign - var matcher = new ExactMatcher(MatchBehaviour.RejectOnMatch, false, "cat"); + var matcher = new ExactMatcher(MatchBehaviour.RejectOnMatch, false, MatchOperator.Or, "cat"); // Act double result = matcher.IsMatch("cat"); diff --git a/test/WireMock.Net.Tests/Matchers/JmesPathMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/JmesPathMatcherTests.cs index eab4af1d..afdff3f7 100644 --- a/test/WireMock.Net.Tests/Matchers/JmesPathMatcherTests.cs +++ b/test/WireMock.Net.Tests/Matchers/JmesPathMatcherTests.cs @@ -51,7 +51,7 @@ namespace WireMock.Net.Tests.Matchers public void JmesPathMatcher_IsMatch_NullString() { // Assign - string s = null; + string? s = null; var matcher = new JmesPathMatcher(""); // Act @@ -65,7 +65,7 @@ namespace WireMock.Net.Tests.Matchers public void JmesPathMatcher_IsMatch_NullObject() { // Assign - object o = null; + object? o = null; var matcher = new JmesPathMatcher(""); // Act @@ -154,7 +154,7 @@ namespace WireMock.Net.Tests.Matchers public void JmesPathMatcher_IsMatch_RejectOnMatch() { // Assign - var matcher = new JmesPathMatcher(MatchBehaviour.RejectOnMatch, false, "things.x == 'RequiredThing'"); + var matcher = new JmesPathMatcher(MatchBehaviour.RejectOnMatch, false, MatchOperator.Or, "things.x == 'RequiredThing'"); // Act double match = matcher.IsMatch(JObject.Parse("{ \"things\": { \"x\": \"RequiredThing\" } }")); diff --git a/test/WireMock.Net.Tests/Matchers/JsonPathMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/JsonPathMatcherTests.cs index 262c9a9f..a79ecc52 100644 --- a/test/WireMock.Net.Tests/Matchers/JsonPathMatcherTests.cs +++ b/test/WireMock.Net.Tests/Matchers/JsonPathMatcherTests.cs @@ -3,160 +3,159 @@ using NFluent; using WireMock.Matchers; using Xunit; -namespace WireMock.Net.Tests.Matchers +namespace WireMock.Net.Tests.Matchers; + +public class JsonPathMatcherTests { - public class JsonPathMatcherTests + [Fact] + public void JsonPathMatcher_GetName() { - [Fact] - public void JsonPathMatcher_GetName() + // Assign + var matcher = new JsonPathMatcher("X"); + + // Act + string name = matcher.Name; + + // Assert + Check.That(name).Equals("JsonPathMatcher"); + } + + [Fact] + public void JsonPathMatcher_GetPatterns() + { + // Assign + var matcher = new JsonPathMatcher("X"); + + // Act + var patterns = matcher.GetPatterns(); + + // Assert + Check.That(patterns).ContainsExactly("X"); + } + + [Fact] + public void JsonPathMatcher_IsMatch_ByteArray() + { + // Assign + var bytes = new byte[0]; + var matcher = new JsonPathMatcher(""); + + // Act + double match = matcher.IsMatch(bytes); + + // Assert + Check.That(match).IsEqualTo(0); + } + + [Fact] + public void JsonPathMatcher_IsMatch_NullString() + { + // Assign + string? s = null; + var matcher = new JsonPathMatcher(""); + + // Act + double match = matcher.IsMatch(s); + + // Assert + Check.That(match).IsEqualTo(0); + } + + [Fact] + public void JsonPathMatcher_IsMatch_NullObject() + { + // Assign + object? o = null; + var matcher = new JsonPathMatcher(""); + + // Act + double match = matcher.IsMatch(o); + + // Assert + Check.That(match).IsEqualTo(0); + } + + [Fact] + public void JsonPathMatcher_IsMatch_String_Exception_Mismatch() + { + // Assign + var matcher = new JsonPathMatcher("xxx"); + + // Act + double match = matcher.IsMatch(""); + + // Assert + Check.That(match).IsEqualTo(0); + } + + [Fact] + public void JsonPathMatcher_IsMatch_Object_Exception_Mismatch() + { + // Assign + var matcher = new JsonPathMatcher(""); + + // Act + double match = matcher.IsMatch("x"); + + // Assert + Check.That(match).IsEqualTo(0); + } + + [Fact] + public void JsonPathMatcher_IsMatch_AnonymousObject() + { + // Assign + var matcher = new JsonPathMatcher("$..[?(@.Id == 1)]"); + + // Act + double match = matcher.IsMatch(new { Id = 1, Name = "Test" }); + + // Assert + Check.That(match).IsEqualTo(1); + } + + [Fact] + public void JsonPathMatcher_IsMatch_JObject() + { + // Assign + string[] patterns = { "$..[?(@.Id == 1)]" }; + var matcher = new JsonPathMatcher(patterns); + + // Act + var jobject = new JObject { - // Assign - var matcher = new JsonPathMatcher("X"); + { "Id", new JValue(1) }, + { "Name", new JValue("Test") } + }; + double match = matcher.IsMatch(jobject); - // Act - string name = matcher.Name; + // Assert + Check.That(match).IsEqualTo(1); + } - // Assert - Check.That(name).Equals("JsonPathMatcher"); - } + [Fact] + public void JsonPathMatcher_IsMatch_JObject_Parsed() + { + // Assign + var matcher = new JsonPathMatcher("$..[?(@.Id == 1)]"); - [Fact] - public void JsonPathMatcher_GetPatterns() - { - // Assign - var matcher = new JsonPathMatcher("X"); + // Act + double match = matcher.IsMatch(JObject.Parse("{\"Id\":1,\"Name\":\"Test\"}")); - // Act - var patterns = matcher.GetPatterns(); + // Assert + Check.That(match).IsEqualTo(1); + } - // Assert - Check.That(patterns).ContainsExactly("X"); - } + [Fact] + public void JsonPathMatcher_IsMatch_RejectOnMatch() + { + // Assign + var matcher = new JsonPathMatcher(MatchBehaviour.RejectOnMatch, false, MatchOperator.Or, "$..[?(@.Id == 1)]"); - [Fact] - public void JsonPathMatcher_IsMatch_ByteArray() - { - // Assign - var bytes = new byte[0]; - var matcher = new JsonPathMatcher(""); + // Act + double match = matcher.IsMatch(JObject.Parse("{\"Id\":1,\"Name\":\"Test\"}")); - // Act - double match = matcher.IsMatch(bytes); - - // Assert - Check.That(match).IsEqualTo(0); - } - - [Fact] - public void JsonPathMatcher_IsMatch_NullString() - { - // Assign - string s = null; - var matcher = new JsonPathMatcher(""); - - // Act - double match = matcher.IsMatch(s); - - // Assert - Check.That(match).IsEqualTo(0); - } - - [Fact] - public void JsonPathMatcher_IsMatch_NullObject() - { - // Assign - object o = null; - var matcher = new JsonPathMatcher(""); - - // Act - double match = matcher.IsMatch(o); - - // Assert - Check.That(match).IsEqualTo(0); - } - - [Fact] - public void JsonPathMatcher_IsMatch_String_Exception_Mismatch() - { - // Assign - var matcher = new JsonPathMatcher("xxx"); - - // Act - double match = matcher.IsMatch(""); - - // Assert - Check.That(match).IsEqualTo(0); - } - - [Fact] - public void JsonPathMatcher_IsMatch_Object_Exception_Mismatch() - { - // Assign - var matcher = new JsonPathMatcher(""); - - // Act - double match = matcher.IsMatch("x"); - - // Assert - Check.That(match).IsEqualTo(0); - } - - [Fact] - public void JsonPathMatcher_IsMatch_AnonymousObject() - { - // Assign - var matcher = new JsonPathMatcher("$..[?(@.Id == 1)]"); - - // Act - double match = matcher.IsMatch(new { Id = 1, Name = "Test" }); - - // Assert - Check.That(match).IsEqualTo(1); - } - - [Fact] - public void JsonPathMatcher_IsMatch_JObject() - { - // Assign - string[] patterns = { "$..[?(@.Id == 1)]" }; - var matcher = new JsonPathMatcher(patterns); - - // Act - var jobject = new JObject - { - { "Id", new JValue(1) }, - { "Name", new JValue("Test") } - }; - double match = matcher.IsMatch(jobject); - - // Assert - Check.That(match).IsEqualTo(1); - } - - [Fact] - public void JsonPathMatcher_IsMatch_JObject_Parsed() - { - // Assign - var matcher = new JsonPathMatcher("$..[?(@.Id == 1)]"); - - // Act - double match = matcher.IsMatch(JObject.Parse("{\"Id\":1,\"Name\":\"Test\"}")); - - // Assert - Check.That(match).IsEqualTo(1); - } - - [Fact] - public void JsonPathMatcher_IsMatch_RejectOnMatch() - { - // Assign - var matcher = new JsonPathMatcher(MatchBehaviour.RejectOnMatch, false, "$..[?(@.Id == 1)]"); - - // Act - double match = matcher.IsMatch(JObject.Parse("{\"Id\":1,\"Name\":\"Test\"}")); - - // Assert - Check.That(match).IsEqualTo(0.0); - } + // Assert + Check.That(match).IsEqualTo(0.0); } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Matchers/WildcardMatcherTest.cs b/test/WireMock.Net.Tests/Matchers/WildcardMatcherTest.cs index 7fdefc2d..6adb2544 100644 --- a/test/WireMock.Net.Tests/Matchers/WildcardMatcherTest.cs +++ b/test/WireMock.Net.Tests/Matchers/WildcardMatcherTest.cs @@ -10,7 +10,7 @@ namespace WireMock.Net.Tests.Matchers public class WildcardMatcherTest { [Fact] - public void WildcardMatcher_IsMatch_With_StringMatcher_And_StringPattern() + public void WildcardMatcher_IsMatch_With_StringPattern() { // Arrange var pattern = new StringPattern @@ -26,6 +26,26 @@ namespace WireMock.Net.Tests.Matchers matcher.IsMatch("a").Should().Be(1.0d); } + [Fact] + public void WildcardMatcher_IsMatch_With_StringPatterns() + { + // Arrange + AnyOf pattern1 = new StringPattern + { + Pattern = "a" + }; + AnyOf pattern2 = new StringPattern + { + Pattern = "b" + }; + + // Act + var matcher = new WildcardMatcher(new [] { pattern1, pattern2 }); + + // Assert + matcher.IsMatch("a").Should().Be(1.0d); + } + [Fact] public void WildcardMatcher_IsMatch_Positive() { diff --git a/test/WireMock.Net.Tests/Matchers/XPathMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/XPathMatcherTests.cs index d67806f8..19068053 100644 --- a/test/WireMock.Net.Tests/Matchers/XPathMatcherTests.cs +++ b/test/WireMock.Net.Tests/Matchers/XPathMatcherTests.cs @@ -2,68 +2,67 @@ using NFluent; using WireMock.Matchers; using Xunit; -namespace WireMock.Net.Tests.Matchers +namespace WireMock.Net.Tests.Matchers; + +public class XPathMatcherTests { - public class XPathMatcherTests + [Fact] + public void XPathMatcher_GetName() { - [Fact] - public void XPathMatcher_GetName() - { - // Assign - var matcher = new XPathMatcher("X"); + // Assign + var matcher = new XPathMatcher("X"); - // Act - string name = matcher.Name; + // Act + string name = matcher.Name; - // Assert - Check.That(name).Equals("XPathMatcher"); - } + // Assert + Check.That(name).Equals("XPathMatcher"); + } - [Fact] - public void XPathMatcher_GetPatterns() - { - // Assign - var matcher = new XPathMatcher("X"); + [Fact] + public void XPathMatcher_GetPatterns() + { + // Assign + var matcher = new XPathMatcher("X"); - // Act - var patterns = matcher.GetPatterns(); + // Act + var patterns = matcher.GetPatterns(); - // Assert - Check.That(patterns).ContainsExactly("X"); - } + // Assert + Check.That(patterns).ContainsExactly("X"); + } - [Fact] - public void XPathMatcher_IsMatch_AcceptOnMatch() - { - // Assign - string xml = @" + [Fact] + public void XPathMatcher_IsMatch_AcceptOnMatch() + { + // Assign + string xml = @" abc "; - var matcher = new XPathMatcher("/todo-list[count(todo-item) = 1]"); + var matcher = new XPathMatcher("/todo-list[count(todo-item) = 1]"); - // Act - double result = matcher.IsMatch(xml); + // Act + double result = matcher.IsMatch(xml); - // Assert - Check.That(result).IsEqualTo(1.0); - } + // Assert + Check.That(result).IsEqualTo(1.0); + } - [Fact] - public void XPathMatcher_IsMatch_RejectOnMatch() - { - // Assign - string xml = @" + [Fact] + public void XPathMatcher_IsMatch_RejectOnMatch() + { + // Assign + string xml = @" abc "; - var matcher = new XPathMatcher(MatchBehaviour.RejectOnMatch, false, "/todo-list[count(todo-item) = 1]"); + var matcher = new XPathMatcher(MatchBehaviour.RejectOnMatch, false, MatchOperator.Or, "/todo-list[count(todo-item) = 1]"); - // Act - double result = matcher.IsMatch(xml); + // Act + double result = matcher.IsMatch(xml); - // Assert - Check.That(result).IsEqualTo(0.0); - } + // Assert + Check.That(result).IsEqualTo(0.0); } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Plugin/PluginLoaderTests.cs b/test/WireMock.Net.Tests/Plugin/PluginLoaderTests.cs index 69843a47..25476def 100644 --- a/test/WireMock.Net.Tests/Plugin/PluginLoaderTests.cs +++ b/test/WireMock.Net.Tests/Plugin/PluginLoaderTests.cs @@ -6,33 +6,32 @@ using WireMock.Models; using WireMock.Plugin; using Xunit; -namespace WireMock.Net.Tests.Plugin +namespace WireMock.Net.Tests.Plugin; + +public class PluginLoaderTests { - public class PluginLoaderTests + public interface IDummy { - public interface IDummy - { - } + } - [Fact] - public void Load_Valid() - { - // Act - AnyOf pattern = "x"; - var result = PluginLoader.Load(MatchBehaviour.AcceptOnMatch, pattern); + [Fact] + public void Load_Valid() + { + // Act + AnyOf pattern = "x"; + var result = PluginLoader.Load(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, pattern); - // Assert - result.Should().NotBeNull(); - } + // Assert + result.Should().NotBeNull(); + } - [Fact] - public void Load_Invalid_ThrowsException() - { - // Act - Action a = () => PluginLoader.Load(); + [Fact] + public void Load_Invalid_ThrowsException() + { + // Act + Action a = () => PluginLoader.Load(); - // Assert - a.Should().Throw(); - } + // Assert + a.Should().Throw(); } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithUrlTests.cs b/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithUrlTests.cs index cd14e513..33d5dc7c 100644 --- a/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithUrlTests.cs +++ b/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithUrlTests.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using NFluent; using WireMock.Matchers; using WireMock.Matchers.Request; @@ -25,7 +25,7 @@ namespace WireMock.Net.Tests.RequestBuilders public void RequestBuilder_WithUrl_MatchBehaviour_Strings() { // Act - var requestBuilder = (Request)Request.Create().WithUrl(MatchBehaviour.AcceptOnMatch, "http://a", "http://b"); + var requestBuilder = (Request)Request.Create().WithUrl(MatchOperator.Or, "http://a", "http://b"); // Assert var matchers = requestBuilder.GetPrivateFieldValue>("_requestMatchers"); diff --git a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageBodyMatcherTests.cs b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageBodyMatcherTests.cs index de388b2b..86897a0e 100644 --- a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageBodyMatcherTests.cs +++ b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageBodyMatcherTests.cs @@ -27,7 +27,7 @@ namespace WireMock.Net.Tests.RequestMatchers DetectedBodyType = BodyType.String }; var stringMatcherMock = new Mock(); - stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(0.5d); + stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(1d); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); @@ -38,15 +38,19 @@ namespace WireMock.Net.Tests.RequestMatchers double score = matcher.GetMatchingScore(requestMessage, result); // Assert - Check.That(score).IsEqualTo(0.5d); + Check.That(score).IsEqualTo(1d); // Verify stringMatcherMock.Verify(m => m.GetPatterns(), Times.Never); stringMatcherMock.Verify(m => m.IsMatch("b"), Times.Once); } - [Fact] - public void RequestMessageBodyMatcher_GetMatchingScore_BodyAsString_IStringMatchers() + [Theory] + [InlineData(1d, 1d, 1d)] + [InlineData(0d, 1d, 1d)] + [InlineData(1d, 0d, 1d)] + [InlineData(0d, 0d, 0d)] + public void RequestMessageBodyMatcher_GetMatchingScore_BodyAsString_IStringMatchers_Or(double one, double two, double expected) { // Assign var body = new BodyData @@ -55,25 +59,110 @@ namespace WireMock.Net.Tests.RequestMatchers DetectedBodyType = BodyType.String }; var stringMatcherMock1 = new Mock(); - stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(0.2d); + stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(one); + var stringMatcherMock2 = new Mock(); - stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(0.8d); + stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(two); + var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object }; var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); - var matcher = new RequestMessageBodyMatcher(matchers.Cast().ToArray()); + var matcher = new RequestMessageBodyMatcher(MatchOperator.Or, matchers.Cast().ToArray()); // Act var result = new RequestMatchResult(); double score = matcher.GetMatchingScore(requestMessage, result); // Assert - Check.That(score).IsEqualTo(0.8d); + Check.That(score).IsEqualTo(expected); // Verify stringMatcherMock1.Verify(m => m.GetPatterns(), Times.Never); stringMatcherMock1.Verify(m => m.IsMatch("b"), Times.Once); + + stringMatcherMock2.Verify(m => m.GetPatterns(), Times.Never); + stringMatcherMock2.Verify(m => m.IsMatch("b"), Times.Once); + stringMatcherMock2.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData(1d, 1d, 1d)] + [InlineData(0d, 1d, 0d)] + [InlineData(1d, 0d, 0d)] + [InlineData(0d, 0d, 0d)] + public void RequestMessageBodyMatcher_GetMatchingScore_BodyAsString_IStringMatchers_And(double one, double two, double expected) + { + // Assign + var body = new BodyData + { + BodyAsString = "b", + DetectedBodyType = BodyType.String + }; + var stringMatcherMock1 = new Mock(); + stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(one); + + var stringMatcherMock2 = new Mock(); + stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(two); + + var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object }; + + var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); + + var matcher = new RequestMessageBodyMatcher(MatchOperator.And, matchers.Cast().ToArray()); + + // Act + var result = new RequestMatchResult(); + double score = matcher.GetMatchingScore(requestMessage, result); + + // Assert + Check.That(score).IsEqualTo(expected); + + // Verify + stringMatcherMock1.Verify(m => m.GetPatterns(), Times.Never); + stringMatcherMock1.Verify(m => m.IsMatch("b"), Times.Once); + + stringMatcherMock2.Verify(m => m.GetPatterns(), Times.Never); + stringMatcherMock2.Verify(m => m.IsMatch("b"), Times.Once); + stringMatcherMock2.VerifyNoOtherCalls(); + } + + [Theory] + [InlineData(1d, 1d, 1d)] + [InlineData(0d, 1d, 0.5d)] + [InlineData(1d, 0d, 0.5d)] + [InlineData(0d, 0d, 0d)] + public void RequestMessageBodyMatcher_GetMatchingScore_BodyAsString_IStringMatchers_Average(double one, double two, double expected) + { + // Assign + var body = new BodyData + { + BodyAsString = "b", + DetectedBodyType = BodyType.String + }; + var stringMatcherMock1 = new Mock(); + stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(one); + + var stringMatcherMock2 = new Mock(); + stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(two); + + var matchers = new[] { stringMatcherMock1.Object, stringMatcherMock2.Object }; + + var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); + + var matcher = new RequestMessageBodyMatcher(MatchOperator.Average, matchers.Cast().ToArray()); + + // Act + var result = new RequestMatchResult(); + double score = matcher.GetMatchingScore(requestMessage, result); + + // Assert + Check.That(score).IsEqualTo(expected); + + // Verify + stringMatcherMock1.Verify(m => m.GetPatterns(), Times.Never); + stringMatcherMock1.Verify(m => m.IsMatch("b"), Times.Once); + stringMatcherMock2.Verify(m => m.GetPatterns(), Times.Never); stringMatcherMock2.Verify(m => m.IsMatch("b"), Times.Once); } @@ -116,7 +205,7 @@ namespace WireMock.Net.Tests.RequestMatchers DetectedBodyType = BodyType.Json }; var stringMatcherMock = new Mock(); - stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(0.5d); + stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(1.0d); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); @@ -127,7 +216,7 @@ namespace WireMock.Net.Tests.RequestMatchers double score = matcher.GetMatchingScore(requestMessage, result); // Assert - Check.That(score).IsEqualTo(0.5d); + Check.That(score).IsEqualTo(1.0d); // Verify stringMatcherMock.Verify(m => m.IsMatch(It.IsAny()), Times.Once); @@ -144,7 +233,8 @@ namespace WireMock.Net.Tests.RequestMatchers DetectedBodyType = BodyType.Json }; var stringMatcherMock = new Mock(); - stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(0.5d); + stringMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(1d); + stringMatcherMock.SetupGet(m => m.MatchOperator).Returns(MatchOperator.Or); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); @@ -155,7 +245,7 @@ namespace WireMock.Net.Tests.RequestMatchers double score = matcher.GetMatchingScore(requestMessage, result); // Assert - Check.That(score).IsEqualTo(0.5d); + Check.That(score).IsEqualTo(1d); // Verify stringMatcherMock.Verify(m => m.IsMatch(It.IsAny()), Times.Once); @@ -171,7 +261,7 @@ namespace WireMock.Net.Tests.RequestMatchers DetectedBodyType = BodyType.Json }; var objectMatcherMock = new Mock(); - objectMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(0.5d); + objectMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(1d); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); @@ -182,7 +272,7 @@ namespace WireMock.Net.Tests.RequestMatchers double score = matcher.GetMatchingScore(requestMessage, result); // Assert - Check.That(score).IsEqualTo(0.5d); + Check.That(score).IsEqualTo(1d); // Verify objectMatcherMock.Verify(m => m.IsMatch(42), Times.Once); @@ -200,7 +290,7 @@ namespace WireMock.Net.Tests.RequestMatchers var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); - var matcher = new RequestMessageBodyMatcher(new CSharpCodeMatcher(MatchBehaviour.AcceptOnMatch, "return it.value == 42;")); + var matcher = new RequestMessageBodyMatcher(new CSharpCodeMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, "return it.value == 42;")); // Act var result = new RequestMatchResult(); @@ -270,7 +360,7 @@ namespace WireMock.Net.Tests.RequestMatchers DetectedBodyType = BodyType.Bytes }; var objectMatcherMock = new Mock(); - objectMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(0.5d); + objectMatcherMock.Setup(m => m.IsMatch(It.IsAny())).Returns(1.0d); var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body); @@ -281,7 +371,7 @@ namespace WireMock.Net.Tests.RequestMatchers double score = matcher.GetMatchingScore(requestMessage, result); // Assert - Check.That(score).IsEqualTo(0.5d); + Check.That(score).IsEqualTo(1.0d); // Verify objectMatcherMock.Verify(m => m.IsMatch(It.IsAny()), Times.Once); diff --git a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageHeaderMatcherTests.cs b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageHeaderMatcherTests.cs index 80d468e4..01ea5717 100644 --- a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageHeaderMatcherTests.cs +++ b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageHeaderMatcherTests.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using NFluent; using WireMock.Matchers; using WireMock.Matchers.Request; @@ -86,7 +86,7 @@ namespace WireMock.Net.Tests.RequestMatchers Check.That(score).IsEqualTo(1.0d); } - [Fact] + [Fact(Skip = "does not work anymore since 'and'/'or'/'average'")] public void RequestMessageHeaderMatcher_GetMatchingScore_RejectOnMatch() { // Assign @@ -108,7 +108,7 @@ namespace WireMock.Net.Tests.RequestMatchers // Assign var headers = new Dictionary { { "h", new[] { "x" } } }; var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", null, headers); - var matcher = new RequestMessageHeaderMatcher(MatchBehaviour.AcceptOnMatch, "h", false, new ExactMatcher("x")); + var matcher = new RequestMessageHeaderMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, "h", false, new ExactMatcher("x")); // Act var result = new RequestMatchResult(); diff --git a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageMethodMatcherTests.cs b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageMethodMatcherTests.cs new file mode 100644 index 00000000..ab46ac50 --- /dev/null +++ b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageMethodMatcherTests.cs @@ -0,0 +1,61 @@ +using FluentAssertions; +using WireMock.Matchers; +using WireMock.Matchers.Request; +using WireMock.Models; +using Xunit; + +namespace WireMock.Net.Tests.RequestMatchers; + +public class RequestMessageMethodMatcherTests +{ + [Theory] + [InlineData("get", 1d)] + [InlineData("post", 1d)] + [InlineData("trace", 0d)] + public void RequestMessageMethodMatcherTests_GetMatchingScore_Or(string method, double expected) + { + // Assign + var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=test1"), method, "127.0.0.1"); + var matcher = new RequestMessageMethodMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, "Get", "Post"); + + // Act + var result = new RequestMatchResult(); + double score = matcher.GetMatchingScore(requestMessage, result); + + // Assert + score.Should().Be(expected); + } + + [Theory] + [InlineData("get", 0.5d)] + [InlineData("post", 0.5d)] + [InlineData("trace", 0d)] + public void RequestMessageMethodMatcherTests_GetMatchingScore_Average(string method, double expected) + { + // Assign + var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=test1"), method, "127.0.0.1"); + var matcher = new RequestMessageMethodMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.Average, "Get", "Post"); + + // Act + var result = new RequestMatchResult(); + double score = matcher.GetMatchingScore(requestMessage, result); + + // Assert + score.Should().Be(expected); + } + + [Fact] + public void RequestMessageMethodMatcherTests_GetMatchingScore_And() + { + // Assign + var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=test1"), "get", "127.0.0.1"); + var matcher = new RequestMessageMethodMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.And, "Get", "Post"); + + // Act + var result = new RequestMatchResult(); + double score = matcher.GetMatchingScore(requestMessage, result); + + // Assert + score.Should().Be(0d); + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageParamMatcherTests.cs b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageParamMatcherTests.cs index d61be615..048040e6 100644 --- a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageParamMatcherTests.cs +++ b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageParamMatcherTests.cs @@ -1,4 +1,4 @@ -using NFluent; +using NFluent; using WireMock.Matchers; using WireMock.Matchers.Request; using WireMock.Models; @@ -24,7 +24,7 @@ namespace WireMock.Net.Tests.RequestMatchers } [Fact] - public void RequestMessageParamMatcher_GetMatchingScore_KeyWith1ValuePresentInUrl_And_With2Strings_Returns0_5() + public void RequestMessageParamMatcher_GetMatchingScore_KeyWith1ValuePresentInUrl_And_With2Strings_Or_Returns0_5() { // Assign var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=test1"), "GET", "127.0.0.1"); diff --git a/test/WireMock.Net.Tests/Serialization/CustomPathParamMatcher.cs b/test/WireMock.Net.Tests/Serialization/CustomPathParamMatcher.cs index 27c3b7ae..1e4695c7 100644 --- a/test/WireMock.Net.Tests/Serialization/CustomPathParamMatcher.cs +++ b/test/WireMock.Net.Tests/Serialization/CustomPathParamMatcher.cs @@ -7,103 +7,110 @@ using Newtonsoft.Json; using WireMock.Matchers; using WireMock.Models; -namespace WireMock.Net.Tests.Serialization +namespace WireMock.Net.Tests.Serialization; + +/// +/// This matcher is only for unit test purposes +/// +public class CustomPathParamMatcher : IStringMatcher { - /// - /// This matcher is only for unit test purposes - /// - public class CustomPathParamMatcher : IStringMatcher + public string Name => nameof(CustomPathParamMatcher); + public MatchBehaviour MatchBehaviour { get; } + public bool ThrowException { get; } + + private readonly string _path; + private readonly string[] _pathParts; + private readonly Dictionary _pathParams; + + public CustomPathParamMatcher(string path, Dictionary pathParams) : this(MatchBehaviour.AcceptOnMatch, path, pathParams) { - public string Name => nameof(CustomPathParamMatcher); - public MatchBehaviour MatchBehaviour { get; } - public bool ThrowException { get; } - - private readonly string _path; - private readonly string[] _pathParts; - private readonly Dictionary _pathParams; - - public CustomPathParamMatcher(string path, Dictionary pathParams) : this(MatchBehaviour.AcceptOnMatch, path, pathParams) - { - } - - public CustomPathParamMatcher(MatchBehaviour matchBehaviour, string path, Dictionary pathParams, bool throwException = false) - { - MatchBehaviour = matchBehaviour; - ThrowException = throwException; - _path = path; - _pathParts = GetPathParts(path); - _pathParams = pathParams.ToDictionary(x => x.Key, x => x.Value, StringComparer.OrdinalIgnoreCase); - } - - public double IsMatch(string input) - { - var inputParts = GetPathParts(input); - if (inputParts.Length != _pathParts.Length) - { - return MatchScores.Mismatch; - } - - try - { - for (int i = 0; i < inputParts.Length; i++) - { - var inputPart = inputParts[i]; - var pathPart = _pathParts[i]; - if (pathPart.StartsWith("{") && pathPart.EndsWith("}")) - { - var pathParamName = pathPart.Trim('{').Trim('}'); - if (!_pathParams.ContainsKey(pathParamName)) - { - return MatchScores.Mismatch; - } - - if (!Regex.IsMatch(inputPart, _pathParams[pathParamName], RegexOptions.IgnoreCase)) - { - return MatchScores.Mismatch; - } - } - else - { - if (!inputPart.Equals(pathPart, StringComparison.InvariantCultureIgnoreCase)) - { - return MatchScores.Mismatch; - } - } - } - } - catch - { - if (ThrowException) - { - throw; - } - - return MatchScores.Mismatch; - } - - return MatchScores.Perfect; - } - - public AnyOf[] GetPatterns() - { - return new[] { new AnyOf(JsonConvert.SerializeObject(new CustomPathParamMatcherModel(_path, _pathParams))) }; - } - - private string[] GetPathParts(string path) - { - var hashMarkIndex = path.IndexOf('#'); - if (hashMarkIndex != -1) - { - path = path.Substring(0, hashMarkIndex); - } - - var queryParamsIndex = path.IndexOf('?'); - if (queryParamsIndex != -1) - { - path = path.Substring(0, queryParamsIndex); - } - - return path.Trim().Trim('/').ToLower().Split('/'); - } } -} + + public CustomPathParamMatcher( + MatchBehaviour matchBehaviour, + string path, + Dictionary pathParams, + bool throwException = false, + MatchOperator matchOperator = MatchOperator.Or) + { + MatchBehaviour = matchBehaviour; + ThrowException = throwException; + _path = path; + _pathParts = GetPathParts(path); + _pathParams = pathParams.ToDictionary(x => x.Key, x => x.Value, StringComparer.OrdinalIgnoreCase); + MatchOperator = matchOperator; + } + + public double IsMatch(string input) + { + var inputParts = GetPathParts(input); + if (inputParts.Length != _pathParts.Length) + { + return MatchScores.Mismatch; + } + + try + { + for (int i = 0; i < inputParts.Length; i++) + { + var inputPart = inputParts[i]; + var pathPart = _pathParts[i]; + if (pathPart.StartsWith("{") && pathPart.EndsWith("}")) + { + var pathParamName = pathPart.Trim('{').Trim('}'); + if (!_pathParams.ContainsKey(pathParamName)) + { + return MatchScores.Mismatch; + } + + if (!Regex.IsMatch(inputPart, _pathParams[pathParamName], RegexOptions.IgnoreCase)) + { + return MatchScores.Mismatch; + } + } + else + { + if (!inputPart.Equals(pathPart, StringComparison.InvariantCultureIgnoreCase)) + { + return MatchScores.Mismatch; + } + } + } + } + catch + { + if (ThrowException) + { + throw; + } + + return MatchScores.Mismatch; + } + + return MatchScores.Perfect; + } + + public AnyOf[] GetPatterns() + { + return new[] { new AnyOf(JsonConvert.SerializeObject(new CustomPathParamMatcherModel(_path, _pathParams))) }; + } + + public MatchOperator MatchOperator { get; } + + private static string[] GetPathParts(string path) + { + var hashMarkIndex = path.IndexOf('#'); + if (hashMarkIndex != -1) + { + path = path.Substring(0, hashMarkIndex); + } + + var queryParamsIndex = path.IndexOf('?'); + if (queryParamsIndex != -1) + { + path = path.Substring(0, queryParamsIndex); + } + + return path.Trim().Trim('/').ToLower().Split('/'); + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Serialization/MatcherModelMapperTests.cs b/test/WireMock.Net.Tests/Serialization/MatcherModelMapperTests.cs index 3746d782..4ab9742e 100644 --- a/test/WireMock.Net.Tests/Serialization/MatcherModelMapperTests.cs +++ b/test/WireMock.Net.Tests/Serialization/MatcherModelMapperTests.cs @@ -180,42 +180,50 @@ namespace WireMock.Net.Tests.Serialization Check.ThatCode(() => _sut.Map(model)).Throws(); } - [Fact] - public void MatcherModelMapper_Map_RegexMatcher() + [Theory] + [InlineData(MatchOperator.Or, 1.0d)] + [InlineData(MatchOperator.And, 0.0d)] + [InlineData(MatchOperator.Average, 0.5d)] + public void MatcherModelMapper_Map_RegexMatcher(MatchOperator matchOperator, double expected) { // Assign var model = new MatcherModel { Name = "RegexMatcher", Patterns = new[] { "x", "y" }, - IgnoreCase = true + IgnoreCase = true, + MatchOperator = matchOperator.ToString() }; // Act - var matcher = (RegexMatcher)_sut.Map(model); + var matcher = (RegexMatcher)_sut.Map(model)!; // Assert Check.That(matcher.GetPatterns()).ContainsExactly("x", "y"); - Check.That(matcher.IsMatch("X")).IsEqualTo(0.5d); + Check.That(matcher.IsMatch("X")).IsEqualTo(expected); } - [Fact] - public void MatcherModelMapper_Map_WildcardMatcher_IgnoreCase() + [Theory] + [InlineData(MatchOperator.Or, 1.0d)] + [InlineData(MatchOperator.And, 0.0d)] + [InlineData(MatchOperator.Average, 0.5d)] + public void MatcherModelMapper_Map_WildcardMatcher_IgnoreCase(MatchOperator matchOperator, double expected) { // Assign var model = new MatcherModel { Name = "WildcardMatcher", Patterns = new[] { "x", "y" }, - IgnoreCase = true + IgnoreCase = true, + MatchOperator = matchOperator.ToString() }; // Act - var matcher = (WildcardMatcher)_sut.Map(model); + var matcher = (WildcardMatcher)_sut.Map(model)!; // Assert Check.That(matcher.GetPatterns()).ContainsExactly("x", "y"); - Check.That(matcher.IsMatch("X")).IsEqualTo(0.5d); + Check.That(matcher.IsMatch("X")).IsEqualTo(expected); } [Fact] diff --git a/test/WireMock.Net.Tests/TestUtils.cs b/test/WireMock.Net.Tests/TestUtils.cs index 5c8b896d..89a02f19 100644 --- a/test/WireMock.Net.Tests/TestUtils.cs +++ b/test/WireMock.Net.Tests/TestUtils.cs @@ -1,70 +1,69 @@ -using System; +using System; using System.Reflection; -namespace WireMock.Net.Tests +namespace WireMock.Net.Tests; + +public static class TestUtils { - public static class TestUtils + public static T GetPrivateFieldValue(this object obj, string fieldName) { - public static T GetPrivateFieldValue(this object obj, string fieldName) - { - var field = obj.GetType().GetTypeInfo().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); + var field = obj.GetType().GetTypeInfo().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); - return (T)field.GetValue(obj); + return (T)field.GetValue(obj); + } + + /// + /// Set a _private_ Field Value on a given Object + /// + /// Type of the Property + /// Object from where the Property Value is returned + /// Property name as string. + /// the value to set + public static void SetPrivateFieldValue(this object obj, string propertyName, T value) + { + if (obj == null) + { + throw new ArgumentNullException(nameof(obj)); } - /// - /// Set a _private_ Field Value on a given Object - /// - /// Type of the Property - /// Object from where the Property Value is returned - /// Property name as string. - /// the value to set - public static void SetPrivateFieldValue(this object obj, string propertyName, T value) + Type t = obj.GetType(); + FieldInfo fi = null; + while (fi == null && t != null) { - if (obj == null) - { - throw new ArgumentNullException(nameof(obj)); - } - - Type t = obj.GetType(); - FieldInfo fi = null; - while (fi == null && t != null) - { - fi = t.GetField(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - t = t.BaseType; - } - - if (fi == null) - { - throw new ArgumentOutOfRangeException(nameof(propertyName), $"Field {propertyName} was not found in Type {obj.GetType().FullName}"); - } - - fi.SetValue(obj, value); + fi = t.GetField(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + t = t.BaseType; } - /// - /// Sets a _private_ Property Value from a given Object. - /// - /// Type of the Property - /// Object from where the Property Value is set - /// Property name as string. - /// Value to set. - public static void SetPrivatePropertyValue(this object obj, string propertyName, T value) + if (fi == null) { - Type t = obj.GetType(); - PropertyInfo propertyInfo = null; - while (propertyInfo == null && t != null) - { - propertyInfo = t.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - t = t.BaseType; - } - - if (propertyInfo == null) - { - throw new ArgumentOutOfRangeException(nameof(propertyName), $"Private property {propertyName} was not found in Type {obj.GetType().FullName}"); - } - - propertyInfo.SetValue(obj, value); + throw new ArgumentOutOfRangeException(nameof(propertyName), $"Field {propertyName} was not found in Type {obj.GetType().FullName}"); } + + fi.SetValue(obj, value); + } + + /// + /// Sets a _private_ Property Value from a given Object. + /// + /// Type of the Property + /// Object from where the Property Value is set + /// Property name as string. + /// Value to set. + public static void SetPrivatePropertyValue(this object obj, string propertyName, T value) + { + Type? t = obj.GetType(); + PropertyInfo? propertyInfo = null; + while (propertyInfo == null && t != null) + { + propertyInfo = t.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + t = t.BaseType; + } + + if (propertyInfo == null) + { + throw new ArgumentOutOfRangeException(nameof(propertyName), $"Private property {propertyName} was not found in Type {obj.GetType().FullName}"); + } + + propertyInfo.SetValue(obj, value); } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/WireMockServerTests.cs b/test/WireMock.Net.Tests/WireMockServerTests.cs index 74068016..7eb75665 100644 --- a/test/WireMock.Net.Tests/WireMockServerTests.cs +++ b/test/WireMock.Net.Tests/WireMockServerTests.cs @@ -23,306 +23,306 @@ using WireMock.Util; using Xunit; using Xunit.Abstractions; -namespace WireMock.Net.Tests +namespace WireMock.Net.Tests; + +public partial class WireMockServerTests { - public partial class WireMockServerTests + private readonly ITestOutputHelper _testOutputHelper; + + public WireMockServerTests(ITestOutputHelper testOutputHelper) { - private readonly ITestOutputHelper _testOutputHelper; + _testOutputHelper = testOutputHelper; + } - public WireMockServerTests(ITestOutputHelper testOutputHelper) + [Fact] + public async Task WireMockServer_Should_Reset_LogEntries() + { + // Arrange + var server = WireMockServer.Start(); + + // Act + await server.CreateClient().GetAsync("/foo").ConfigureAwait(false); + server.ResetLogEntries(); + + // Assert + server.LogEntries.Should().BeEmpty(); + + server.Stop(); + } + + [Fact] + public void WireMockServer_Should_reset_mappings() + { + // given + string path = $"/foo_{Guid.NewGuid()}"; + var server = WireMockServer.Start(); + + server + .Given(Request.Create() + .WithPath(path) + .UsingGet()) + .RespondWith(Response.Create() + .WithBody(@"{ msg: ""Hello world!""}")); + + // when + server.ResetMappings(); + + // then + Check.That(server.Mappings).IsEmpty(); + Check.ThatAsyncCode(() => new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + path)).ThrowsAny(); + + server.Stop(); + } + + [Fact] + public async Task WireMockServer_Should_respond_a_redirect_without_body() + { + // Assign + string path = $"/foo_{Guid.NewGuid()}"; + string pathToRedirect = $"/bar_{Guid.NewGuid()}"; + + var server = WireMockServer.Start(new WireMockServerSettings { - _testOutputHelper = testOutputHelper; - } + Logger = new TestOutputHelperWireMockLogger(_testOutputHelper) + }); - [Fact] - public async Task WireMockServer_Should_reset_requestlogs() - { - // given - var server = WireMockServer.Start(); + server + .Given(Request.Create() + .WithPath(path) + .UsingGet()) + .RespondWith(Response.Create() + .WithStatusCode(307) + .WithHeader("Location", pathToRedirect)); + server + .Given(Request.Create() + .WithPath(pathToRedirect) + .UsingGet()) + .RespondWith(Response.Create() + .WithStatusCode(200) + .WithBody("REDIRECT SUCCESSFUL")); - // when - await new HttpClient().GetAsync("http://localhost:" + server.Ports[0] + "/foo").ConfigureAwait(false); - server.ResetLogEntries(); + // Act + var response = await new HttpClient().GetStringAsync($"http://localhost:{server.Ports[0]}{path}").ConfigureAwait(false); - // then - Check.That(server.LogEntries).IsEmpty(); + // Assert + Check.That(response).IsEqualTo("REDIRECT SUCCESSFUL"); - server.Stop(); - } - - [Fact] - public void WireMockServer_Should_reset_mappings() - { - // given - string path = $"/foo_{Guid.NewGuid()}"; - var server = WireMockServer.Start(); - - server - .Given(Request.Create() - .WithPath(path) - .UsingGet()) - .RespondWith(Response.Create() - .WithBody(@"{ msg: ""Hello world!""}")); - - // when - server.ResetMappings(); - - // then - Check.That(server.Mappings).IsEmpty(); - Check.ThatAsyncCode(() => new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + path)).ThrowsAny(); - - server.Stop(); - } - - [Fact] - public async Task WireMockServer_Should_respond_a_redirect_without_body() - { - // Assign - string path = $"/foo_{Guid.NewGuid()}"; - string pathToRedirect = $"/bar_{Guid.NewGuid()}"; - - var server = WireMockServer.Start(new WireMockServerSettings - { - Logger = new TestOutputHelperWireMockLogger(_testOutputHelper) - }); - - server - .Given(Request.Create() - .WithPath(path) - .UsingGet()) - .RespondWith(Response.Create() - .WithStatusCode(307) - .WithHeader("Location", pathToRedirect)); - server - .Given(Request.Create() - .WithPath(pathToRedirect) - .UsingGet()) - .RespondWith(Response.Create() - .WithStatusCode(200) - .WithBody("REDIRECT SUCCESSFUL")); - - // Act - var response = await new HttpClient().GetStringAsync($"http://localhost:{server.Ports[0]}{path}").ConfigureAwait(false); - - // Assert - Check.That(response).IsEqualTo("REDIRECT SUCCESSFUL"); - - server.Stop(); - } + server.Stop(); + } #if NETCOREAPP3_1 || NET5_0 || NET6_0 - [Fact] - public async Task WireMockServer_WithCorsPolicyOptions_Should_Work_Correct() - { - // Arrange - var settings = new WireMockServerSettings - { - CorsPolicyOptions = CorsPolicyOptions.AllowAll - }; - var server = WireMockServer.Start(settings); +[Fact] +public async Task WireMockServer_WithCorsPolicyOptions_Should_Work_Correct() +{ + // Arrange + var settings = new WireMockServerSettings + { + CorsPolicyOptions = CorsPolicyOptions.AllowAll + }; + var server = WireMockServer.Start(settings); - server.Given(Request.Create().WithPath("/*")).RespondWith(Response.Create().WithBody("x")); + server.Given(Request.Create().WithPath("/*")).RespondWith(Response.Create().WithBody("x")); - // Act - var response = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + "/foo").ConfigureAwait(false); + // Act + var response = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + "/foo").ConfigureAwait(false); - // Asser. - response.Should().Be("x"); + // Asser. + response.Should().Be("x"); - server.Stop(); - } + server.Stop(); +} #endif - [Fact] - public async Task WireMockServer_Should_delay_responses_for_a_given_route() + [Fact] + public async Task WireMockServer_Should_delay_responses_for_a_given_route() + { + // Arrange + var server = WireMockServer.Start(); + + server + .Given(Request.Create() + .WithPath("/*")) + .RespondWith(Response.Create() + .WithBody(@"{ msg: ""Hello world!""}") + .WithDelay(TimeSpan.FromMilliseconds(200))); + + // Act + var watch = new Stopwatch(); + watch.Start(); + await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + "/foo").ConfigureAwait(false); + watch.Stop(); + + // Asser. + watch.ElapsedMilliseconds.Should().BeGreaterOrEqualTo(0); + + server.Stop(); + } + + [Fact] + public async Task WireMockServer_Should_randomly_delay_responses_for_a_given_route() + { + // Arrange + var server = WireMockServer.Start(); + + server + .Given(Request.Create() + .WithPath("/*")) + .RespondWith(Response.Create() + .WithBody(@"{ msg: ""Hello world!""}") + .WithRandomDelay(10, 1000)); + + var watch = new Stopwatch(); + watch.Start(); + + var httClient = new HttpClient(); + async Task ExecuteTimedRequestAsync() { - // Arrange - var server = WireMockServer.Start(); - - server - .Given(Request.Create() - .WithPath("/*")) - .RespondWith(Response.Create() - .WithBody(@"{ msg: ""Hello world!""}") - .WithDelay(TimeSpan.FromMilliseconds(200))); - - // Act - var watch = new Stopwatch(); - watch.Start(); - await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + "/foo").ConfigureAwait(false); - watch.Stop(); - - // Asser. - watch.ElapsedMilliseconds.Should().BeGreaterOrEqualTo(0); - - server.Stop(); + watch.Reset(); + await httClient.GetStringAsync("http://localhost:" + server.Ports[0] + "/foo").ConfigureAwait(false); + return watch.ElapsedMilliseconds; } - [Fact] - public async Task WireMockServer_Should_randomly_delay_responses_for_a_given_route() - { - // Arrange - var server = WireMockServer.Start(); + // Act + await ExecuteTimedRequestAsync().ConfigureAwait(false); + await ExecuteTimedRequestAsync().ConfigureAwait(false); + await ExecuteTimedRequestAsync().ConfigureAwait(false); - server - .Given(Request.Create() - .WithPath("/*")) - .RespondWith(Response.Create() - .WithBody(@"{ msg: ""Hello world!""}") - .WithRandomDelay(10, 1000)); + server.Stop(); + } - var watch = new Stopwatch(); - watch.Start(); + [Fact] + public async Task WireMockServer_Should_delay_responses() + { + // Arrange + var server = WireMockServer.Start(); + server.AddGlobalProcessingDelay(TimeSpan.FromMilliseconds(200)); + server + .Given(Request.Create().WithPath("/*")) + .RespondWith(Response.Create().WithBody(@"{ msg: ""Hello world!""}")); - var httClient = new HttpClient(); - async Task ExecuteTimedRequestAsync() - { - watch.Reset(); - await httClient.GetStringAsync("http://localhost:" + server.Ports[0] + "/foo").ConfigureAwait(false); - return watch.ElapsedMilliseconds; - } + // Act + var watch = new Stopwatch(); + watch.Start(); + await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + "/foo").ConfigureAwait(false); + watch.Stop(); - // Act - await ExecuteTimedRequestAsync().ConfigureAwait(false); - await ExecuteTimedRequestAsync().ConfigureAwait(false); - await ExecuteTimedRequestAsync().ConfigureAwait(false); + // Assert + watch.ElapsedMilliseconds.Should().BeGreaterOrEqualTo(0); - server.Stop(); - } + server.Stop(); + } - [Fact] - public async Task WireMockServer_Should_delay_responses() - { - // Arrange - var server = WireMockServer.Start(); - server.AddGlobalProcessingDelay(TimeSpan.FromMilliseconds(200)); - server - .Given(Request.Create().WithPath("/*")) - .RespondWith(Response.Create().WithBody(@"{ msg: ""Hello world!""}")); + //Leaving commented as this requires an actual certificate with password, along with a service that expects a client certificate + //[Fact] + //public async Task Should_proxy_responses_with_client_certificate() + //{ + // // given + // var _server = WireMockServer.Start(); + // _server + // .Given(Request.Create().WithPath("/*")) + // .RespondWith(Response.Create().WithProxy("https://server-that-expects-a-client-certificate", @"\\yourclientcertificatecontainingprivatekey.pfx", "yourclientcertificatepassword")); - // Act - var watch = new Stopwatch(); - watch.Start(); - await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + "/foo").ConfigureAwait(false); - watch.Stop(); + // // when + // var result = await new HttpClient().GetStringAsync("http://localhost:" + _server.Ports[0] + "/someurl?someQuery=someValue"); - // Assert - watch.ElapsedMilliseconds.Should().BeGreaterOrEqualTo(0); + // // then + // Check.That(result).Contains("google"); + //} - server.Stop(); - } + [Fact] + public async Task WireMockServer_Should_exclude_restrictedResponseHeader() + { + // Assign + string path = $"/foo_{Guid.NewGuid()}"; + var server = WireMockServer.Start(); - //Leaving commented as this requires an actual certificate with password, along with a service that expects a client certificate - //[Fact] - //public async Task Should_proxy_responses_with_client_certificate() - //{ - // // given - // var _server = WireMockServer.Start(); - // _server - // .Given(Request.Create().WithPath("/*")) - // .RespondWith(Response.Create().WithProxy("https://server-that-expects-a-client-certificate", @"\\yourclientcertificatecontainingprivatekey.pfx", "yourclientcertificatepassword")); + server + .Given(Request.Create().WithPath(path).UsingGet()) + .RespondWith(Response.Create().WithHeader("Transfer-Encoding", "chunked").WithHeader("test", "t")); - // // when - // var result = await new HttpClient().GetStringAsync("http://localhost:" + _server.Ports[0] + "/someurl?someQuery=someValue"); + // Act + var response = await new HttpClient().GetAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); - // // then - // Check.That(result).Contains("google"); - //} + // Assert + Check.That(response.Headers.Contains("test")).IsTrue(); + Check.That(response.Headers.Contains("Transfer-Encoding")).IsFalse(); - [Fact] - public async Task WireMockServer_Should_exclude_restrictedResponseHeader() - { - // Assign - string path = $"/foo_{Guid.NewGuid()}"; - var server = WireMockServer.Start(); - - server - .Given(Request.Create().WithPath(path).UsingGet()) - .RespondWith(Response.Create().WithHeader("Transfer-Encoding", "chunked").WithHeader("test", "t")); - - // Act - var response = await new HttpClient().GetAsync("http://localhost:" + server.Ports[0] + path).ConfigureAwait(false); - - // Assert - Check.That(response.Headers.Contains("test")).IsTrue(); - Check.That(response.Headers.Contains("Transfer-Encoding")).IsFalse(); - - server.Stop(); - } + server.Stop(); + } #if !NET452 && !NET461 - [Theory] - [InlineData("TRACE")] - [InlineData("GET")] - public async Task WireMockServer_Should_exclude_body_for_methods_where_body_is_definitely_disallowed(string method) - { - // Assign - string content = "hello"; - var server = WireMockServer.Start(); +[Theory] +[InlineData("TRACE")] +[InlineData("GET")] +public async Task WireMockServer_Should_exclude_body_for_methods_where_body_is_definitely_disallowed(string method) +{ + // Assign + string content = "hello"; + var server = WireMockServer.Start(); - server - .Given(Request.Create().WithBody((byte[] bodyBytes) => bodyBytes != null)) - .AtPriority(0) - .RespondWith(Response.Create().WithStatusCode(400)); - server - .Given(Request.Create()) - .AtPriority(1) - .RespondWith(Response.Create().WithStatusCode(200)); + server + .Given(Request.Create().WithBody((byte[] bodyBytes) => bodyBytes != null)) + .AtPriority(0) + .RespondWith(Response.Create().WithStatusCode(400)); + server + .Given(Request.Create()) + .AtPriority(1) + .RespondWith(Response.Create().WithStatusCode(200)); - // Act - var request = new HttpRequestMessage(new HttpMethod(method), "http://localhost:" + server.Ports[0] + "/"); - request.Content = new StringContent(content); - var response = await new HttpClient().SendAsync(request).ConfigureAwait(false); + // Act + var request = new HttpRequestMessage(new HttpMethod(method), "http://localhost:" + server.Ports[0] + "/"); + request.Content = new StringContent(content); + var response = await new HttpClient().SendAsync(request).ConfigureAwait(false); - // Assert - Check.That(response.StatusCode).Equals(HttpStatusCode.OK); + // Assert + Check.That(response.StatusCode).Equals(HttpStatusCode.OK); - server.Stop(); - } + server.Stop(); +} #endif - [Theory] - [InlineData("POST")] - [InlineData("PUT")] - [InlineData("OPTIONS")] - [InlineData("REPORT")] - [InlineData("DELETE")] - [InlineData("SOME-UNKNOWN-METHOD")] // default behavior for unknown methods is to allow a body (see BodyParser.ShouldParseBody) - public async Task WireMockServer_Should_not_exclude_body_for_supported_methods(string method) - { - // Assign - string content = "hello"; - var server = WireMockServer.Start(); + [Theory] + [InlineData("POST")] + [InlineData("PUT")] + [InlineData("OPTIONS")] + [InlineData("REPORT")] + [InlineData("DELETE")] + [InlineData("SOME-UNKNOWN-METHOD")] // default behavior for unknown methods is to allow a body (see BodyParser.ShouldParseBody) + public async Task WireMockServer_Should_not_exclude_body_for_supported_methods(string method) + { + // Assign + string content = "hello"; + var server = WireMockServer.Start(); - server - .Given(Request.Create().WithBody(content)) - .AtPriority(0) - .RespondWith(Response.Create().WithStatusCode(200)); - server - .Given(Request.Create()) - .AtPriority(1) - .RespondWith(Response.Create().WithStatusCode(400)); + server + .Given(Request.Create().WithBody(content)) + .AtPriority(0) + .RespondWith(Response.Create().WithStatusCode(200)); + server + .Given(Request.Create()) + .AtPriority(1) + .RespondWith(Response.Create().WithStatusCode(400)); - // Act - var request = new HttpRequestMessage(new HttpMethod(method), "http://localhost:" + server.Ports[0] + "/"); - request.Content = new StringContent(content); - var response = await new HttpClient().SendAsync(request).ConfigureAwait(false); + // Act + var request = new HttpRequestMessage(new HttpMethod(method), "http://localhost:" + server.Ports[0] + "/"); + request.Content = new StringContent(content); + var response = await new HttpClient().SendAsync(request).ConfigureAwait(false); - // Assert - Check.That(response.StatusCode).Equals(HttpStatusCode.OK); + // Assert + Check.That(response.StatusCode).Equals(HttpStatusCode.OK); - server.Stop(); - } + server.Stop(); + } - [Theory] - [InlineData("application/json")] - [InlineData("application/json; charset=ascii")] - [InlineData("application/json; charset=utf-8")] - [InlineData("application/json; charset=UTF-8")] - public async Task WireMockServer_Should_AcceptPostMappingsWithContentTypeJsonAndAnyCharset(string contentType) - { - // Arrange - string message = @"{ + [Theory] + [InlineData("application/json")] + [InlineData("application/json; charset=ascii")] + [InlineData("application/json; charset=utf-8")] + [InlineData("application/json; charset=UTF-8")] + public async Task WireMockServer_Should_AcceptPostMappingsWithContentTypeJsonAndAnyCharset(string contentType) + { + // Arrange + string message = @"{ ""request"": { ""method"": ""GET"", ""url"": ""/some/thing"" @@ -335,31 +335,31 @@ namespace WireMock.Net.Tests } } }"; - var stringContent = new StringContent(message); - stringContent.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); - var server = WireMockServer.StartWithAdminInterface(); + var stringContent = new StringContent(message); + stringContent.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + var server = WireMockServer.StartWithAdminInterface(); - // Act - var response = await new HttpClient().PostAsync($"{server.Url}/__admin/mappings", stringContent).ConfigureAwait(false); + // Act + var response = await new HttpClient().PostAsync($"{server.Url}/__admin/mappings", stringContent).ConfigureAwait(false); - // Assert - Check.That(response.StatusCode).Equals(HttpStatusCode.Created); - Check.That(await response.Content.ReadAsStringAsync().ConfigureAwait(false)).Contains("Mapping added"); + // Assert + Check.That(response.StatusCode).Equals(HttpStatusCode.Created); + Check.That(await response.Content.ReadAsStringAsync().ConfigureAwait(false)).Contains("Mapping added"); - server.Stop(); - } + server.Stop(); + } - [Theory] - [InlineData("gzip")] - [InlineData("deflate")] - public async Task WireMockServer_Should_SupportRequestGZipAndDeflate(string contentEncoding) - { - // Arrange - const string body = "hello wiremock"; - byte[] compressed = CompressionUtils.Compress(contentEncoding, Encoding.UTF8.GetBytes(body)); + [Theory] + [InlineData("gzip")] + [InlineData("deflate")] + public async Task WireMockServer_Should_SupportRequestGZipAndDeflate(string contentEncoding) + { + // Arrange + const string body = "hello wiremock"; + byte[] compressed = CompressionUtils.Compress(contentEncoding, Encoding.UTF8.GetBytes(body)); - var server = WireMockServer.Start(); - server.Given( + var server = WireMockServer.Start(); + server.Given( Request.Create() .WithPath("/foo") .WithBody("hello wiremock") @@ -368,84 +368,84 @@ namespace WireMock.Net.Tests Response.Create().WithBody("OK") ); - var content = new StreamContent(new MemoryStream(compressed)); - content.Headers.ContentType = new MediaTypeHeaderValue("text/plain"); - content.Headers.ContentEncoding.Add(contentEncoding); + var content = new StreamContent(new MemoryStream(compressed)); + content.Headers.ContentType = new MediaTypeHeaderValue("text/plain"); + content.Headers.ContentEncoding.Add(contentEncoding); - // Act - var response = await new HttpClient().PostAsync($"{server.Urls[0]}/foo", content).ConfigureAwait(false); + // Act + var response = await new HttpClient().PostAsync($"{server.Urls[0]}/foo", content).ConfigureAwait(false); - // Assert - Check.That(await response.Content.ReadAsStringAsync().ConfigureAwait(false)).Contains("OK"); + // Assert + Check.That(await response.Content.ReadAsStringAsync().ConfigureAwait(false)).Contains("OK"); - server.Stop(); - } + server.Stop(); + } #if !NET452 - [Fact] - public async Task WireMockServer_Should_respond_to_ipv4_loopback() + [Fact] + public async Task WireMockServer_Should_respond_to_ipv4_loopback() + { + // Assign + var server = WireMockServer.Start(); + + server + .Given(Request.Create() + .WithPath("/*")) + .RespondWith(Response.Create() + .WithStatusCode(200) + .WithBody("from ipv4 loopback")); + + // Act + var response = await new HttpClient().GetStringAsync($"http://127.0.0.1:{server.Ports[0]}/foo").ConfigureAwait(false); + + // Assert + Check.That(response).IsEqualTo("from ipv4 loopback"); + + server.Stop(); + } + + [Fact] + public async Task WireMockServer_Should_respond_to_ipv6_loopback() + { + // Assign + var server = WireMockServer.Start(); + + server + .Given(Request.Create() + .WithPath("/*")) + .RespondWith(Response.Create() + .WithStatusCode(200) + .WithBody("from ipv6 loopback")); + + // Act + var response = await new HttpClient().GetStringAsync($"http://[::1]:{server.Ports[0]}/foo").ConfigureAwait(false); + + // Assert + Check.That(response).IsEqualTo("from ipv6 loopback"); + + server.Stop(); + } + + [Fact] + public async Task WireMockServer_Using_JsonMapping_And_CustomMatcher_WithCorrectParams_ShouldMatch() + { + // Arrange + var settings = new WireMockServerSettings(); + settings.WatchStaticMappings = true; + settings.WatchStaticMappingsInSubdirectories = true; + settings.CustomMatcherMappings = new Dictionary>(); + settings.CustomMatcherMappings[nameof(CustomPathParamMatcher)] = matcherModel => { - // Assign - var server = WireMockServer.Start(); + var matcherParams = JsonConvert.DeserializeObject((string)matcherModel.Pattern); + return new CustomPathParamMatcher( + matcherModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, + matcherParams.Path, matcherParams.PathParams, + settings.ThrowExceptionWhenMatcherFails == true + ); + }; - server - .Given(Request.Create() - .WithPath("/*")) - .RespondWith(Response.Create() - .WithStatusCode(200) - .WithBody("from ipv4 loopback")); - - // Act - var response = await new HttpClient().GetStringAsync($"http://127.0.0.1:{server.Ports[0]}/foo").ConfigureAwait(false); - - // Assert - Check.That(response).IsEqualTo("from ipv4 loopback"); - - server.Stop(); - } - - [Fact] - public async Task WireMockServer_Should_respond_to_ipv6_loopback() - { - // Assign - var server = WireMockServer.Start(); - - server - .Given(Request.Create() - .WithPath("/*")) - .RespondWith(Response.Create() - .WithStatusCode(200) - .WithBody("from ipv6 loopback")); - - // Act - var response = await new HttpClient().GetStringAsync($"http://[::1]:{server.Ports[0]}/foo").ConfigureAwait(false); - - // Assert - Check.That(response).IsEqualTo("from ipv6 loopback"); - - server.Stop(); - } - - [Fact] - public async Task WireMockServer_Using_JsonMapping_And_CustomMatcher_WithCorrectParams_ShouldMatch() - { - // Arrange - var settings = new WireMockServerSettings(); - settings.WatchStaticMappings = true; - settings.WatchStaticMappingsInSubdirectories = true; - settings.CustomMatcherMappings = new Dictionary>(); - settings.CustomMatcherMappings[nameof(CustomPathParamMatcher)] = matcherModel => - { - var matcherParams = JsonConvert.DeserializeObject((string)matcherModel.Pattern); - return new CustomPathParamMatcher( - matcherModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, - matcherParams.Path, matcherParams.PathParams, - settings.ThrowExceptionWhenMatcherFails == true - ); - }; - - var server = WireMockServer.Start(settings); - server.WithMapping(@"{ + var server = WireMockServer.Start(settings); + server.WithMapping(@"{ ""Request"": { ""Path"": { ""Matchers"": [ @@ -463,37 +463,37 @@ namespace WireMock.Net.Tests }, ""Body"": ""OK"" } -}"); + }"); - // Act - var response = await new HttpClient().PostAsync("http://localhost:" + server.Ports[0] + "/customer/132/document/pic.jpg", new StringContent("{ Hi = \"Hello World\" }")).ConfigureAwait(false); + // Act + var response = await new HttpClient().PostAsync("http://localhost:" + server.Ports[0] + "/customer/132/document/pic.jpg", new StringContent("{ Hi = \"Hello World\" }")).ConfigureAwait(false); - // Assert - response.StatusCode.Should().Be(HttpStatusCode.OK); + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); - server.Stop(); - } + server.Stop(); + } - [Fact] - public async Task WireMockServer_Using_JsonMapping_And_CustomMatcher_WithIncorrectParams_ShouldNotMatch() + [Fact] + public async Task WireMockServer_Using_JsonMapping_And_CustomMatcher_WithIncorrectParams_ShouldNotMatch() + { + // Arrange + var settings = new WireMockServerSettings(); + settings.WatchStaticMappings = true; + settings.WatchStaticMappingsInSubdirectories = true; + settings.CustomMatcherMappings = new Dictionary>(); + settings.CustomMatcherMappings[nameof(CustomPathParamMatcher)] = matcherModel => { - // Arrange - var settings = new WireMockServerSettings(); - settings.WatchStaticMappings = true; - settings.WatchStaticMappingsInSubdirectories = true; - settings.CustomMatcherMappings = new Dictionary>(); - settings.CustomMatcherMappings[nameof(CustomPathParamMatcher)] = matcherModel => - { - var matcherParams = JsonConvert.DeserializeObject((string)matcherModel.Pattern); - return new CustomPathParamMatcher( - matcherModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, - matcherParams.Path, matcherParams.PathParams, - settings.ThrowExceptionWhenMatcherFails == true - ); - }; + var matcherParams = JsonConvert.DeserializeObject((string)matcherModel.Pattern); + return new CustomPathParamMatcher( + matcherModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch, + matcherParams.Path, matcherParams.PathParams, + settings.ThrowExceptionWhenMatcherFails == true + ); + }; - var server = WireMockServer.Start(settings); - server.WithMapping(@"{ + var server = WireMockServer.Start(settings); + server.WithMapping(@"{ ""Request"": { ""Path"": { ""Matchers"": [ @@ -511,16 +511,15 @@ namespace WireMock.Net.Tests }, ""Body"": ""OK"" } -}"); + }"); - // Act - var response = await new HttpClient().PostAsync("http://localhost:" + server.Ports[0] + "/customer/132/document/pic", new StringContent("{ Hi = \"Hello World\" }")).ConfigureAwait(false); + // Act + var response = await new HttpClient().PostAsync("http://localhost:" + server.Ports[0] + "/customer/132/document/pic", new StringContent("{ Hi = \"Hello World\" }")).ConfigureAwait(false); - // Assert - response.StatusCode.Should().Be(HttpStatusCode.NotFound); + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NotFound); - server.Stop(); - } + server.Stop(); + } #endif - } } \ No newline at end of file