From 1392119f9dc7207daf56a52d23f7034d3f3e1b57 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 12 Mar 2019 14:42:52 +0100 Subject: [PATCH] RequestMessageParamMatcher supports Ignore Case for the key (#254) * RequestMessageParamMatcher * /o: * 1.0.8 --- Directory.Build.props | 2 +- azure-pipelines.yml | 2 +- src/WireMock.Net/Admin/Mappings/ParamModel.cs | 5 ++ .../Request/RequestMessageParamMatcher.cs | 17 +++- .../RequestBuilders/IParamsRequestBuilder.cs | 51 ++++++++++- .../RequestBuilders/Request.Params.cs | 89 +++++++++++++++++++ src/WireMock.Net/RequestBuilders/Request.cs | 50 +---------- src/WireMock.Net/RequestMessage.cs | 11 ++- .../Serialization/MappingConverter.cs | 1 + .../Server/FluentMockServer.Admin.cs | 5 +- .../RequestMessageParamMatcherTests.cs | 29 ++++-- .../WireMock.Net.Tests/RequestMessageTests.cs | 10 +++ 12 files changed, 202 insertions(+), 70 deletions(-) create mode 100644 src/WireMock.Net/RequestBuilders/Request.Params.cs diff --git a/Directory.Build.props b/Directory.Build.props index 5438effc..1efbb2df 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 1.0.7 + 1.0.8 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ac816e65..7a56d892 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -24,7 +24,7 @@ steps: # - https://github.com/Microsoft/vsts-tasks/issues/8291 # - script: | - %USERPROFILE%\.dotnet\tools\dotnet-sonarscanner begin /k:"wiremock" /d:sonar.organization="stefh-github" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.login="$(SONAR_TOKEN)" /v:"$(buildId)" /d:sonar.cs.opencover.reportsPaths="**\coverage.opencover.xml" + %USERPROFILE%\.dotnet\tools\dotnet-sonarscanner begin /k:"wiremock" /o:"stefh-github" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.login="$(SONAR_TOKEN)" /v:"$(buildId)" /d:sonar.cs.opencover.reportsPaths="**\coverage.opencover.xml" displayName: Begin SonarScanner # Build source, tests and run tests for net452 and netcoreapp2.1 (with coverage) diff --git a/src/WireMock.Net/Admin/Mappings/ParamModel.cs b/src/WireMock.Net/Admin/Mappings/ParamModel.cs index 8b4ca0b1..90d263a5 100644 --- a/src/WireMock.Net/Admin/Mappings/ParamModel.cs +++ b/src/WireMock.Net/Admin/Mappings/ParamModel.cs @@ -10,6 +10,11 @@ /// public string Name { get; set; } + /// + /// Defines if the key should be matched using case-ignore. + /// + public bool? IgnoreCase { get; set; } + /// /// Gets or sets the matchers. /// diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs index 47b4fe74..e74e9303 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs @@ -24,6 +24,11 @@ namespace WireMock.Matchers.Request /// public string Key { get; } + /// + /// Defines if the key should be matched using case-ignore. + /// + public bool? IgnoreCase { get; private set; } + /// /// The matchers. /// @@ -34,7 +39,8 @@ namespace WireMock.Matchers.Request /// /// The match behaviour. /// The key. - public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, [NotNull] string key) : this(matchBehaviour, key, (IStringMatcher[])null) + /// 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) { } @@ -43,8 +49,9 @@ namespace WireMock.Matchers.Request /// /// The match behaviour. /// The key. + /// Defines if the key should be matched using case-ignore. /// The values. - public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, [NotNull] string key, [CanBeNull] string[] values) : this(matchBehaviour, key, values?.Select(value => new ExactMatcher(matchBehaviour, value)).Cast().ToArray()) + public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, [NotNull] string key, bool ignoreCase, [CanBeNull] string[] values) : this(matchBehaviour, key, ignoreCase, values?.Select(value => new ExactMatcher(matchBehaviour, value)).Cast().ToArray()) { } @@ -53,13 +60,15 @@ namespace WireMock.Matchers.Request /// /// The match behaviour. /// The key. + /// Defines if the key should be matched using case-ignore. /// The matchers. - public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, [NotNull] string key, [CanBeNull] IStringMatcher[] matchers) + public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, [NotNull] string key, bool ignoreCase, [CanBeNull] IStringMatcher[] matchers) { Check.NotNull(key, nameof(key)); _matchBehaviour = matchBehaviour; Key = key; + IgnoreCase = ignoreCase; Matchers = matchers; } @@ -88,7 +97,7 @@ namespace WireMock.Matchers.Request return MatchScores.ToScore(requestMessage.Query != null && Funcs.Any(f => f(requestMessage.Query))); } - WireMockList valuesPresentInRequestMessage = requestMessage.GetParameter(Key); + WireMockList valuesPresentInRequestMessage = requestMessage.GetParameter(Key, IgnoreCase ?? false); if (valuesPresentInRequestMessage == null) { // Key is not present at all, just return Mismatch diff --git a/src/WireMock.Net/RequestBuilders/IParamsRequestBuilder.cs b/src/WireMock.Net/RequestBuilders/IParamsRequestBuilder.cs index ddb21610..06bb0a3b 100644 --- a/src/WireMock.Net/RequestBuilders/IParamsRequestBuilder.cs +++ b/src/WireMock.Net/RequestBuilders/IParamsRequestBuilder.cs @@ -1,6 +1,6 @@ -using System; +using JetBrains.Annotations; +using System; using System.Collections.Generic; -using JetBrains.Annotations; using WireMock.Matchers; using WireMock.Util; @@ -19,6 +19,15 @@ namespace WireMock.RequestBuilders /// The . IRequestBuilder WithParam([NotNull] 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 and values. /// @@ -27,6 +36,15 @@ namespace WireMock.RequestBuilders /// The . IRequestBuilder WithParam([NotNull] string key, [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([NotNull] string key, bool ignoreCase, [CanBeNull] params string[] values); + /// /// WithParam: matching on key and matchers. /// @@ -35,6 +53,15 @@ namespace WireMock.RequestBuilders /// The . IRequestBuilder WithParam([NotNull] string key, [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([NotNull] string key, bool ignoreCase, [CanBeNull] params IStringMatcher[] matchers); + /// /// WithParam: matching on key, values and matchBehaviour. /// @@ -44,6 +71,16 @@ namespace WireMock.RequestBuilders /// The . IRequestBuilder WithParam([NotNull] string key, MatchBehaviour matchBehaviour, [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([NotNull] string key, MatchBehaviour matchBehaviour, bool ignoreCase = false, [CanBeNull] params string[] values); + /// /// WithParam: matching on key, matchers and matchBehaviour. /// @@ -53,6 +90,16 @@ namespace WireMock.RequestBuilders /// The . IRequestBuilder WithParam([NotNull] string key, MatchBehaviour matchBehaviour, [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([NotNull] string key, MatchBehaviour matchBehaviour, bool ignoreCase = false, [CanBeNull] params IStringMatcher[] matchers); + /// /// WithParam: matching on functions. /// diff --git a/src/WireMock.Net/RequestBuilders/Request.Params.cs b/src/WireMock.Net/RequestBuilders/Request.Params.cs new file mode 100644 index 00000000..94dcd6bd --- /dev/null +++ b/src/WireMock.Net/RequestBuilders/Request.Params.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using WireMock.Matchers; +using WireMock.Matchers.Request; +using WireMock.Util; +using WireMock.Validation; + +namespace WireMock.RequestBuilders +{ + public partial class Request + { + /// + public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + return WithParam(key, false, matchBehaviour); + } + + /// + public IRequestBuilder WithParam(string key, bool ignoreCase, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + Check.NotNull(key, nameof(key)); + + _requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase)); + return this; + } + + /// + public IRequestBuilder WithParam(string key, params string[] values) + { + return WithParam(key, MatchBehaviour.AcceptOnMatch, false, values); + } + + /// + public IRequestBuilder WithParam(string key, bool ignoreCase, params string[] values) + { + return WithParam(key, MatchBehaviour.AcceptOnMatch, ignoreCase, values); + } + + /// + public IRequestBuilder WithParam(string key, params IStringMatcher[] matchers) + { + return WithParam(key, MatchBehaviour.AcceptOnMatch, false, matchers); + } + + /// + public IRequestBuilder WithParam(string key, bool ignoreCase, params IStringMatcher[] matchers) + { + return WithParam(key, MatchBehaviour.AcceptOnMatch, ignoreCase, matchers); + } + + /// + public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, params string[] values) + { + return WithParam(key, matchBehaviour, false, values); + } + + /// + public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, bool ignoreCase = false, params string[] values) + { + Check.NotNull(key, nameof(key)); + + _requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase, values)); + return this; + } + + public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, params IStringMatcher[] matchers) + { + return WithParam(key, matchBehaviour, false, matchers); + } + + /// + public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, bool ignoreCase, params IStringMatcher[] matchers) + { + Check.NotNull(key, nameof(key)); + + _requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase, matchers)); + return this; + } + + /// + public IRequestBuilder WithParam(params Func>, bool>[] funcs) + { + Check.NotNullOrEmpty(funcs, nameof(funcs)); + + _requestMatchers.Add(new RequestMessageParamMatcher(funcs)); + return this; + } + } +} \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/Request.cs b/src/WireMock.Net/RequestBuilders/Request.cs index cd32f121..054f8d2d 100644 --- a/src/WireMock.Net/RequestBuilders/Request.cs +++ b/src/WireMock.Net/RequestBuilders/Request.cs @@ -12,7 +12,7 @@ namespace WireMock.RequestBuilders /// /// The requests. /// - public class Request : RequestMessageCompositeMatcher, IRequestBuilder + public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder { private readonly IList _requestMatchers; @@ -291,54 +291,6 @@ namespace WireMock.RequestBuilders return this; } - /// - public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - Check.NotNull(key, nameof(key)); - - _requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key)); - return this; - } - - /// - public IRequestBuilder WithParam(string key, params string[] values) - { - return WithParam(key, MatchBehaviour.AcceptOnMatch, values); - } - - /// - public IRequestBuilder WithParam(string key, params IStringMatcher[] matchers) - { - return WithParam(key, MatchBehaviour.AcceptOnMatch, matchers); - } - - /// - public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, params string[] values) - { - Check.NotNull(key, nameof(key)); - - _requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, values)); - return this; - } - - /// - public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, params IStringMatcher[] matchers) - { - Check.NotNull(key, nameof(key)); - - _requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, matchers)); - return this; - } - - /// - public IRequestBuilder WithParam(params Func>, bool>[] funcs) - { - Check.NotNullOrEmpty(funcs, nameof(funcs)); - - _requestMatchers.Add(new RequestMessageParamMatcher(funcs)); - return this; - } - /// public IRequestBuilder WithHeader(string name, string pattern, MatchBehaviour matchBehaviour) { diff --git a/src/WireMock.Net/RequestMessage.cs b/src/WireMock.Net/RequestMessage.cs index 5a7262fe..46583b43 100644 --- a/src/WireMock.Net/RequestMessage.cs +++ b/src/WireMock.Net/RequestMessage.cs @@ -1,8 +1,8 @@ -using System; +using JetBrains.Annotations; +using System; using System.Collections.Generic; using System.Linq; using System.Net; -using JetBrains.Annotations; using WireMock.Models; using WireMock.Util; using WireMock.Validation; @@ -211,15 +211,18 @@ namespace WireMock /// Get a query parameter. /// /// The key. + /// Defines if the key should be matched using case-ignore. /// The query parameter. - public WireMockList GetParameter(string key) + public WireMockList GetParameter(string key, bool ignoreCase = false) { if (Query == null) { return null; } - return Query.ContainsKey(key) ? Query[key] : null; + var query = !ignoreCase ? Query : new Dictionary>(Query, StringComparer.OrdinalIgnoreCase); + + return query.ContainsKey(key) ? query[key] : null; } } } \ No newline at end of file diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs index 46b5af01..0ad9f9ec 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net/Serialization/MappingConverter.cs @@ -66,6 +66,7 @@ namespace WireMock.Serialization Params = paramsMatchers != null && paramsMatchers.Any() ? paramsMatchers.Select(pm => new ParamModel { Name = pm.Key, + IgnoreCase = pm.IgnoreCase, Matchers = MatcherMapper.Map(pm.Matchers) }).ToList() : null, diff --git a/src/WireMock.Net/Server/FluentMockServer.Admin.cs b/src/WireMock.Net/Server/FluentMockServer.Admin.cs index e7a8011d..84fa0057 100644 --- a/src/WireMock.Net/Server/FluentMockServer.Admin.cs +++ b/src/WireMock.Net/Server/FluentMockServer.Admin.cs @@ -265,7 +265,7 @@ namespace WireMock.Server request.WithPath(requestMessage.Path); request.UsingMethod(requestMessage.Method); - requestMessage.Query.Loop((key, value) => request.WithParam(key, value.ToArray())); + requestMessage.Query.Loop((key, value) => request.WithParam(key, false, value.ToArray())); requestMessage.Cookies.Loop((key, value) => request.WithCookie(key, value)); var allBlackListedHeaders = new List(blacklistedHeaders) { "Cookie" }; @@ -685,7 +685,8 @@ namespace WireMock.Server { foreach (var paramModel in requestModel.Params.Where(c => c.Matchers != null)) { - requestBuilder = requestBuilder.WithParam(paramModel.Name, paramModel.Matchers.Select(MatcherMapper.Map).Cast().ToArray()); + bool ignoreCase = paramModel?.IgnoreCase ?? false; + requestBuilder = requestBuilder.WithParam(paramModel.Name, ignoreCase, paramModel.Matchers.Select(MatcherMapper.Map).Cast().ToArray()); } } diff --git a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageParamMatcherTests.cs b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageParamMatcherTests.cs index 78585e4c..4e73db18 100644 --- a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageParamMatcherTests.cs +++ b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageParamMatcherTests.cs @@ -8,12 +8,27 @@ namespace WireMock.Net.Tests.RequestMatchers { public class RequestMessageParamMatcherTests { + [Fact] + public void RequestMessageParamMatcher_GetMatchingScore_IgnoreCaseKeyWithValuesPresentInUrl_MatchExactOnStringValues() + { + // Assign + var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=test1"), "GET", "127.0.0.1"); + var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "KeY", true, new[] { "test1" }); + + // Act + var result = new RequestMatchResult(); + double score = matcher.GetMatchingScore(requestMessage, result); + + // Assert + Check.That(score).IsEqualTo(1.0d); + } + [Fact] public void RequestMessageParamMatcher_GetMatchingScore_KeyWithValuesPresentInUrl_MatchExactOnStringValues() { // Assign var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=test1,test2"), "GET", "127.0.0.1"); - var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", new[] { "test1", "test2" }); + var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, new[] { "test1", "test2" }); // Act var result = new RequestMatchResult(); @@ -28,7 +43,7 @@ namespace WireMock.Net.Tests.RequestMatchers { // Assign var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=test1,test2"), "GET", "127.0.0.1"); - var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", new IStringMatcher[] { new ExactMatcher("test1"), new ExactMatcher("test2") }); + var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, new IStringMatcher[] { new ExactMatcher("test1"), new ExactMatcher("test2") }); // Act var result = new RequestMatchResult(); @@ -43,7 +58,7 @@ namespace WireMock.Net.Tests.RequestMatchers { // Assign var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=test0,test2"), "GET", "127.0.0.1"); - var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", new[] { "test1", "test2" }); + var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, new[] { "test1", "test2" }); // Act var result = new RequestMatchResult(); @@ -58,7 +73,7 @@ namespace WireMock.Net.Tests.RequestMatchers { // Assign var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key"), "GET", "127.0.0.1"); - var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", new[] { "test1", "test2" }); + var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, new[] { "test1", "test2" }); // Act var result = new RequestMatchResult(); @@ -73,7 +88,7 @@ namespace WireMock.Net.Tests.RequestMatchers { // Assign var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key"), "GET", "127.0.0.1"); - var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key"); + var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false); // Act var result = new RequestMatchResult(); @@ -88,7 +103,7 @@ namespace WireMock.Net.Tests.RequestMatchers { // Assign var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key"), "GET", "127.0.0.1"); - var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", new string[] { }); + var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false, new string[] { }); // Act var result = new RequestMatchResult(); @@ -103,7 +118,7 @@ namespace WireMock.Net.Tests.RequestMatchers { // Assign var requestMessage = new RequestMessage(new UrlDetails("http://localhost?key=frank@contoso.com"), "GET", "127.0.0.1"); - var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key"); + var matcher = new RequestMessageParamMatcher(MatchBehaviour.AcceptOnMatch, "key", false); // Act var result = new RequestMatchResult(); diff --git a/test/WireMock.Net.Tests/RequestMessageTests.cs b/test/WireMock.Net.Tests/RequestMessageTests.cs index 8d04bece..f4828818 100644 --- a/test/WireMock.Net.Tests/RequestMessageTests.cs +++ b/test/WireMock.Net.Tests/RequestMessageTests.cs @@ -39,6 +39,16 @@ namespace WireMock.Net.Tests Check.That(request.GetParameter("foo")).ContainsExactly("bar"); } + [Fact] + public void RequestMessage_ParseQuery_SingleKey_SingleValue_WithIgnoreCase() + { + // Assign + var request = new RequestMessage(new UrlDetails("http://localhost?foo=bar"), "POST", ClientIp); + + // Assert + Check.That(request.GetParameter("FoO", true)).ContainsExactly("bar"); + } + [Fact] public void RequestMessage_ParseQuery_MultipleKeys_MultipleValues() {