Compare commits

..

7 Commits

Author SHA1 Message Date
Stef Heyenrath
30372a9348 1.5.39 2023-10-09 18:30:19 +02:00
Stef Heyenrath
bc75db8c8c Fix RequestMessageParamMatcher : RejectOnMatch (#1006) 2023-10-09 18:28:22 +02:00
Stef Heyenrath
6254ee3950 1.5.38 2023-10-02 19:33:32 +02:00
Carsten Alder
60bf12e2a9 Support for xml namespaces in XPathMatcher (#1005)
* Support for xml namespaces in XPathMatcher

* Review findings of Stef implemented.

* Fix of build error

* New review findings by Stef

---------

Co-authored-by: Carsten Alder <carsten.alder@schleupen.de>
2023-10-02 19:29:31 +02:00
Stef Heyenrath
a25a8cabf8 1.5.37 2023-09-27 21:43:50 +02:00
Stef Heyenrath
9f9fc85a64 JmesPathMatcherTests (#998) 2023-09-27 21:42:34 +02:00
Stef Heyenrath
b63076a9ac Fix MappingModel to map IgnoreCase and RejectOnMatch for Headers, Cookies and Parameters (#1004) 2023-09-26 21:01:33 +02:00
23 changed files with 684 additions and 74 deletions

View File

@@ -1,3 +1,15 @@
# 1.5.39 (09 October 2023)
- [#1006](https://github.com/WireMock-Net/WireMock.Net/pull/1006) - Fix RequestMessageParamMatcher : RejectOnMatch [bug] contributed by [StefH](https://github.com/StefH)
- [#997](https://github.com/WireMock-Net/WireMock.Net/issues/997) - JmesPathMatcher or and MatchOperator working in version 1.5.34 but not 1.5.35 [bug]
# 1.5.38 (02 October 2023)
- [#1005](https://github.com/WireMock-Net/WireMock.Net/pull/1005) - Support for xml namespaces in XPathMatcher [feature] contributed by [cal-schleupen](https://github.com/cal-schleupen)
# 1.5.37 (27 September 2023)
- [#998](https://github.com/WireMock-Net/WireMock.Net/pull/998) - Add JmesPathMatcher UnitTests [test] contributed by [StefH](https://github.com/StefH)
- [#1004](https://github.com/WireMock-Net/WireMock.Net/pull/1004) - Fix MappingModel to map IgnoreCase and RejectOnMatch for Headers, Cookies and Parameters [bug] contributed by [StefH](https://github.com/StefH)
- [#1003](https://github.com/WireMock-Net/WireMock.Net/issues/1003) - Store Mapping per POST request ignores &quot;IgnoreCase&quot; property of HeaderModel [bug]
# 1.5.36 (21 September 2023)
- [#986](https://github.com/WireMock-Net/WireMock.Net/pull/986) - Write logging in case a Matcher throws an exception [feature] contributed by [StefH](https://github.com/StefH)
- [#996](https://github.com/WireMock-Net/WireMock.Net/pull/996) - Remove dependency on Microsoft.AspNet.WebApi.Client [feature] contributed by [StefH](https://github.com/StefH)

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>1.5.36</VersionPrefix>
<VersionPrefix>1.5.39</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/WireMock-Net/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>

View File

@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes
SET version=1.5.36
SET version=1.5.39
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels question invalid doc duplicate --version %version% --token %GH_TOKEN%

View File

@@ -1,8 +1,5 @@
# 1.5.36 (21 September 2023)
- #986 Write logging in case a Matcher throws an exception [feature]
- #996 Remove dependency on Microsoft.AspNet.WebApi.Client [feature]
- #1002 Fixed logic for SaveUnmatchedRequests [bug]
- #974 HttpClient extension methods causes ambiguous invocations in .NET 7 [bug]
- #1001 SaveUnmatchedRequests stopped working [bug]
# 1.5.39 (09 October 2023)
- #1006 Fix RequestMessageParamMatcher : RejectOnMatch [bug]
- #997 JmesPathMatcher or and MatchOperator working in version 1.5.34 but not 1.5.35 [bug]
The full release notes can be found here: https://github.com/WireMock-Net/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -73,4 +73,8 @@ public class MatcherModel
/// </summary>
public MatcherModel? ContentMatcher { get; set; }
#endregion
#region XPathMatcher
public XmlNamespace[]? XmlNamespaceMap { get; set; }
#endregion
}

View File

@@ -16,6 +16,11 @@ public class ParamModel
/// </summary>
public bool? IgnoreCase { get; set; }
/// <summary>
/// Gets or sets the Reject on match for the Param Name.
/// </summary>
public bool? RejectOnMatch { get; set; }
/// <summary>
/// Gets or sets the matchers.
/// </summary>

View File

@@ -0,0 +1,21 @@
namespace WireMock.Admin.Mappings;
/// <summary>
/// Defines an xml namespace consisting of prefix and uri.
/// <example>xmlns:i="http://www.w3.org/2001/XMLSchema-instance"</example>
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class XmlNamespace
{
/// <summary>
/// The prefix.
/// <example>i</example>
/// </summary>
public string Prefix { get; set; } = null!;
/// <summary>
/// The uri.
/// <example>http://www.w3.org/2001/XMLSchema-instance</example>
/// </summary>
public string Uri { get; set; } = null!;
}

View File

@@ -118,8 +118,9 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
// The SelectToken method can accept a string path to a child token ( i.e. "Manufacturers[0].Products[0].Price").
// In that case it will return a JValue (some type) which does not implement the IEnumerable interface.
return MatchScores.ToScore(
_patterns.Select(pattern => array.SelectToken(pattern.GetPattern()) != null).ToArray(), MatchOperator);
var values = _patterns.Select(pattern => array.SelectToken(pattern.GetPattern()) != null).ToArray();
return MatchScores.ToScore(values, MatchOperator);
}
// https://github.com/WireMock-Net/WireMock.Net/issues/965

View File

@@ -53,7 +53,7 @@ public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false)
{
Guard.NotNull(value, nameof(value));
Guard.NotNull(value);
MatchBehaviour = matchBehaviour;
IgnoreCase = ignoreCase;

View File

@@ -11,9 +11,15 @@ namespace WireMock.Matchers.Request;
/// <inheritdoc cref="IRequestMatcher"/>
public class RequestMessageCookieMatcher : IRequestMatcher
{
private readonly MatchBehaviour _matchBehaviour;
/// <summary>
/// MatchBehaviour
/// </summary>
public MatchBehaviour MatchBehaviour { get; }
private readonly bool _ignoreCase;
/// <summary>
/// IgnoreCase
/// </summary>
public bool IgnoreCase { get; }
/// <summary>
/// The functions
@@ -39,8 +45,8 @@ public class RequestMessageCookieMatcher : IRequestMatcher
/// <param name="matchBehaviour">The match behaviour.</param>
public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, string name, string pattern, bool ignoreCase)
{
_matchBehaviour = matchBehaviour;
_ignoreCase = ignoreCase;
MatchBehaviour = matchBehaviour;
IgnoreCase = ignoreCase;
Name = Guard.NotNull(name);
Matchers = new IStringMatcher[] { new WildcardMatcher(matchBehaviour, Guard.NotNull(pattern), ignoreCase) };
}
@@ -67,10 +73,10 @@ public class RequestMessageCookieMatcher : IRequestMatcher
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, string name, bool ignoreCase, params IStringMatcher[] matchers)
{
_matchBehaviour = matchBehaviour;
MatchBehaviour = matchBehaviour;
Name = Guard.NotNull(name);
Matchers = Guard.NotNull(matchers);
_ignoreCase = ignoreCase;
IgnoreCase = ignoreCase;
}
/// <summary>
@@ -96,11 +102,11 @@ public class RequestMessageCookieMatcher : IRequestMatcher
{
if (requestMessage.Cookies == null)
{
return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch);
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
}
// Check if we want to use IgnoreCase to compare the Cookie-Name and Cookie-Value
var cookies = !_ignoreCase ? requestMessage.Cookies : new Dictionary<string, string>(requestMessage.Cookies, StringComparer.OrdinalIgnoreCase);
var cookies = !IgnoreCase ? requestMessage.Cookies : new Dictionary<string, string>(requestMessage.Cookies, StringComparer.OrdinalIgnoreCase);
if (Funcs != null)
{
@@ -114,7 +120,7 @@ public class RequestMessageCookieMatcher : IRequestMatcher
if (!cookies.ContainsKey(Name))
{
return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch);
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
}
return Matchers.Max(m => m.IsMatch(cookies[Name]));

View File

@@ -12,8 +12,15 @@ namespace WireMock.Matchers.Request;
/// <inheritdoc cref="IRequestMatcher"/>
public class RequestMessageHeaderMatcher : IRequestMatcher
{
private readonly MatchBehaviour _matchBehaviour;
private readonly bool _ignoreCase;
/// <summary>
/// MatchBehaviour
/// </summary>
public MatchBehaviour MatchBehaviour { get; }
/// <summary>
/// IgnoreCase
/// </summary>
public bool IgnoreCase { get; }
/// <summary>
/// The functions
@@ -47,8 +54,8 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
Guard.NotNull(name);
Guard.NotNull(pattern);
_matchBehaviour = matchBehaviour;
_ignoreCase = ignoreCase;
MatchBehaviour = matchBehaviour;
IgnoreCase = ignoreCase;
Name = name;
Matchers = new IStringMatcher[] { new WildcardMatcher(matchBehaviour, pattern, ignoreCase) };
}
@@ -80,11 +87,11 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
Guard.NotNull(name);
Guard.NotNull(matchers);
_matchBehaviour = matchBehaviour;
MatchBehaviour = matchBehaviour;
MatchOperator = matchOperator;
Name = name;
Matchers = matchers;
_ignoreCase = ignoreCase;
IgnoreCase = ignoreCase;
}
/// <summary>
@@ -108,11 +115,11 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
{
if (requestMessage.Headers == null)
{
return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch);
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
}
// Check if we want to use IgnoreCase to compare the Header-Name and Header-Value(s)
var headers = !_ignoreCase ? requestMessage.Headers : new Dictionary<string, WireMockList<string>>(requestMessage.Headers, StringComparer.OrdinalIgnoreCase);
var headers = !IgnoreCase ? requestMessage.Headers : new Dictionary<string, WireMockList<string>>(requestMessage.Headers, StringComparer.OrdinalIgnoreCase);
if (Funcs != null)
{
@@ -124,7 +131,7 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
{
if (!headers.ContainsKey(Name))
{
return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch);
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
}
var results = new List<MatchResult>();
@@ -138,6 +145,6 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
return MatchResult.From(results, MatchOperator);
}
return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch);
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
}
}

View File

@@ -29,7 +29,7 @@ public class RequestMessageParamMatcher : IRequestMatcher
/// <summary>
/// Defines if the key should be matched using case-ignore.
/// </summary>
public bool? IgnoreCase { get; }
public bool IgnoreCase { get; }
/// <summary>
/// The matchers.
@@ -85,22 +85,22 @@ public class RequestMessageParamMatcher : IRequestMatcher
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
var (score, exception) = GetMatchResult(requestMessage).Expand();
return requestMatchResult.AddScore(GetType(), score, exception);
var score = GetMatchScore(requestMessage);
return requestMatchResult.AddScore(GetType(), MatchBehaviourHelper.Convert(MatchBehaviour, score), null);
}
private MatchResult GetMatchResult(IRequestMessage requestMessage)
private double GetMatchScore(IRequestMessage requestMessage)
{
if (Funcs != null)
{
return MatchScores.ToScore(requestMessage.Query != null && Funcs.Any(f => f(requestMessage.Query)));
}
var valuesPresentInRequestMessage = ((RequestMessage)requestMessage).GetParameter(Key, IgnoreCase ?? false);
var valuesPresentInRequestMessage = ((RequestMessage)requestMessage).GetParameter(Key, IgnoreCase);
if (valuesPresentInRequestMessage == null)
{
// Key is not present at all, just return Mismatch
return default;
return MatchScores.Mismatch;
}
if (Matchers != null && Matchers.Any())
@@ -115,10 +115,10 @@ public class RequestMessageParamMatcher : IRequestMatcher
return MatchScores.Perfect;
}
return default;
return MatchScores.Mismatch;
}
private static MatchResult CalculateScore(IReadOnlyList<IStringMatcher> matchers, WireMockList<string> valuesPresentInRequestMessage)
private static double CalculateScore(IReadOnlyList<IStringMatcher> matchers, WireMockList<string> valuesPresentInRequestMessage)
{
var total = new List<double>();

View File

@@ -1,5 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.XPath;
@@ -7,6 +7,7 @@ using AnyOfTypes;
using WireMock.Extensions;
using WireMock.Models;
using Stef.Validation;
using WireMock.Admin.Mappings;
#if !NETSTANDARD1_3
using Wmhelp.XPath2;
#endif
@@ -24,11 +25,16 @@ public class XPathMatcher : IStringMatcher
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <summary>
/// Array of namespace prefix and uri.
/// </summary>
public XmlNamespace[]? XmlNamespaceMap { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="XPathMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
public XPathMatcher(params AnyOf<string, StringPattern>[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns)
public XPathMatcher(params AnyOf<string, StringPattern>[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, null, patterns)
{
}
@@ -37,13 +43,16 @@ public class XPathMatcher : IStringMatcher
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
/// <param name="xmlNamespaceMap">The xml namespaces of the xml document.</param>
/// <param name="patterns">The patterns.</param>
public XPathMatcher(
MatchBehaviour matchBehaviour,
MatchOperator matchOperator = MatchOperator.Or,
XmlNamespace[]? xmlNamespaceMap = null,
params AnyOf<string, StringPattern>[] patterns)
{
_patterns = Guard.NotNull(patterns);
XmlNamespaceMap = xmlNamespaceMap;
MatchBehaviour = matchBehaviour;
MatchOperator = matchOperator;
}
@@ -52,24 +61,34 @@ public class XPathMatcher : IStringMatcher
public MatchResult IsMatch(string? input)
{
var score = MatchScores.Mismatch;
Exception? exception = null;
if (input != null && TryGetXPathNavigator(input, out var nav))
if (input == null)
{
try
{
#if NETSTANDARD1_3
score = MatchScores.ToScore(_patterns.Select(p => true.Equals(nav.Evaluate($"boolean({p.GetPattern()})"))).ToArray(), MatchOperator);
#else
score = MatchScores.ToScore(_patterns.Select(p => true.Equals(nav.XPath2Evaluate($"boolean({p.GetPattern()})"))).ToArray(), MatchOperator);
#endif
}
catch (Exception ex)
{
exception = ex;
}
return CreateMatchResult(score);
}
try
{
var xPathEvaluator = new XPathEvaluator();
xPathEvaluator.Load(input);
if (!xPathEvaluator.IsXmlDocumentLoaded)
{
return CreateMatchResult(score);
}
score = MatchScores.ToScore(xPathEvaluator.Evaluate(_patterns, XmlNamespaceMap), MatchOperator);
}
catch (Exception exception)
{
return CreateMatchResult(score, exception);
}
return CreateMatchResult(score);
}
private MatchResult CreateMatchResult(double score, Exception? exception = null)
{
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
@@ -84,18 +103,54 @@ public class XPathMatcher : IStringMatcher
/// <inheritdoc />
public string Name => nameof(XPathMatcher);
private static bool TryGetXPathNavigator(string input, [NotNullWhen(true)] out XPathNavigator? nav)
private class XPathEvaluator
{
try
private XmlDocument? _xmlDocument;
private XPathNavigator? _xpathNavigator;
public bool IsXmlDocumentLoaded => _xmlDocument != null;
public void Load(string input)
{
nav = new XmlDocument { InnerXml = input }.CreateNavigator()!;
return true;
try
{
_xmlDocument = new XmlDocument { InnerXml = input };
_xpathNavigator = _xmlDocument.CreateNavigator();
}
catch
{
_xmlDocument = default;
}
}
catch
public bool[] Evaluate(AnyOf<string, StringPattern>[] patterns, IEnumerable<XmlNamespace>? xmlNamespaceMap)
{
nav = default;
return false;
XmlNamespaceManager? xmlNamespaceManager = GetXmlNamespaceManager(xmlNamespaceMap);
return patterns
.Select(p =>
#if NETSTANDARD1_3
true.Equals(_xpathNavigator.Evaluate($"boolean({p.GetPattern()})", xmlNamespaceManager)))
#else
true.Equals(_xpathNavigator.XPath2Evaluate($"boolean({p.GetPattern()})", xmlNamespaceManager)))
#endif
.ToArray();
}
private XmlNamespaceManager? GetXmlNamespaceManager(IEnumerable<XmlNamespace>? xmlNamespaceMap)
{
if (_xpathNavigator == null || xmlNamespaceMap == null)
{
return default;
}
var nsManager = new XmlNamespaceManager(_xpathNavigator.NameTable);
foreach (XmlNamespace xmlNamespace in xmlNamespaceMap)
{
nsManager.AddNamespace(xmlNamespace.Prefix, xmlNamespace.Uri);
}
return nsManager;
}
}
}

View File

@@ -259,19 +259,24 @@ internal class MappingConverter
Headers = headerMatchers.Any() ? headerMatchers.Select(hm => new HeaderModel
{
Name = hm.Name,
Matchers = _mapper.Map(hm.Matchers)
IgnoreCase = hm.IgnoreCase ? true : null,
RejectOnMatch = hm.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : null,
Matchers = _mapper.Map(hm.Matchers),
}).ToList() : null,
Cookies = cookieMatchers.Any() ? cookieMatchers.Select(cm => new CookieModel
{
Name = cm.Name,
IgnoreCase = cm.IgnoreCase ? true : null,
RejectOnMatch = cm.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : null,
Matchers = _mapper.Map(cm.Matchers)
}).ToList() : null,
Params = paramsMatchers.Any() ? paramsMatchers.Select(pm => new ParamModel
{
Name = pm.Key,
IgnoreCase = pm.IgnoreCase == true ? true : null,
IgnoreCase = pm.IgnoreCase ? true : null,
RejectOnMatch = pm.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : null,
Matchers = _mapper.Map(pm.Matchers)
}).ToList() : null
},

View File

@@ -101,7 +101,8 @@ internal class MatcherMapper
return new JmesPathMatcher(matchBehaviour, matchOperator, stringPatterns);
case nameof(XPathMatcher):
return new XPathMatcher(matchBehaviour, matchOperator, stringPatterns);
var xmlNamespaces = matcher.XmlNamespaceMap;
return new XPathMatcher(matchBehaviour, matchOperator, xmlNamespaces, stringPatterns);
case nameof(WildcardMatcher):
return new WildcardMatcher(matchBehaviour, stringPatterns, ignoreCase, matchOperator);
@@ -159,6 +160,10 @@ internal class MatcherMapper
case JsonPartialWildcardMatcher jsonPartialWildcardMatcher:
model.Regex = jsonPartialWildcardMatcher.Regex;
break;
case XPathMatcher xpathMatcher:
model.XmlNamespaceMap = xpathMatcher.XmlNamespaceMap;
break;
}
switch (matcher)

View File

@@ -1,4 +1,5 @@
using System;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using NFluent;
using WireMock.Matchers;
@@ -163,4 +164,30 @@ public class JmesPathMatcherTests
// Assert
Check.That(match).IsEqualTo(0.0);
}
[Fact]
public void JmesPathMatcher_IsMatch_MultiplePatternsUsingMatchOperatorAnd()
{
// Assign
var matcher = new JmesPathMatcher(MatchOperator.And, "things.x == 'RequiredThing'", "things.x == 'abc'");
// Act
double score = matcher.IsMatch(JObject.Parse("{ \"things\": { \"x\": \"RequiredThing\" } }")).Score;
// Assert
score.Should().Be(0);
}
[Fact]
public void JmesPathMatcher_IsMatch_MultiplePatternsUsingMatchOperatorOr()
{
// Assign
var matcher = new JmesPathMatcher(MatchOperator.Or, "things.x == 'RequiredThing'", "things.x == 'abc'");
// Act
double score = matcher.IsMatch(JObject.Parse("{ \"things\": { \"x\": \"RequiredThing\" } }")).Score;
// Assert
score.Should().Be(1);
}
}

View File

@@ -1,4 +1,5 @@
using System;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using NFluent;
using WireMock.Matchers;
@@ -324,4 +325,52 @@ public class JsonPathMatcherTests
// Assert
Check.That(match).IsEqualTo(1.0);
}
[Fact]
public void JsonPathMatcher_IsMatch_MultiplePatternsUsingMatchOperatorAnd()
{
// Assign
var matcher = new JsonPathMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.And, "$.arr[0].sub[0].subline1", "$.arr[0].line2");
// Act
double match = matcher.IsMatch(JObject.Parse(@"{
""name"": ""PathSelectorTest"",
""test"": ""test"",
""test2"": ""test2"",
""arr"": [{
""line1"": ""line1"",
""sub"":[
{
""subline1"":""subline1""
}]
}]
}")).Score;
// Assert
match.Should().Be(0);
}
[Fact]
public void JsonPathMatcher_IsMatch_MultiplePatternsUsingMatchOperatorOr()
{
// Assign
var matcher = new JsonPathMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, "$.arr[0].sub[0].subline2", "$.arr[0].line1");
// Act
double match = matcher.IsMatch(JObject.Parse(@"{
""name"": ""PathSelectorTest"",
""test"": ""test"",
""test2"": ""test2"",
""arr"": [{
""line1"": ""line1"",
""sub"":[
{
""subline1"":""subline1""
}]
}]
}")).Score;
// Assert
match.Should().Be(1);
}
}

