From cb09d65f10cd8a5b4a84e34417e48b0836a9ae5a Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 23 Jul 2019 18:02:46 +0200 Subject: [PATCH] Support WithBody with multiple matchers (#304) --- Directory.Build.props | 2 +- GitHubReleaseNotes.txt | 2 +- .../MainApp.cs | 7 ++ src/WireMock.Net/Admin/Mappings/BodyModel.cs | 5 ++ .../Request/RequestMessageBodyMatcher.cs | 40 ++++++---- .../RequestBuilders/IBodyRequestBuilder.cs | 11 ++- .../RequestBuilders/Request.WithBody.cs | 73 +++++++++++++++++++ ...Request.Params.cs => Request.WithParam.cs} | 0 src/WireMock.Net/RequestBuilders/Request.cs | 57 --------------- .../Serialization/MappingConverter.cs | 27 ++++--- .../Serialization/MatcherMapper.cs | 7 +- .../Server/FluentMockServer.Admin.cs | 7 +- .../RequestBuilderWithBodyTests.cs | 25 ++++++- .../RequestMessageBodyMatcherTests.cs | 36 ++++++++- 14 files changed, 206 insertions(+), 93 deletions(-) create mode 100644 src/WireMock.Net/RequestBuilders/Request.WithBody.cs rename src/WireMock.Net/RequestBuilders/{Request.Params.cs => Request.WithParam.cs} (100%) diff --git a/Directory.Build.props b/Directory.Build.props index cd4a2184..4310c281 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 1.0.24 + 1.0.25 diff --git a/GitHubReleaseNotes.txt b/GitHubReleaseNotes.txt index 554d7463..dd30771c 100644 --- a/GitHubReleaseNotes.txt +++ b/GitHubReleaseNotes.txt @@ -1,3 +1,3 @@ https://github.com/StefH/GitHubReleaseNotes -GitHubReleaseNotes.exe --output CHANGELOG.md --skip-empty-releases --version 1.0.24.0 \ No newline at end of file +GitHubReleaseNotes.exe --output CHANGELOG.md --skip-empty-releases --version 1.0.25.0 \ No newline at end of file diff --git a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs index 334fecc0..8ea9ded1 100644 --- a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs +++ b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs @@ -94,6 +94,13 @@ namespace WireMock.Net.ConsoleApplication ) .RespondWith(Response.Create().WithBody("XPathMatcher!")); + server + .Given(Request.Create() + .WithPath("/xpaths").UsingPost() + .WithBody(new[] { new XPathMatcher("/todo-list[count(todo-item) = 3]"), new XPathMatcher("/todo-list[count(todo-item) = 4]") }) + ) + .RespondWith(Response.Create().WithBody("xpaths!")); + server .Given(Request .Create() diff --git a/src/WireMock.Net/Admin/Mappings/BodyModel.cs b/src/WireMock.Net/Admin/Mappings/BodyModel.cs index da5a725f..e45e5949 100644 --- a/src/WireMock.Net/Admin/Mappings/BodyModel.cs +++ b/src/WireMock.Net/Admin/Mappings/BodyModel.cs @@ -9,5 +9,10 @@ /// Gets or sets the matcher. /// public MatcherModel Matcher { get; set; } + + /// + /// Gets or sets the matchers. + /// + public MatcherModel[] Matchers { get; set; } } } \ 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 18a81443..3faf8c71 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs @@ -1,5 +1,6 @@ -using System; using JetBrains.Annotations; +using System; +using System.Linq; using WireMock.Util; using WireMock.Validation; @@ -26,16 +27,16 @@ namespace WireMock.Matchers.Request public Func JsonFunc { get; } /// - /// The matcher. + /// The matchers. /// - public IMatcher Matcher { get; } + public IMatcher[] Matchers { get; } /// /// Initializes a new instance of the class. /// /// The match behaviour. /// The body. - public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, [NotNull] string body) : this(new WildcardMatcher(matchBehaviour, body)) + public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, [NotNull] string body) : this(new[] { new WildcardMatcher(matchBehaviour, body) }.Cast().ToArray()) { } @@ -44,7 +45,7 @@ namespace WireMock.Matchers.Request /// /// The match behaviour. /// The body. - public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, [NotNull] byte[] body) : this(new ExactObjectMatcher(matchBehaviour, body)) + public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, [NotNull] byte[] body) : this(new[] { new ExactObjectMatcher(matchBehaviour, body) }.Cast().ToArray()) { } @@ -53,7 +54,7 @@ namespace WireMock.Matchers.Request /// /// The match behaviour. /// The body. - public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, [NotNull] object body) : this(new ExactObjectMatcher(matchBehaviour, body)) + public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, [NotNull] object body) : this(new[] { new ExactObjectMatcher(matchBehaviour, body) }.Cast().ToArray()) { } @@ -90,25 +91,24 @@ namespace WireMock.Matchers.Request /// /// Initializes a new instance of the class. /// - /// The matcher. - public RequestMessageBodyMatcher([NotNull] IMatcher matcher) + /// The matchers. + public RequestMessageBodyMatcher([NotNull] params IMatcher[] matchers) { - Check.NotNull(matcher, nameof(matcher)); - - Matcher = matcher; + Check.NotNull(matchers, nameof(matchers)); + Matchers = matchers; } /// public double GetMatchingScore(RequestMessage requestMessage, RequestMatchResult requestMatchResult) { - double score = IsMatch(requestMessage); + double score = CalculateMatchScore(requestMessage); return requestMatchResult.AddScore(GetType(), score); } - private double IsMatch(RequestMessage requestMessage) + private double CalculateMatchScore(RequestMessage requestMessage, IMatcher matcher) { // Check if the matcher is a IObjectMatcher - if (Matcher is IObjectMatcher objectMatcher) + if (matcher is IObjectMatcher objectMatcher) { // If the body is a JSON object, try to match. if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json) @@ -124,7 +124,7 @@ namespace WireMock.Matchers.Request } // Check if the matcher is a IStringMatcher - if (Matcher is IStringMatcher stringMatcher) + 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) @@ -133,6 +133,16 @@ namespace WireMock.Matchers.Request } } + return MatchScores.Mismatch; + } + + private double CalculateMatchScore(RequestMessage requestMessage) + { + if (Matchers != null && Matchers.Any()) + { + return Matchers.Max(matcher => CalculateMatchScore(requestMessage, matcher)); + } + if (Func != null) { return MatchScores.ToScore(requestMessage?.BodyData?.DetectedBodyType == BodyType.String && Func(requestMessage.BodyData.BodyAsString)); diff --git a/src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs b/src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs index b3e686f9..06469fd1 100644 --- a/src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs +++ b/src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs @@ -1,5 +1,5 @@ -using System; -using JetBrains.Annotations; +using JetBrains.Annotations; +using System; using WireMock.Matchers; namespace WireMock.RequestBuilders @@ -16,6 +16,13 @@ namespace WireMock.RequestBuilders /// The . IRequestBuilder WithBody([NotNull] IMatcher matcher); + /// + /// WithBody: IMatcher[] + /// + /// The matchers. + /// The . + IRequestBuilder WithBody([NotNull] IMatcher[] matchers); + /// /// WithBody: Body as string /// diff --git a/src/WireMock.Net/RequestBuilders/Request.WithBody.cs b/src/WireMock.Net/RequestBuilders/Request.WithBody.cs new file mode 100644 index 00000000..dc1f8e19 --- /dev/null +++ b/src/WireMock.Net/RequestBuilders/Request.WithBody.cs @@ -0,0 +1,73 @@ +using System; +using WireMock.Matchers; +using WireMock.Matchers.Request; +using WireMock.Validation; + +namespace WireMock.RequestBuilders +{ + public partial class Request + { + /// + 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) + { + Check.NotNull(matchers, nameof(matchers)); + + _requestMatchers.Add(new RequestMessageBodyMatcher(matchers)); + return this; + } + + /// + public IRequestBuilder WithBody(Func func) + { + Check.NotNull(func, nameof(func)); + + _requestMatchers.Add(new RequestMessageBodyMatcher(func)); + return this; + } + + /// + public IRequestBuilder WithBody(Func func) + { + Check.NotNull(func, nameof(func)); + + _requestMatchers.Add(new RequestMessageBodyMatcher(func)); + return this; + } + + /// + public IRequestBuilder WithBody(Func func) + { + Check.NotNull(func, nameof(func)); + + _requestMatchers.Add(new RequestMessageBodyMatcher(func)); + return this; + } + } +} diff --git a/src/WireMock.Net/RequestBuilders/Request.Params.cs b/src/WireMock.Net/RequestBuilders/Request.WithParam.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/Request.Params.cs rename to src/WireMock.Net/RequestBuilders/Request.WithParam.cs diff --git a/src/WireMock.Net/RequestBuilders/Request.cs b/src/WireMock.Net/RequestBuilders/Request.cs index 3fa1b9f9..8864c158 100644 --- a/src/WireMock.Net/RequestBuilders/Request.cs +++ b/src/WireMock.Net/RequestBuilders/Request.cs @@ -233,63 +233,6 @@ namespace WireMock.RequestBuilders return this; } - /// - 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) - { - Check.NotNull(matcher, nameof(matcher)); - - _requestMatchers.Add(new RequestMessageBodyMatcher(matcher)); - return this; - } - - /// - public IRequestBuilder WithBody(Func func) - { - Check.NotNull(func, nameof(func)); - - _requestMatchers.Add(new RequestMessageBodyMatcher(func)); - return this; - } - - /// - public IRequestBuilder WithBody(Func func) - { - Check.NotNull(func, nameof(func)); - - _requestMatchers.Add(new RequestMessageBodyMatcher(func)); - return this; - } - - /// - public IRequestBuilder WithBody(Func func) - { - Check.NotNull(func, nameof(func)); - - _requestMatchers.Add(new RequestMessageBodyMatcher(func)); - return this; - } - /// public IRequestBuilder WithHeader(string name, string pattern, MatchBehaviour matchBehaviour) { diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs index 0a8f9010..cdbf44e0 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net/Serialization/MappingConverter.cs @@ -21,14 +21,14 @@ namespace WireMock.Serialization var headerMatchers = request.GetRequestMessageMatchers(); var cookieMatchers = request.GetRequestMessageMatchers(); var paramsMatchers = request.GetRequestMessageMatchers(); - var bodyMatcher = request.GetRequestMessageMatcher(); var methodMatcher = request.GetRequestMessageMatcher(); + var bodyMatcher = request.GetRequestMessageMatcher(); var mappingModel = new MappingModel { Guid = mapping.Guid, Title = mapping.Title, - Priority = mapping.Priority != 0 ? mapping.Priority : (int?) null, + Priority = mapping.Priority != 0 ? mapping.Priority : (int?)null, Scenario = mapping.Scenario, WhenStateIs = mapping.ExecutionConditionState, SetStateTo = mapping.NextState, @@ -66,14 +66,9 @@ namespace WireMock.Serialization Params = paramsMatchers != null && paramsMatchers.Any() ? paramsMatchers.Select(pm => new ParamModel { Name = pm.Key, - IgnoreCase = pm.IgnoreCase == true ? true : (bool?) null, + IgnoreCase = pm.IgnoreCase == true ? true : (bool?)null, Matchers = MatcherMapper.Map(pm.Matchers) - }).ToList() : null, - - Body = methodMatcher?.Methods != null && methodMatcher.Methods.Any(m => m == "get") ? null : new BodyModel - { - Matcher = bodyMatcher != null ? MatcherMapper.Map(bodyMatcher.Matcher) : null - } + }).ToList() : null }, Response = new ResponseModel { @@ -81,6 +76,20 @@ namespace WireMock.Serialization } }; + if (methodMatcher?.Methods != null && methodMatcher.Methods.All(m => m != "get") && bodyMatcher?.Matchers != null) + { + mappingModel.Request.Body = new BodyModel(); + + if (bodyMatcher.Matchers.Length == 1) + { + mappingModel.Request.Body.Matcher = MatcherMapper.Map(bodyMatcher.Matchers[0]); + } + else if (bodyMatcher.Matchers.Length > 1) + { + mappingModel.Request.Body.Matchers = MatcherMapper.Map(bodyMatcher.Matchers); + } + } + if (!string.IsNullOrEmpty(response.ProxyUrl)) { mappingModel.Response.StatusCode = null; diff --git a/src/WireMock.Net/Serialization/MatcherMapper.cs b/src/WireMock.Net/Serialization/MatcherMapper.cs index 14154c7f..331b9fce 100644 --- a/src/WireMock.Net/Serialization/MatcherMapper.cs +++ b/src/WireMock.Net/Serialization/MatcherMapper.cs @@ -10,6 +10,11 @@ namespace WireMock.Serialization { internal static class MatcherMapper { + public static IMatcher[] Map([CanBeNull] IEnumerable matchers) + { + return matchers?.Select(Map).Where(m => m != null).ToArray(); + } + public static IMatcher Map([CanBeNull] MatcherModel matcher) { if (matcher == null) @@ -70,7 +75,7 @@ namespace WireMock.Serialization public static MatcherModel[] Map([CanBeNull] IEnumerable matchers) { - return matchers?.Select(Map).Where(x => x != null).ToArray(); + return matchers?.Select(Map).Where(m => m != null).ToArray(); } public static MatcherModel Map([CanBeNull] IMatcher matcher) diff --git a/src/WireMock.Net/Server/FluentMockServer.Admin.cs b/src/WireMock.Net/Server/FluentMockServer.Admin.cs index e5d449fa..d3c18f37 100644 --- a/src/WireMock.Net/Server/FluentMockServer.Admin.cs +++ b/src/WireMock.Net/Server/FluentMockServer.Admin.cs @@ -716,8 +716,11 @@ namespace WireMock.Server if (requestModel.Body?.Matcher != null) { - var bodyMatcher = MatcherMapper.Map(requestModel.Body.Matcher); - requestBuilder = requestBuilder.WithBody(bodyMatcher); + requestBuilder = requestBuilder.WithBody(MatcherMapper.Map(requestModel.Body.Matcher)); + } + else if (requestModel.Body?.Matchers != null) + { + requestBuilder = requestBuilder.WithBody(MatcherMapper.Map(requestModel.Body.Matchers)); } return requestBuilder; diff --git a/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithBodyTests.cs b/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithBodyTests.cs index 34b47979..08e64373 100644 --- a/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithBodyTests.cs +++ b/test/WireMock.Net.Tests/RequestBuilders/RequestBuilderWithBodyTests.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; -using NFluent; +using FluentAssertions; +using System.Collections.Generic; +using System.Linq; using WireMock.Matchers; using WireMock.Matchers.Request; using WireMock.RequestBuilders; @@ -20,8 +21,24 @@ namespace WireMock.Net.Tests.RequestBuilders // Assert var matchers = requestBuilder.GetPrivateFieldValue>("_requestMatchers"); - Check.That(matchers.Count).IsEqualTo(1); - Check.That(((RequestMessageBodyMatcher) matchers[0]).Matcher).IsEqualTo(matcher); + matchers.Should().HaveCount(1); + ((RequestMessageBodyMatcher)matchers[0]).Matchers.Should().Contain(matcher); + } + + [Fact] + public void RequestBuilder_WithBody_IMatchers() + { + // Assign + var matcher1 = new WildcardMatcher("x"); + var matcher2 = new WildcardMatcher("y"); + + // Act + var requestBuilder = (Request)Request.Create().WithBody(new[] { matcher1, matcher2 }.Cast().ToArray()); + + // Assert + var matchers = requestBuilder.GetPrivateFieldValue>("_requestMatchers"); + matchers.Should().HaveCount(1); + ((RequestMessageBodyMatcher)matchers[0]).Matchers.Should().Contain(new[] { matcher1, matcher2 }); } } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageBodyMatcherTests.cs b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageBodyMatcherTests.cs index 6a21a4bb..f2173024 100644 --- a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageBodyMatcherTests.cs +++ b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageBodyMatcherTests.cs @@ -1,4 +1,5 @@ -using Moq; +using System.Linq; +using Moq; using NFluent; using WireMock.Matchers; using WireMock.Matchers.Request; @@ -38,6 +39,39 @@ namespace WireMock.Net.Tests.RequestMatchers stringMatcherMock.Verify(m => m.IsMatch("b"), Times.Once); } + [Fact] + public void RequestMessageBodyMatcher_GetMatchingScore_BodyAsString_IStringMatchers() + { + // Assign + var body = new BodyData + { + BodyAsString = "b", + DetectedBodyType = BodyType.String + }; + var stringMatcherMock1 = new Mock(); + stringMatcherMock1.Setup(m => m.IsMatch(It.IsAny())).Returns(0.2d); + var stringMatcherMock2 = new Mock(); + stringMatcherMock2.Setup(m => m.IsMatch(It.IsAny())).Returns(0.8d); + 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()); + + // Act + var result = new RequestMatchResult(); + double score = matcher.GetMatchingScore(requestMessage, result); + + // Assert + Check.That(score).IsEqualTo(0.8d); + + // 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); + } + [Fact] public void RequestMessageBodyMatcher_GetMatchingScore_BodyAsBytes_IStringMatcher() {