From 72335d48d6c02d2819ef7aa17288d39e367939df Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 19 Jan 2017 21:51:22 +0100 Subject: [PATCH] Add XmlPath2 / RegEx matchers Solves issue #5 --- src/WireMock/Matchers/IMatcher.cs | 17 +++++ src/WireMock/Matchers/RegexMatcher.cs | 38 ++++++++++ src/WireMock/Matchers/XPathMatcher.cs | 42 +++++++++++ src/WireMock/RequestBodySpec.cs | 61 ++++++++-------- .../RequestBuilders/IBodyRequestBuilder.cs | 13 ++++ src/WireMock/RequestBuilders/Request.cs | 14 ++++ src/WireMock/project.json | 3 +- test/WireMock.Net.Tests/RequestTests.cs | 70 +++++++++++++++---- 8 files changed, 214 insertions(+), 44 deletions(-) create mode 100644 src/WireMock/Matchers/IMatcher.cs create mode 100644 src/WireMock/Matchers/RegexMatcher.cs create mode 100644 src/WireMock/Matchers/XPathMatcher.cs diff --git a/src/WireMock/Matchers/IMatcher.cs b/src/WireMock/Matchers/IMatcher.cs new file mode 100644 index 000000000..398f2508a --- /dev/null +++ b/src/WireMock/Matchers/IMatcher.cs @@ -0,0 +1,17 @@ +namespace WireMock.Matchers +{ + /// + /// IMatcher + /// + public interface IMatcher + { + /// + /// Determines whether the specified input is match. + /// + /// The input. + /// + /// true if the specified input is match; otherwise, false. + /// + bool IsMatch(string input); + } +} \ No newline at end of file diff --git a/src/WireMock/Matchers/RegexMatcher.cs b/src/WireMock/Matchers/RegexMatcher.cs new file mode 100644 index 000000000..657a137df --- /dev/null +++ b/src/WireMock/Matchers/RegexMatcher.cs @@ -0,0 +1,38 @@ +using System.Text.RegularExpressions; +using JetBrains.Annotations; +using WireMock.Validation; + +namespace WireMock.Matchers +{ + /// + /// Regular Expression Matcher + /// + /// + public class RegexMatcher : IMatcher + { + private readonly Regex _expression; + + /// + /// Initializes a new instance of the class. + /// + /// The pattern. + public RegexMatcher([NotNull] string pattern) + { + Check.NotNull(pattern, nameof(pattern)); + + _expression = new Regex(pattern, RegexOptions.Compiled); + } + + /// + /// Determines whether the specified input is match. + /// + /// The input. + /// + /// true if the specified input is match; otherwise, false. + /// + public bool IsMatch(string input) + { + return input != null && _expression.IsMatch(input); + } + } +} \ No newline at end of file diff --git a/src/WireMock/Matchers/XPathMatcher.cs b/src/WireMock/Matchers/XPathMatcher.cs new file mode 100644 index 000000000..1334b74d3 --- /dev/null +++ b/src/WireMock/Matchers/XPathMatcher.cs @@ -0,0 +1,42 @@ +using System.Xml; +using JetBrains.Annotations; +using WireMock.Validation; +using Wmhelp.XPath2; + +namespace WireMock.Matchers +{ + /// + /// XPath2Matcher + /// + /// + public class XPathMatcher : IMatcher + { + private readonly string _pattern; + + /// + /// Initializes a new instance of the class. + /// + /// The pattern. + public XPathMatcher([NotNull] string pattern) + { + Check.NotNull(pattern, nameof(pattern)); + + _pattern = pattern; + } + + /// + /// Determines whether the specified input is match. + /// + /// The input. + /// + /// true if the specified input is match; otherwise, false. + /// + public bool IsMatch(string input) + { + var nav = new XmlDocument { InnerXml = input }.CreateNavigator(); + object result = nav.XPath2Evaluate($"boolean({_pattern})"); + + return true.Equals(result); + } + } +} \ No newline at end of file diff --git a/src/WireMock/RequestBodySpec.cs b/src/WireMock/RequestBodySpec.cs index bd12ff8ba..117748ff4 100644 --- a/src/WireMock/RequestBodySpec.cs +++ b/src/WireMock/RequestBodySpec.cs @@ -2,22 +2,9 @@ using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using JetBrains.Annotations; +using WireMock.Matchers; using WireMock.Validation; -[module: - SuppressMessage("StyleCop.CSharp.ReadabilityRules", - "SA1101:PrefixLocalCallsWithThis", - Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] -[module: - SuppressMessage("StyleCop.CSharp.NamingRules", - "SA1309:FieldNamesMustNotBeginWithUnderscore", - Justification = "Reviewed. Suppression is OK here, as it conflicts with internal naming rules.")] -[module: - SuppressMessage("StyleCop.CSharp.DocumentationRules", - "SA1633:FileMustHaveHeader", - Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")] -// ReSharper disable ArrangeThisQualifier -// ReSharper disable InconsistentNaming namespace WireMock { /// @@ -28,22 +15,22 @@ public class RequestBodySpec : ISpecifyRequests /// /// The bodyRegex. /// - private readonly byte[] bodyData; + private readonly byte[] _bodyData; /// - /// The bodyRegex. + /// The matcher. /// - private readonly Regex bodyRegex; + private readonly IMatcher _matcher; /// /// The body function /// - private readonly Func bodyFunc; + private readonly Func _bodyFunc; /// /// The body data function /// - private readonly Func bodyDataFunc; + private readonly Func _bodyDataFunc; /// /// Initializes a new instance of the class. @@ -54,7 +41,7 @@ public class RequestBodySpec : ISpecifyRequests public RequestBodySpec([NotNull, RegexPattern] string body) { Check.NotNull(body, nameof(body)); - bodyRegex = new Regex(body); + _matcher = new RegexMatcher(body); } /// @@ -66,7 +53,7 @@ public RequestBodySpec([NotNull, RegexPattern] string body) public RequestBodySpec([NotNull] byte[] body) { Check.NotNull(body, nameof(body)); - bodyData = body; + _bodyData = body; } /// @@ -78,7 +65,7 @@ public RequestBodySpec([NotNull] byte[] body) public RequestBodySpec([NotNull] Func func) { Check.NotNull(func, nameof(func)); - bodyFunc = func; + _bodyFunc = func; } /// @@ -90,7 +77,19 @@ public RequestBodySpec([NotNull] Func func) public RequestBodySpec([NotNull] Func func) { Check.NotNull(func, nameof(func)); - bodyDataFunc = func; + _bodyDataFunc = func; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The body matcher. + /// + public RequestBodySpec([NotNull] IMatcher matcher) + { + Check.NotNull(matcher, nameof(matcher)); + _matcher = matcher; } /// @@ -104,17 +103,17 @@ public RequestBodySpec([NotNull] Func func) /// public bool IsSatisfiedBy(RequestMessage requestMessage) { - if (bodyRegex != null) - return bodyRegex.IsMatch(requestMessage.BodyAsString); + if (_matcher != null) + return _matcher.IsMatch(requestMessage.BodyAsString); - if (bodyData != null) - return requestMessage.Body == bodyData; + if (_bodyData != null) + return requestMessage.Body == _bodyData; - if (bodyFunc != null) - return bodyFunc(requestMessage.BodyAsString); + if (_bodyFunc != null) + return _bodyFunc(requestMessage.BodyAsString); - if (bodyDataFunc != null) - return bodyDataFunc(requestMessage.Body); + if (_bodyDataFunc != null) + return _bodyDataFunc(requestMessage.Body); return false; } diff --git a/src/WireMock/RequestBuilders/IBodyRequestBuilder.cs b/src/WireMock/RequestBuilders/IBodyRequestBuilder.cs index 2412c1bef..262107159 100644 --- a/src/WireMock/RequestBuilders/IBodyRequestBuilder.cs +++ b/src/WireMock/RequestBuilders/IBodyRequestBuilder.cs @@ -1,4 +1,6 @@ using System; +using JetBrains.Annotations; +using WireMock.Matchers; namespace WireMock.RequestBuilders { @@ -7,6 +9,17 @@ namespace WireMock.RequestBuilders /// public interface IBodyRequestBuilder { + /// + /// The with body. + /// + /// + /// The matcher. + /// + /// + /// The . + /// + ISpecifyRequests WithBody([NotNull] IMatcher matcher); + /// /// The with body. /// diff --git a/src/WireMock/RequestBuilders/Request.cs b/src/WireMock/RequestBuilders/Request.cs index 1bccb8fb9..4078d58ae 100644 --- a/src/WireMock/RequestBuilders/Request.cs +++ b/src/WireMock/RequestBuilders/Request.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using JetBrains.Annotations; +using WireMock.Matchers; [module: SuppressMessage("StyleCop.CSharp.ReadabilityRules", @@ -255,6 +256,19 @@ public ISpecifyRequests WithBody(Func func) return this; } + /// + /// The with body. + /// + /// The matcher. + /// + /// The . + /// + public ISpecifyRequests WithBody(IMatcher matcher) + { + _requestSpecs.Add(new RequestBodySpec(matcher)); + return this; + } + /// /// The with parameters. /// diff --git a/src/WireMock/project.json b/src/WireMock/project.json index a9e0dd196..385fac96d 100644 --- a/src/WireMock/project.json +++ b/src/WireMock/project.json @@ -25,7 +25,8 @@ "JetBrains.Annotations": { "version": "10.2.1", "type": "build" - } + }, + "XPath2": "1.0.3.1" }, "frameworks": { diff --git a/test/WireMock.Net.Tests/RequestTests.cs b/test/WireMock.Net.Tests/RequestTests.cs index 85abd3be4..e07799c4f 100644 --- a/test/WireMock.Net.Tests/RequestTests.cs +++ b/test/WireMock.Net.Tests/RequestTests.cs @@ -1,20 +1,11 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Text; using NFluent; using NUnit.Framework; using WireMock.RequestBuilders; +using WireMock.Matchers; -[module: - SuppressMessage("StyleCop.CSharp.DocumentationRules", - "SA1600:ElementsMustBeDocumented", - Justification = "Reviewed. Suppression is OK here, as it's a tests class.")] -[module: - SuppressMessage("StyleCop.CSharp.DocumentationRules", - "SA1633:FileMustHaveHeader", - Justification = "Reviewed. Suppression is OK here, as unknown copyright and company.")] -// ReSharper disable InconsistentNaming namespace WireMock.Net.Tests { [TestFixture] @@ -249,7 +240,7 @@ public void Should_specify_requests_matching_given_header_prefix() public void Should_specify_requests_matching_given_body() { // given - var spec = Request.WithUrl("/foo").UsingAnyVerb().WithBody(".*Hello world!.*"); + var spec = Request.WithUrl("/foo").UsingAnyVerb().WithBody("Hello world!"); // when string bodyAsString = "Hello world!"; @@ -261,7 +252,7 @@ public void Should_specify_requests_matching_given_body() } [Test] - public void Should_specify_requests_matching_given_body_as_wildcard() + public void Should_specify_requests_matching_given_body_as_regex() { // given var spec = Request.WithUrl("/foo").UsingAnyVerb().WithBody("H.*o"); @@ -275,6 +266,61 @@ public void Should_specify_requests_matching_given_body_as_wildcard() Check.That(spec.IsSatisfiedBy(request)).IsTrue(); } + [Test] + public void Should_specify_requests_matching_given_body_as_regexmatcher() + { + // given + var spec = Request.WithUrl("/foo").UsingAnyVerb().WithBody(new RegexMatcher("H.*o")); + + // when + string bodyAsString = "Hello world!"; + byte[] body = Encoding.UTF8.GetBytes(bodyAsString); + var request = new RequestMessage(new Uri("http://localhost/foo"), "PUT", body, bodyAsString); + + // then + Check.That(spec.IsSatisfiedBy(request)).IsTrue(); + } + + [Test] + public void Should_specify_requests_matching_given_body_as_xpathmatcher_true() + { + // given + var spec = Request.WithUrl("/foo").UsingAnyVerb().WithBody(new XPathMatcher("/todo-list[count(todo-item) = 3]")); + + // when + string xmlBodyAsString = @" + + abc + def + xyz + "; + byte[] body = Encoding.UTF8.GetBytes(xmlBodyAsString); + var request = new RequestMessage(new Uri("http://localhost/foo"), "PUT", body, xmlBodyAsString); + + // then + Check.That(spec.IsSatisfiedBy(request)).IsTrue(); + } + + [Test] + public void Should_specify_requests_matching_given_body_as_xpathmatcher_false() + { + // given + var spec = Request.WithUrl("/foo").UsingAnyVerb().WithBody(new XPathMatcher("/todo-list[count(todo-item) = 99]")); + + // when + string xmlBodyAsString = @" + + abc + def + xyz + "; + byte[] body = Encoding.UTF8.GetBytes(xmlBodyAsString); + var request = new RequestMessage(new Uri("http://localhost/foo"), "PUT", body, xmlBodyAsString); + + // then + Check.That(spec.IsSatisfiedBy(request)).IsFalse(); + } + [Test] public void Should_exclude_requests_not_matching_given_body() {