View File

@@ -1,4 +1,5 @@
using NFluent;
using WireMock.Admin.Mappings;
using WireMock.Matchers;
using Xunit;
@@ -49,6 +50,71 @@ public class XPathMatcherTests
Check.That(result).IsEqualTo(1.0);
}
[Fact]
public void XPathMatcher_IsMatch_WithNamespaces_AcceptOnMatch()
{
// Assign
string input =
@"<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
<s:Body>
<QueryRequest xmlns=""urn://MyWcfService"">
<MaxResults i:nil=""true"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""/>
<Restriction i:nil=""true"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""/>
<SearchMode i:nil=""true"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""/>
</QueryRequest>
</s:Body>
</s:Envelope>";
var xmlNamespaces = new[]
{
new XmlNamespace { Prefix = "s", Uri = "http://schemas.xmlsoap.org/soap/envelope/" },
new XmlNamespace { Prefix = "i", Uri = "http://www.w3.org/2001/XMLSchema-instance" }
};
var matcher = new XPathMatcher(
MatchBehaviour.AcceptOnMatch,
MatchOperator.Or,
xmlNamespaces,
"/s:Envelope/s:Body/*[local-name()='QueryRequest' and namespace-uri()='urn://MyWcfService']");
// Act
double result = matcher.IsMatch(input).Score;
// Assert
Check.That(result).IsEqualTo(1.0);
}
[Fact]
public void XPathMatcher_IsMatch_WithNamespaces_OneSelfDefined_AcceptOnMatch()
{
// Assign
string input =
@"<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
<s:Body>
<QueryRequest xmlns=""urn://MyWcfService"">
<MaxResults i:nil=""true"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""/>
<Restriction i:nil=""true"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""/>
<SearchMode i:nil=""true"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""/>
</QueryRequest>
</s:Body>
</s:Envelope>";
var xmlNamespaces = new[]
{
new XmlNamespace { Prefix = "s", Uri = "http://schemas.xmlsoap.org/soap/envelope/" },
new XmlNamespace { Prefix = "i", Uri = "http://www.w3.org/2001/XMLSchema-instance" },
new XmlNamespace { Prefix = "q", Uri = "urn://MyWcfService" }
};
var matcher = new XPathMatcher(
MatchBehaviour.AcceptOnMatch,
MatchOperator.Or,
xmlNamespaces,
"/s:Envelope/s:Body/q:QueryRequest");
// Act
double result = matcher.IsMatch(input).Score;
// Assert
Check.That(result).IsEqualTo(1.0);
}
[Fact]
public void XPathMatcher_IsMatch_RejectOnMatch()
{
@@ -57,7 +123,7 @@ public class XPathMatcherTests
<todo-list>
<todo-item id='a1'>abc</todo-item>
</todo-list>";
var matcher = new XPathMatcher(MatchBehaviour.RejectOnMatch, MatchOperator.Or, "/todo-list[count(todo-item) = 1]");
var matcher = new XPathMatcher(MatchBehaviour.RejectOnMatch, MatchOperator.Or, null, "/todo-list[count(todo-item) = 1]");
// Act
double result = matcher.IsMatch(xml).Score;

View File

@@ -0,0 +1,122 @@
{
Guid: Guid_1,
UpdatedAt: DateTime_1,
Request: {
Headers: [
{
Name: MatchBehaviour.RejectOnMatch,
Matchers: [
{
Name: WildcardMatcher,
Pattern: hv-1,
IgnoreCase: true,
RejectOnMatch: true
}
],
IgnoreCase: true,
RejectOnMatch: true
},
{
Name: MatchBehaviour.AcceptOnMatch,
Matchers: [
{
Name: WildcardMatcher,
Pattern: hv-2,
IgnoreCase: true
}
],
IgnoreCase: true
},
{
Name: IgnoreCase_false,
Matchers: [
{
Name: WildcardMatcher,
Pattern: hv-3,
IgnoreCase: false
}
]
},
{
Name: IgnoreCase_true,
Matchers: [
{
Name: WildcardMatcher,
Pattern: hv-4,
IgnoreCase: true
}
],
IgnoreCase: true
},
{
Name: ExactMatcher,
Matchers: [
{
Name: ExactMatcher,
Pattern: h-exact,
IgnoreCase: false
}
]
}
],
Cookies: [
{
Name: MatchBehaviour.RejectOnMatch,
Matchers: [
{
Name: WildcardMatcher,
Pattern: cv-1,
IgnoreCase: true,
RejectOnMatch: true
}
],
IgnoreCase: true,
RejectOnMatch: true
},
{
Name: MatchBehaviour.AcceptOnMatch,
Matchers: [
{
Name: WildcardMatcher,
Pattern: cv-2,
IgnoreCase: true
}
],
IgnoreCase: true
},
{
Name: IgnoreCase_false,
Matchers: [
{
Name: WildcardMatcher,
Pattern: cv-3,
IgnoreCase: false
}
]
},
{
Name: IgnoreCase_true,
Matchers: [
{
Name: WildcardMatcher,
Pattern: cv-4,
IgnoreCase: true
}
],
IgnoreCase: true
},
{
Name: ExactMatcher,
Matchers: [
{
Name: ExactMatcher,
Pattern: c-exact,
IgnoreCase: false
}
]
}
]
},
Response: {},
UseWebhooksFireAndForget: false
}

View File

@@ -0,0 +1,59 @@
{
Guid: Guid_1,
UpdatedAt: DateTime_1,
Request: {
Params: [
{
Name: MatchBehaviour.RejectOnMatch,
RejectOnMatch: true
},
{
Name: MatchBehaviour.RejectOnMatch|IgnoreCase_false,
RejectOnMatch: true
},
{
Name: IgnoreCase_false,
Matchers: [
{
Name: ExactMatcher,
Pattern: pv-3a,
IgnoreCase: false
},
{
Name: ExactMatcher,
Pattern: pv-3b,
IgnoreCase: false
}
]
},
{
Name: IgnoreCase_true,
IgnoreCase: true,
Matchers: [
{
Name: ExactMatcher,
Pattern: pv-3a,
IgnoreCase: true
},
{
Name: ExactMatcher,
Pattern: pv-3b,
IgnoreCase: true
}
]
},
{
Name: ExactMatcher,
Matchers: [
{
Name: ExactMatcher,
Pattern: exact,
IgnoreCase: false
}
]
}
]
},
Response: {},
UseWebhooksFireAndForget: false
}

View File

@@ -5,6 +5,7 @@ using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using VerifyXunit;
using WireMock.Matchers;
using WireMock.Models;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
@@ -362,6 +363,60 @@ public partial class MappingConverterTests
return Verifier.Verify(model);
}
[Fact]
public Task ToMappingModel_WithHeader_And_Cookie_ReturnsCorrectModel()
{
// Assign
var request = Request.Create()
.WithHeader("MatchBehaviour.RejectOnMatch", "hv-1", MatchBehaviour.RejectOnMatch)
.WithHeader("MatchBehaviour.AcceptOnMatch", "hv-2", MatchBehaviour.AcceptOnMatch)
.WithHeader("IgnoreCase_false", "hv-3", false)
.WithHeader("IgnoreCase_true", "hv-4")
.WithHeader("ExactMatcher", new ExactMatcher("h-exact"))
.WithCookie("MatchBehaviour.RejectOnMatch", "cv-1", MatchBehaviour.RejectOnMatch)
.WithCookie("MatchBehaviour.AcceptOnMatch", "cv-2", MatchBehaviour.AcceptOnMatch)
.WithCookie("IgnoreCase_false", "cv-3", false)
.WithCookie("IgnoreCase_true", "cv-4")
.WithCookie("ExactMatcher", new ExactMatcher("c-exact"))
;
var response = Response.Create();
var mapping = new Mapping(_guid, _updatedAt, null, null, null, _settings, request, response, 0, null, null, null, null, null, false, null, data: null, probability: null);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
// Verify
return Verifier.Verify(model);
}
[Fact]
public Task ToMappingModel_WithParam_ReturnsCorrectModel()
{
// Assign
var request = Request.Create()
.WithParam("MatchBehaviour.RejectOnMatch", MatchBehaviour.RejectOnMatch)
.WithParam("MatchBehaviour.RejectOnMatch|IgnoreCase_false", false, MatchBehaviour.RejectOnMatch)
.WithParam("IgnoreCase_false", false, "pv-3a", "pv-3b")
.WithParam("IgnoreCase_true", true, "pv-3a", "pv-3b")
.WithParam("ExactMatcher", new ExactMatcher("exact"))
;
var response = Response.Create();
var mapping = new Mapping(_guid, _updatedAt, null, null, null, _settings, request, response, 0, null, null, null, null, null, false, null, data: null, probability: null);
// Act
var model = _sut.ToMappingModel(mapping);
// Assert
model.Should().NotBeNull();
// Verify
return Verifier.Verify(model);
}
#if GRAPHQL
[Fact]
public Task ToMappingModel_Request_WithBodyAsGraphQLSchema_ReturnsCorrectModel()

View File

@@ -145,6 +145,26 @@ public class MatcherMapperTests
model.IgnoreCase.Should().BeTrue();
}
[Fact]
public void MatcherMapper_Map_XPathMatcher()
{
// Assign
var xmlNamespaceMap = new[]
{
new XmlNamespace { Prefix = "s", Uri = "http://schemas.xmlsoap.org/soap/envelope/" },
new XmlNamespace { Prefix = "i", Uri = "http://www.w3.org/2001/XMLSchema-instance" },
new XmlNamespace { Prefix = "q", Uri = "urn://MyWcfService" }
};
var matcher = new XPathMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.And, xmlNamespaceMap);
// Act
var model = _sut.Map(matcher)!;
// Assert
model.XmlNamespaceMap.Should().NotBeNull();
model.XmlNamespaceMap.Should().BeEquivalentTo(xmlNamespaceMap);
}
[Fact]
public void MatcherMapper_Map_MatcherModel_Null()
{
@@ -522,4 +542,47 @@ public class MatcherMapperTests
matcher.ContentTypeMatcher.Should().BeAssignableTo<ContentTypeMatcher>().Which.GetPatterns().Should().ContainSingle("text/json");
}
#endif
[Fact]
public void MatcherMapper_Map_MatcherModel_XPathMatcher_WithXmlNamespaces_As_String()
{
// Assign
var pattern = "/s:Envelope/s:Body/*[local-name()='QueryRequest']";
var model = new MatcherModel
{
Name = "XPathMatcher",
Pattern = pattern,
XmlNamespaceMap = new[]
{
new XmlNamespace { Prefix = "s", Uri = "http://schemas.xmlsoap.org/soap/envelope/" }
}
};
// Act
var matcher = (XPathMatcher)_sut.Map(model)!;
// Assert
matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch);
matcher.XmlNamespaceMap.Should().NotBeNull();
matcher.XmlNamespaceMap.Should().HaveCount(1);
}
[Fact]
public void MatcherMapper_Map_MatcherModel_XPathMatcher_WithoutXmlNamespaces_As_String()
{
// Assign
var pattern = "/s:Envelope/s:Body/*[local-name()='QueryRequest']";
var model = new MatcherModel
{
Name = "XPathMatcher",
Pattern = pattern
};
// Act
var matcher = (XPathMatcher)_sut.Map(model)!;
// Assert
matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch);
matcher.XmlNamespaceMap.Should().BeNull();
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Net;
using System.Threading.Tasks;
using FluentAssertions;
using WireMock.Matchers;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
@@ -26,10 +27,10 @@ public partial class WireMockServerTests
};
var server = WireMockServer.Start(settings);
server.Given(
Request.Create()
.UsingGet()
.WithPath("/foo")
.WithParam("query", queryValue)
Request.Create()
.UsingGet()
.WithPath("/foo")
.WithParam("query", queryValue)
)
.RespondWith(
Response.Create().WithStatusCode(200)
@@ -52,10 +53,10 @@ public partial class WireMockServerTests
var queryValue = "1,2,3";
var server = WireMockServer.Start();
server.Given(
Request.Create()
.UsingGet()
.WithPath("/foo")
.WithParam("query", "1", "2", "3")
Request.Create()
.UsingGet()
.WithPath("/foo")
.WithParam("query", "1", "2", "3")
)
.RespondWith(
Response.Create().WithStatusCode(200)
@@ -70,4 +71,54 @@ public partial class WireMockServerTests
server.Stop();
}
[Fact]
public async Task WireMockServer_WithParam_RejectOnMatch_OnNonMatchingParam_ShouldReturnMappingOk()
{
// Arrange
var server = WireMockServer.Start();
server.Given(
Request.Create()
.WithPath("/v1/person/workers")
.WithParam("delta_from", MatchBehaviour.RejectOnMatch)
.UsingGet()
)
.RespondWith(
Response.Create()
);
// Act
var requestUri = new Uri($"http://localhost:{server.Port}/v1/person/workers?showsourcesystem=true&count=700&page=1&sections=personal%2Corganizations%2Cemployment");
var response = await server.CreateClient().GetAsync(requestUri).ConfigureAwait(false);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
server.Stop();
}
[Fact]
public async Task WireMockServer_WithParam_AcceptOnMatch_OnNonMatchingParam_ShouldReturnMappingOk()
{
// Arrange
var server = WireMockServer.Start();
server.Given(
Request.Create()
.WithPath("/v1/person/workers")
.WithParam("delta_from")
.UsingGet()
)
.RespondWith(
Response.Create()
);
// Act
var requestUri = new Uri($"http://localhost:{server.Port}/v1/person/workers?showsourcesystem=true&count=700&page=1&sections=personal%2Corganizations%2Cemployment");
var response = await server.CreateClient().GetAsync(requestUri).ConfigureAwait(false);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
server.Stop();
}
}