diff --git a/src/WireMock.Net.Minimal/Matchers/JSONPathMatcher.cs b/src/WireMock.Net.Minimal/Matchers/JSONPathMatcher.cs index b6e2488e..ae88144b 100644 --- a/src/WireMock.Net.Minimal/Matchers/JSONPathMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/JSONPathMatcher.cs @@ -1,6 +1,5 @@ // Copyright © WireMock.Net -using System.Linq; using AnyOfTypes; using Newtonsoft.Json.Linq; using Stef.Validation; diff --git a/src/WireMock.Net.Minimal/Matchers/SystemTextJsonPathMatcher.cs b/src/WireMock.Net.Minimal/Matchers/SystemTextJsonPathMatcher.cs new file mode 100644 index 00000000..35ca8d8f --- /dev/null +++ b/src/WireMock.Net.Minimal/Matchers/SystemTextJsonPathMatcher.cs @@ -0,0 +1,184 @@ +// Copyright © WireMock.Net + +using System.Text.Json.Nodes; +using AnyOfTypes; +using Json.Path; +using Stef.Validation; +using WireMock.Extensions; +using WireMock.Models; +using WireMock.Util; + +namespace WireMock.Matchers; + +/// +/// SystemTextJsonPathMatcher - behaves the same as but uses System.Text.Json instead of Newtonsoft.Json. +/// +/// +/// +public class SystemTextJsonPathMatcher : IStringMatcher, IObjectMatcher +{ + private readonly AnyOf[] _patterns; + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + public object Value { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + public SystemTextJsonPathMatcher(params string[] patterns) + : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns.ToAnyOfPatterns()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + public SystemTextJsonPathMatcher(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 SystemTextJsonPathMatcher( + MatchBehaviour matchBehaviour, + MatchOperator matchOperator = MatchOperator.Or, + params AnyOf[] patterns) + { + _patterns = Guard.NotNull(patterns); + MatchBehaviour = matchBehaviour; + MatchOperator = matchOperator; + Value = patterns; + } + + /// + public MatchResult IsMatch(string? input) + { + var score = MatchScores.Mismatch; + Exception? exception = null; + + if (!string.IsNullOrWhiteSpace(input)) + { + try + { + var node = JsonNode.Parse(input!); + score = IsMatchInternal(node); + } + catch (Exception ex) + { + exception = ex; + } + } + + return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); + } + + /// + public MatchResult IsMatch(object? input) + { + var score = MatchScores.Mismatch; + Exception? exception = null; + + // When input is null or byte[], return Mismatch. + if (input != null && input is not byte[]) + { + try + { + JsonNode? node = input switch + { + JsonNode jsonNode => jsonNode, + string str => JsonNode.Parse(str), + _ => JsonNode.Parse(System.Text.Json.JsonSerializer.Serialize(input)) + }; + + score = IsMatchInternal(node); + } + catch (Exception ex) + { + exception = ex; + } + } + + return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); + } + + /// + public AnyOf[] GetPatterns() + { + return _patterns; + } + + /// + public MatchOperator MatchOperator { get; } + + /// + public string Name => nameof(SystemTextJsonPathMatcher); + + /// + public string GetCSharpCodeArguments() + { + return $"new {Name}" + + $"(" + + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + + $"{MatchOperator.GetFullyQualifiedEnumValue()}, " + + $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" + + $")"; + } + + private double IsMatchInternal(JsonNode? node) + { + // JsonPath.Net requires the node to be inside an object or array for filter expressions. + // Similar to JsonPathMatcher's ConvertJTokenToJArrayIfNeeded, wrap a plain object in an array + // when it's an object with a single non-array child property. + var evaluationNode = WrapIfNeeded(node); + + var values = _patterns + .Select(pattern => + { + var path = JsonPath.Parse(pattern.GetPattern()); + var result = path.Evaluate(evaluationNode); + return result.Matches is { Count: > 0 }; + }) + .ToArray(); + + return MatchScores.ToScore(values, MatchOperator); + } + + // Mirrors JsonPathMatcher.ConvertJTokenToJArrayIfNeeded: + // If the node is an object with exactly one property whose value is not already an array, + // wrap that value in an array so that filter expressions (e.g. [?(@.x == y)]) can match. + private static JsonNode? WrapIfNeeded(JsonNode? node) + { + if (node is not JsonObject obj) + { + return node; + } + + var properties = obj.ToList(); + if (properties.Count != 1) + { + return node; + } + + var single = properties[0]; + if (single.Value is JsonArray) + { + return node; + } + + var clonedValue = JsonNode.Parse(single.Value?.ToJsonString() ?? "null"); + return new JsonObject + { + [single.Key] = new JsonArray(clonedValue) + }; + } +} diff --git a/src/WireMock.Net.Minimal/Serialization/MatcherMapper.cs b/src/WireMock.Net.Minimal/Serialization/MatcherMapper.cs index 76b3ee56..4d62e4da 100644 --- a/src/WireMock.Net.Minimal/Serialization/MatcherMapper.cs +++ b/src/WireMock.Net.Minimal/Serialization/MatcherMapper.cs @@ -106,9 +106,24 @@ internal class MatcherMapper var valueForJsonPartialWildcardMatcher = matcherModel.Pattern ?? matcherModel.Patterns; return new JsonPartialWildcardMatcher(matchBehaviour, valueForJsonPartialWildcardMatcher!, ignoreCase, useRegex); + case nameof(SystemTextJsonMatcher): + var valueForSystemTextJsonMatcher = matcherModel.Pattern ?? matcherModel.Patterns; + return new SystemTextJsonMatcher(matchBehaviour, valueForSystemTextJsonMatcher!, ignoreCase, useRegex); + + case nameof(SystemTextJsonPartialMatcher): + var valueForSystemTextJsonPartialMatcher = matcherModel.Pattern ?? matcherModel.Patterns; + return new SystemTextJsonPartialMatcher(matchBehaviour, valueForSystemTextJsonPartialMatcher!, ignoreCase, useRegex); + + case nameof(SystemTextJsonPartialWildcardMatcher): + var valueForSystemTextJsonPartialWildcardMatcher = matcherModel.Pattern ?? matcherModel.Patterns; + return new SystemTextJsonPartialWildcardMatcher(matchBehaviour, valueForSystemTextJsonPartialWildcardMatcher!, ignoreCase, useRegex); + case nameof(JsonPathMatcher): return new JsonPathMatcher(matchBehaviour, matchOperator, stringPatterns); + case nameof(SystemTextJsonPathMatcher): + return new SystemTextJsonPathMatcher(matchBehaviour, matchOperator, stringPatterns); + case nameof(JmesPathMatcher): return new JmesPathMatcher(matchBehaviour, matchOperator, stringPatterns); @@ -171,6 +186,10 @@ internal class MatcherMapper model.Regex = jsonMatcher.Regex; break; + case SystemTextJsonMatcher stjMatcher: + model.Regex = stjMatcher.Regex; + break; + case XPathMatcher xpathMatcher: model.XmlNamespaceMap = xpathMatcher.XmlNamespaceMap; break; diff --git a/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj b/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj index 5906f981..f6943bb7 100644 --- a/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj +++ b/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj @@ -38,6 +38,7 @@ + diff --git a/test/WireMock.Net.Tests/Matchers/SystemTextJsonPathMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/SystemTextJsonPathMatcherTests.cs new file mode 100644 index 00000000..e4849a96 --- /dev/null +++ b/test/WireMock.Net.Tests/Matchers/SystemTextJsonPathMatcherTests.cs @@ -0,0 +1,400 @@ +// Copyright © WireMock.Net + +using System.Text.Json.Nodes; +using WireMock.Matchers; + +namespace WireMock.Net.Tests.Matchers; + +public class SystemTextJsonPathMatcherTests +{ + [Fact] + public void SystemTextJsonPathMatcher_GetName() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher("X"); + + // Act + string name = matcher.Name; + + // Assert + name.Should().Be("SystemTextJsonPathMatcher"); + } + + [Fact] + public void SystemTextJsonPathMatcher_GetPatterns() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher("X"); + + // Act + var patterns = matcher.GetPatterns(); + + // Assert + patterns.Should().ContainSingle("X"); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_ByteArray() + { + // Arrange + var bytes = new byte[0]; + var matcher = new SystemTextJsonPathMatcher("$.Id"); + + // Act + double match = matcher.IsMatch(bytes).Score; + + // Assert + match.Should().Be(0); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_NullString() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher("$.Id"); + + // Act + double match = matcher.IsMatch(null).Score; + + // Assert + match.Should().Be(0); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_EmptyString() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher("$.Id"); + + // Act + double match = matcher.IsMatch(string.Empty).Score; + + // Assert + match.Should().Be(0); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_NullObject() + { + // Arrange + object? o = null; + var matcher = new SystemTextJsonPathMatcher("$.Id"); + + // Act + double match = matcher.IsMatch(o).Score; + + // Assert + match.Should().Be(0); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_String_Exception_Mismatch() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher("$.Id"); + + // Act + double match = matcher.IsMatch("not-json").Score; + + // Assert + match.Should().Be(0); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_AnonymousObject() + { + // Arrange - RFC 9535: filter expression requires an array context + var matcher = new SystemTextJsonPathMatcher("$[?(@.Id == 1)]"); + + // Act + double match = matcher.IsMatch(new[] { new { Id = 1, Name = "Test" } }).Score; + + // Assert + match.Should().Be(1); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_AnonymousObject_WithNestedObject() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher("$.things[?(@.name == 'x')]"); + + // Act + double match = matcher.IsMatch(new { things = new { name = "x" } }).Score; + + // Assert + match.Should().Be(1); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_String_WithNestedObject() + { + // Arrange + var json = "{ \"things\": { \"name\": \"x\" } }"; + var matcher = new SystemTextJsonPathMatcher("$.things[?(@.name == 'x')]"); + + // Act + double match = matcher.IsMatch(json).Score; + + // Assert + match.Should().Be(1); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsNoMatch_String_WithNestedObject() + { + // Arrange + var json = "{ \"things\": { \"name\": \"y\" } }"; + var matcher = new SystemTextJsonPathMatcher("$.things[?(@.name == 'x')]"); + + // Act + double match = matcher.IsMatch(json).Score; + + // Assert + match.Should().Be(0); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_JsonNode() + { + // Arrange - RFC 9535: filter expression requires an array context + string[] patterns = { "$[?(@.Id == 1)]" }; + var matcher = new SystemTextJsonPathMatcher(patterns); + + // Act + var node = JsonNode.Parse("[{\"Id\":1,\"Name\":\"Test\"}]"); + double match = matcher.IsMatch(node).Score; + + // Assert + match.Should().Be(1); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_JsonNode_Parsed() + { + // Arrange - RFC 9535: filter expression requires an array context + var matcher = new SystemTextJsonPathMatcher("$[?(@.Id == 1)]"); + + // Act + double match = matcher.IsMatch(JsonNode.Parse("[{\"Id\":1,\"Name\":\"Test\"}]")).Score; + + // Assert + match.Should().Be(1); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_RejectOnMatch() + { + // Arrange - RFC 9535: filter expression requires an array context + var matcher = new SystemTextJsonPathMatcher(MatchBehaviour.RejectOnMatch, MatchOperator.Or, "$[?(@.Id == 1)]"); + + // Act + double match = matcher.IsMatch(JsonNode.Parse("[{\"Id\":1,\"Name\":\"Test\"}]")).Score; + + // Assert + match.Should().Be(0.0); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_ArrayOneLevel() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher("$.arr[0].line1"); + + // Act + double match = matcher.IsMatch(JsonNode.Parse(@"{ + ""name"": ""PathSelectorTest"", + ""test"": ""test"", + ""test2"": ""test2"", + ""arr"": [{ + ""line1"": ""line1"" + }] + }")).Score; + + // Assert + match.Should().Be(1.0); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_ObjectMatch() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher("$.test"); + + // Act + double match = matcher.IsMatch(JsonNode.Parse(@"{ + ""name"": ""PathSelectorTest"", + ""test"": ""test"", + ""test2"": ""test2"", + ""arr"": [ + { + ""line1"": ""line1"" + } + ] + }")).Score; + + // Assert + match.Should().Be(1.0); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_DoesntMatch() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher("$.test3"); + + // Act + double match = matcher.IsMatch(JsonNode.Parse(@"{ + ""name"": ""PathSelectorTest"", + ""test"": ""test"", + ""test2"": ""test2"", + ""arr"": [ + { + ""line1"": ""line1"" + } + ] + }")).Score; + + // Assert + match.Should().Be(0.0); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_DoesntMatchInArray() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher("$arr[0].line1"); + + // Act + double match = matcher.IsMatch(JsonNode.Parse(@"{ + ""name"": ""PathSelectorTest"", + ""test"": ""test"", + ""test2"": ""test2"", + ""arr"": [] + }")).Score; + + // Assert + match.Should().Be(0.0); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_DoesntMatchNoObjectsInArray() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher("$arr[2].line1"); + + // Act + double match = matcher.IsMatch(JsonNode.Parse(@"{ + ""name"": ""PathSelectorTest"", + ""test"": ""test"", + ""test2"": ""test2"", + ""arr"": [] + }")).Score; + + // Assert + match.Should().Be(0.0); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_NestedArrays() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher("$.arr[0].sub[0].subline1"); + + // Act + double match = matcher.IsMatch(JsonNode.Parse(@"{ + ""name"": ""PathSelectorTest"", + ""test"": ""test"", + ""test2"": ""test2"", + ""arr"": [{ + ""line1"": ""line1"", + ""sub"":[ + { + ""subline1"":""subline1"" + }] + }] + }")).Score; + + // Assert + match.Should().Be(1.0); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_MultiplePatternsUsingMatchOperatorAnd() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.And, "$.arr[0].sub[0].subline1", "$.arr[0].line2"); + + // Act + double match = matcher.IsMatch(JsonNode.Parse(@"{ + ""name"": ""PathSelectorTest"", + ""test"": ""test"", + ""test2"": ""test2"", + ""arr"": [{ + ""line1"": ""line1"", + ""sub"":[ + { + ""subline1"":""subline1"" + }] + }] + }")).Score; + + // Assert + match.Should().Be(0); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_MultiplePatternsUsingMatchOperatorOr() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, "$.arr[0].sub[0].subline2", "$.arr[0].line1"); + + // Act + double match = matcher.IsMatch(JsonNode.Parse(@"{ + ""name"": ""PathSelectorTest"", + ""test"": ""test"", + ""test2"": ""test2"", + ""arr"": [{ + ""line1"": ""line1"", + ""sub"":[ + { + ""subline1"":""subline1"" + }] + }] + }")).Score; + + // Assert + match.Should().Be(1); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_String_ArrayOneLevel() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher("$.arr[0].line1"); + + // Act + double match = matcher.IsMatch(@"{ + ""name"": ""PathSelectorTest"", + ""arr"": [{ + ""line1"": ""line1"" + }] + }").Score; + + // Assert + match.Should().Be(1.0); + } + + [Fact] + public void SystemTextJsonPathMatcher_IsMatch_String_DoesntMatch() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher("$.test3"); + + // Act + double match = matcher.IsMatch(@"{ ""test"": ""test"" }").Score; + + // Assert + match.Should().Be(0.0); + } +} diff --git a/test/WireMock.Net.Tests/Serialization/MatcherMapperTests.cs b/test/WireMock.Net.Tests/Serialization/MatcherMapperTests.cs index 580bfdf8..46e7b916 100644 --- a/test/WireMock.Net.Tests/Serialization/MatcherMapperTests.cs +++ b/test/WireMock.Net.Tests/Serialization/MatcherMapperTests.cs @@ -510,11 +510,31 @@ message HelloReply { } [Fact] - public void MatcherMapper_Map_MatcherModel_JsonPartialMatcher_Patterns_As_Object() + public void MatcherMapper_Map_MatcherModel_JsonPartialMatcher_Patterns_1_Value_As_Object() + { + // Assign + object pattern = new { post1 = "value1", post2 = "value2" }; + var patterns = new[] { pattern }; + var model = new MatcherModel + { + Name = "JsonPartialMatcher", + Patterns = patterns + }; + + // Act + var matcher = (JsonPartialMatcher)_sut.Map(model)!; + + // Assert + matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); + matcher.Value.Should().BeEquivalentTo(patterns); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_JsonPartialMatcher_Patterns_2_Values_As_Object() { // Assign object pattern1 = new { AccountIds = new[] { 1, 2, 3 } }; - object pattern2 = new { X = "x" }; + object pattern2 = new { post1 = "value1", post2 = "value2" }; var patterns = new[] { pattern1, pattern2 }; var model = new MatcherModel { @@ -523,7 +543,7 @@ message HelloReply { }; // Act - var matcher = (IJsonMatcher)_sut.Map(model)!; + var matcher = (JsonPartialMatcher)_sut.Map(model)!; // Assert matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); @@ -531,36 +551,33 @@ message HelloReply { } [Fact] - public void MatcherMapper_Map_MatcherModel_JsonPartialMatcher_StringPattern_With_PatternAsFile() + public void MatcherMapper_Map_MatcherModel_JsonPartialWildcardMatcher_Pattern_As_String() { // Assign - var pattern = new StringPattern { Pattern = "{ \"AccountIds\": [ 1, 2, 3 ] }", PatternAsFile = "pf" }; + var pattern = "{ \"Name\": \"T*\" }"; var model = new MatcherModel { - Name = "JsonPartialMatcher", - Pattern = pattern, - Regex = true + Name = "JsonPartialWildcardMatcher", + Pattern = pattern }; // Act - var matcher = (JsonPartialMatcher)_sut.Map(model)!; + var matcher = (JsonPartialWildcardMatcher)_sut.Map(model)!; // Assert matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); - matcher.Value.Should().BeEquivalentTo(pattern); - matcher.Regex.Should().BeTrue(); + matcher.Value.Should().Be(pattern); } [Fact] - public void MatcherMapper_Map_MatcherModel_JsonPartialWildcardMatcher_Patterns_As_Object() + public void MatcherMapper_Map_MatcherModel_JsonPartialWildcardMatcher_Pattern_As_Object() { // Assign object pattern = new { X = "*" }; var model = new MatcherModel { Name = "JsonPartialWildcardMatcher", - Pattern = pattern, - Regex = false + Pattern = pattern }; // Act @@ -569,202 +586,304 @@ message HelloReply { // Assert matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); matcher.Value.Should().BeEquivalentTo(pattern); - matcher.Regex.Should().BeFalse(); } [Fact] - public void MatcherMapper_Map_MatcherModel_NotNullOrEmptyMatcher() + public void MatcherMapper_Map_MatcherModel_JsonPartialWildcardMatcher_RegexTrue() { // Assign + var pattern = "{ \"x\": \"^\\\\d+$\" }"; var model = new MatcherModel { - Name = "NotNullOrEmptyMatcher", - RejectOnMatch = true - }; - - // Act - var matcher = _sut.Map(model)!; - - // Assert - matcher.Should().BeAssignableTo(); - matcher.MatchBehaviour.Should().Be(MatchBehaviour.RejectOnMatch); - } - - [Fact] - public void MatcherMapper_Map_MatcherModel_MimePartMatcher() - { - // Assign - var model = new MatcherModel - { - Name = "MimePartMatcher", - ContentMatcher = new MatcherModel - { - Name = "ExactMatcher", - Pattern = "x" - }, - ContentDispositionMatcher = new MatcherModel - { - Name = "WildcardMatcher", - Pattern = "y" - }, - ContentTransferEncodingMatcher = new MatcherModel - { - Name = "RegexMatcher", - Pattern = "z" - }, - ContentTypeMatcher = new MatcherModel - { - Name = "ContentTypeMatcher", - Pattern = "text/json" - } - }; - - // Act - var matcher = (MimePartMatcher)_sut.Map(model)!; - - // Assert - matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); - matcher.ContentMatcher.Should().BeAssignableTo().Which.GetPatterns().Should().ContainSingle("x"); - matcher.ContentDispositionMatcher.Should().BeAssignableTo().Which.GetPatterns().Should().ContainSingle("y"); - matcher.ContentTransferEncodingMatcher.Should().BeAssignableTo().Which.GetPatterns().Should().ContainSingle("z"); - matcher.ContentTypeMatcher.Should().BeAssignableTo().Which.GetPatterns().Should().ContainSingle("text/json"); - } - - [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 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", + Name = "JsonPartialWildcardMatcher", + Regex = true, Pattern = pattern }; // Act - var matcher = (XPathMatcher)_sut.Map(model)!; + var matcher = (JsonPartialWildcardMatcher)_sut.Map(model)!; // Assert matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); - matcher.XmlNamespaceMap.Should().BeNull(); + matcher.Regex.Should().BeTrue(); + matcher.Value.Should().Be(pattern); } [Fact] - public void MatcherMapper_Map_MatcherModel_CSharpCodeMatcher() + public void MatcherMapper_Map_MatcherModel_JsonPartialWildcardMatcher_RejectOnMatch() { // Assign + var pattern = "{ \"x\": \"*\" }"; var model = new MatcherModel { - Name = "CSharpCodeMatcher", - Patterns = ["return it == \"x\";"] - }; - var sut = new MatcherMapper(new WireMockServerSettings { AllowCSharpCodeMatcher = true }); - - // Act 1 - var matcher1 = (ICSharpCodeMatcher)sut.Map(model)!; - - // Assert 1 - matcher1.Should().NotBeNull(); - matcher1.IsMatch("x").Score.Should().Be(1.0d); - - // Act 2 - var matcher2 = (ICSharpCodeMatcher)sut.Map(model)!; - - // Assert 2 - matcher2.Should().NotBeNull(); - matcher2.IsMatch("x").Score.Should().Be(1.0d); - } - - [Fact] - public void MatcherMapper_Map_MatcherModel_CSharpCodeMatcher_NotAllowed_ThrowsException() - { - // Assign - var model = new MatcherModel - { - Name = "CSharpCodeMatcher", - Patterns = ["x"] - }; - var sut = new MatcherMapper(new WireMockServerSettings { AllowCSharpCodeMatcher = false }); - - // Act - Action action = () => sut.Map(model); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void MatcherMapper_Map_MatcherModel_ExactMatcher_Pattern() - { - // Assign - var model = new MatcherModel - { - Name = "ExactMatcher", - Patterns = ["x"] + Name = "JsonPartialWildcardMatcher", + Pattern = pattern, + RejectOnMatch = true }; // Act - var matcher = (ExactMatcher)_sut.Map(model)!; + var matcher = (JsonPartialWildcardMatcher)_sut.Map(model)!; // Assert - matcher.GetPatterns().Should().ContainSingle("x"); + matcher.MatchBehaviour.Should().Be(MatchBehaviour.RejectOnMatch); + matcher.Value.Should().Be(pattern); } [Fact] - public void MatcherMapper_Map_MatcherModel_ExactMatcher_Patterns() + public void MatcherMapper_Map_MatcherModel_JsonPartialWildcardMatcher_IgnoreCaseTrue() { // Assign + var pattern = "{ \"name\": \"t*\" }"; var model = new MatcherModel { - Name = "ExactMatcher", - Patterns = ["x", "y"] + Name = "JsonPartialWildcardMatcher", + Pattern = pattern, + IgnoreCase = true }; // Act - var matcher = (ExactMatcher)_sut.Map(model)!; + var matcher = (JsonPartialWildcardMatcher)_sut.Map(model)!; // Assert - matcher.GetPatterns().Should().ContainInOrder("x", "y"); + matcher.IgnoreCase.Should().BeTrue(); + matcher.Value.Should().Be(pattern); + } + + #region SystemTextJsonMatcher + + [Fact] + public void MatcherMapper_Map_MatcherModel_SystemTextJsonMatcher_Pattern_As_String() + { + // Assign + var pattern = "{ \"AccountIds\": [ 1, 2, 3 ] }"; + var model = new MatcherModel + { + Name = "SystemTextJsonMatcher", + Pattern = pattern + }; + + // Act + var matcher = (SystemTextJsonMatcher)_sut.Map(model)!; + + // Assert + matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); + matcher.Value.Should().BeEquivalentTo(pattern); + matcher.Regex.Should().BeFalse(); } [Fact] - public void MatcherMapper_Map_MatcherModel_JsonPartialMatcher_RegexFalse() + public void MatcherMapper_Map_MatcherModel_SystemTextJsonMatcher_Pattern_As_Object() + { + // Assign + var pattern = new { AccountIds = new[] { 1, 2, 3 } }; + var model = new MatcherModel + { + Name = "SystemTextJsonMatcher", + Pattern = pattern + }; + + // Act + var matcher = (SystemTextJsonMatcher)_sut.Map(model)!; + + // Assert + matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); + matcher.Value.Should().BeEquivalentTo(pattern); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_SystemTextJsonMatcher_Patterns_As_String() + { + // Assign + var pattern1 = "{ \"AccountIds\": [ 1, 2, 3 ] }"; + var pattern2 = "{ \"post1\": \"value1\" }"; + object[] patterns = [pattern1, pattern2]; + var model = new MatcherModel + { + Name = "SystemTextJsonMatcher", + Patterns = patterns + }; + + // Act + var matcher = (SystemTextJsonMatcher)_sut.Map(model)!; + + // Assert + matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); + matcher.Value.Should().BeEquivalentTo(patterns); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_SystemTextJsonMatcher_RejectOnMatch() { // Assign var pattern = "{ \"x\": 1 }"; var model = new MatcherModel { - Name = "JsonPartialMatcher", + Name = "SystemTextJsonMatcher", + Pattern = pattern, + RejectOnMatch = true + }; + + // Act + var matcher = (SystemTextJsonMatcher)_sut.Map(model)!; + + // Assert + matcher.MatchBehaviour.Should().Be(MatchBehaviour.RejectOnMatch); + matcher.Value.Should().Be(pattern); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_SystemTextJsonMatcher_IgnoreCaseTrue() + { + // Assign + var pattern = "{ \"x\": 1 }"; + var model = new MatcherModel + { + Name = "SystemTextJsonMatcher", + Pattern = pattern, + IgnoreCase = true + }; + + // Act + var matcher = (SystemTextJsonMatcher)_sut.Map(model)!; + + // Assert + matcher.IgnoreCase.Should().BeTrue(); + matcher.Value.Should().Be(pattern); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_SystemTextJsonMatcher_RegexTrue() + { + // Assign + var pattern = "{ \"x\": \"^\\\\d+$\" }"; + var model = new MatcherModel + { + Name = "SystemTextJsonMatcher", + Pattern = pattern, + Regex = true + }; + + // Act + var matcher = (SystemTextJsonMatcher)_sut.Map(model)!; + + // Assert + matcher.Regex.Should().BeTrue(); + matcher.Value.Should().Be(pattern); + } + + [Fact] + public void MatcherMapper_Map_Matcher_SystemTextJsonMatcher_To_MatcherModel() + { + // Assign + var pattern = new { Id = 1, Name = "Test" }; + var matcher = new SystemTextJsonMatcher(MatchBehaviour.AcceptOnMatch, pattern, ignoreCase: true, regex: true); + + // Act + var model = _sut.Map(matcher)!; + + // Assert + model.Name.Should().Be(nameof(SystemTextJsonMatcher)); + model.Pattern.Should().BeEquivalentTo(pattern); + model.IgnoreCase.Should().BeTrue(); + model.Regex.Should().BeTrue(); + model.RejectOnMatch.Should().BeNull(); + } + + [Fact] + public void MatcherMapper_Map_Matcher_SystemTextJsonMatcher_RejectOnMatch_To_MatcherModel() + { + // Assign + var pattern = "{ \"Id\": 1 }"; + var matcher = new SystemTextJsonMatcher(MatchBehaviour.RejectOnMatch, pattern); + + // Act + var model = _sut.Map(matcher)!; + + // Assert + model.Name.Should().Be(nameof(SystemTextJsonMatcher)); + model.Pattern.Should().Be(pattern); + model.RejectOnMatch.Should().BeTrue(); + model.Regex.Should().BeFalse(); + } + + #endregion + + #region SystemTextJsonPartialMatcher + + [Fact] + public void MatcherMapper_Map_MatcherModel_SystemTextJsonPartialMatcher_Pattern_As_String() + { + // Assign + var pattern = "{ \"AccountIds\": [ 1, 2, 3 ] }"; + var model = new MatcherModel + { + Name = "SystemTextJsonPartialMatcher", + Pattern = pattern + }; + + // Act + var matcher = (SystemTextJsonPartialMatcher)_sut.Map(model)!; + + // Assert + matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); + matcher.Value.Should().BeEquivalentTo(pattern); + matcher.Regex.Should().BeFalse(); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_SystemTextJsonPartialMatcher_Pattern_As_Object() + { + // Assign + var pattern = new { AccountIds = new[] { 1, 2, 3 } }; + var model = new MatcherModel + { + Name = "SystemTextJsonPartialMatcher", + Pattern = pattern + }; + + // Act + var matcher = (SystemTextJsonPartialMatcher)_sut.Map(model)!; + + // Assert + matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); + matcher.Value.Should().BeEquivalentTo(pattern); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_SystemTextJsonPartialMatcher_Patterns_As_String() + { + // Assign + var pattern1 = "{ \"AccountIds\": [ 1, 2, 3 ] }"; + var pattern2 = "{ \"X\": \"x\" }"; + object[] patterns = [pattern1, pattern2]; + var model = new MatcherModel + { + Name = "SystemTextJsonPartialMatcher", + Patterns = patterns + }; + + // Act + var matcher = (SystemTextJsonPartialMatcher)_sut.Map(model)!; + + // Assert + matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); + matcher.Value.Should().BeEquivalentTo(patterns); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_SystemTextJsonPartialMatcher_RegexFalse() + { + // Assign + var pattern = "{ \"x\": 1 }"; + var model = new MatcherModel + { + Name = "SystemTextJsonPartialMatcher", Regex = false, Pattern = pattern }; // Act - var matcher = (JsonPartialMatcher)_sut.Map(model)!; + var matcher = (SystemTextJsonPartialMatcher)_sut.Map(model)!; // Assert matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); @@ -774,19 +893,19 @@ message HelloReply { } [Fact] - public void MatcherMapper_Map_MatcherModel_JsonPartialMatcher_RegexTrue() + public void MatcherMapper_Map_MatcherModel_SystemTextJsonPartialMatcher_RegexTrue() { // Assign var pattern = "{ \"x\": 1 }"; var model = new MatcherModel { - Name = "JsonPartialMatcher", + Name = "SystemTextJsonPartialMatcher", Regex = true, Pattern = pattern }; // Act - var matcher = (JsonPartialMatcher)_sut.Map(model)!; + var matcher = (SystemTextJsonPartialMatcher)_sut.Map(model)!; // Assert matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); @@ -796,336 +915,325 @@ message HelloReply { } [Fact] - public void MatcherMapper_Map_MatcherModel_ExactObjectMatcher_ValidBase64StringPattern() + public void MatcherMapper_Map_MatcherModel_SystemTextJsonPartialMatcher_RejectOnMatch() { // Assign + var pattern = "{ \"x\": 1 }"; var model = new MatcherModel { - Name = "ExactObjectMatcher", - Patterns = ["c3RlZg=="] + Name = "SystemTextJsonPartialMatcher", + Pattern = pattern, + RejectOnMatch = true }; // Act - var matcher = (ExactObjectMatcher)_sut.Map(model)!; + var matcher = (SystemTextJsonPartialMatcher)_sut.Map(model)!; // Assert - ((byte[])matcher.Value).Should().BeEquivalentTo(new byte[] { 115, 116, 101, 102 }); + matcher.MatchBehaviour.Should().Be(MatchBehaviour.RejectOnMatch); + matcher.Value.Should().Be(pattern); } [Fact] - public void MatcherMapper_Map_MatcherModel_ExactObjectMatcher_InvalidBase64StringPattern() + public void MatcherMapper_Map_MatcherModel_SystemTextJsonPartialMatcher_IgnoreCaseTrue() { // Assign + var pattern = "{ \"x\": 1 }"; var model = new MatcherModel { - Name = "ExactObjectMatcher", - Patterns = ["_"] + Name = "SystemTextJsonPartialMatcher", + Pattern = pattern, + IgnoreCase = true }; // Act - Action act = () => _sut.Map(model); + var matcher = (SystemTextJsonPartialMatcher)_sut.Map(model)!; // Assert - act.Should().Throw(); - } - - [Theory] - [InlineData(MatchOperator.Or, 1.0d)] - [InlineData(MatchOperator.And, 0.0d)] - [InlineData(MatchOperator.Average, 0.5d)] - public void MatcherMapper_Map_MatcherModel_RegexMatcher(MatchOperator matchOperator, double expected) - { - // Assign - var model = new MatcherModel - { - Name = "RegexMatcher", - Patterns = ["x", "y"], - IgnoreCase = true, - MatchOperator = matchOperator.ToString() - }; - - // Act - var matcher = (RegexMatcher)_sut.Map(model)!; - - // Assert - matcher.GetPatterns().Should().ContainInOrder("x", "y"); - - var result = matcher.IsMatch("X"); - result.Score.Should().Be(expected); - } - - [Theory] - [InlineData(MatchOperator.Or, 1.0d)] - [InlineData(MatchOperator.And, 0.0d)] - [InlineData(MatchOperator.Average, 0.5d)] - public void MatcherMapper_Map_MatcherModel_WildcardMatcher_IgnoreCase(MatchOperator matchOperator, double expected) - { - // Assign - var model = new MatcherModel - { - Name = "WildcardMatcher", - Patterns = ["x", "y"], - IgnoreCase = true, - MatchOperator = matchOperator.ToString() - }; - - // Act - var matcher = (WildcardMatcher)_sut.Map(model)!; - - // Assert - matcher.GetPatterns().Should().ContainInOrder("x", "y"); - - var result = matcher.IsMatch("X"); - result.Score.Should().Be(expected); + matcher.IgnoreCase.Should().BeTrue(); + matcher.Value.Should().Be(pattern); } [Fact] - public void MatcherMapper_Map_MatcherModel_WildcardMatcher_With_PatternAsFile() - { - // Arrange - var file = "c:\\test.txt"; - var fileContent = "c"; - var stringPattern = new StringPattern - { - Pattern = fileContent, - PatternAsFile = file - }; - var fileSystemHandleMock = new Mock(); - fileSystemHandleMock.Setup(f => f.ReadFileAsString(file)).Returns(fileContent); - - var model = new MatcherModel - { - Name = "WildcardMatcher", - PatternAsFile = file - }; - - var settings = new WireMockServerSettings - { - FileSystemHandler = fileSystemHandleMock.Object - }; - var sut = new MatcherMapper(settings); - - // Act - var matcher = (WildcardMatcher)sut.Map(model)!; - - // Assert - matcher.GetPatterns().Should().HaveCount(1).And.Contain(new AnyOf(stringPattern)); - - var result = matcher.IsMatch("c"); - result.Score.Should().Be(MatchScores.Perfect); - } - - [Fact] - public void MatcherMapper_Map_MatcherModel_SimMetricsMatcher() + public void MatcherMapper_Map_Matcher_SystemTextJsonPartialMatcher_To_MatcherModel() { // Assign - var model = new MatcherModel - { - Name = "SimMetricsMatcher", - Pattern = "x" - }; - - // Act - var matcher = (SimMetricsMatcher)_sut.Map(model)!; - - // Assert - matcher.GetPatterns().Should().ContainSingle("x"); - } - - [Fact] - public void MatcherMapper_Map_MatcherModel_SimMetricsMatcher_BlockDistance() - { - // Assign - var model = new MatcherModel - { - Name = "SimMetricsMatcher.BlockDistance", - Pattern = "x" - }; - - // Act - var matcher = (SimMetricsMatcher)_sut.Map(model)!; - - // Assert - matcher.GetPatterns().Should().ContainSingle("x"); - } - - [Fact] - public void MatcherMapper_Map_MatcherModel_SimMetricsMatcher_Throws1() - { - // Assign - var model = new MatcherModel - { - Name = "error", - Pattern = "x" - }; - - // Act - Action act = () => _sut.Map(model); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void MatcherMapper_Map_MatcherModel_SimMetricsMatcher_Throws2() - { - // Assign - var model = new MatcherModel - { - Name = "SimMetricsMatcher.error", - Pattern = "x" - }; - - // Act - Action act = () => _sut.Map(model); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void MatcherMapper_Map_MatcherModel_MatcherModelToCustomMatcher() - { - // Arrange - var patternModel = new CustomPathParamMatcherModel("/customer/{customerId}/document/{documentId}", - new Dictionary(2) - { - { "customerId", @"^[0-9]+$" }, - { "documentId", @"^[0-9a-zA-Z\-\_]+\.[a-zA-Z]+$" } - }); - var model = new MatcherModel - { - Name = nameof(CustomPathParamMatcher), - Pattern = JsonConvert.SerializeObject(patternModel) - }; - - var settings = new WireMockServerSettings(); - settings.CustomMatcherMappings = 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 - ); - }; - var sut = new MatcherMapper(settings); - - // Act - var matcher = sut.Map(model) as CustomPathParamMatcher; - - // Assert - matcher.Should().NotBeNull(); - } - - [Fact] - public void MatcherMapper_Map_MatcherModel_CustomMatcherToMatcherModel() - { - // Arrange - var matcher = new CustomPathParamMatcher("/customer/{customerId}/document/{documentId}", - new Dictionary(2) - { - { "customerId", @"^[0-9]+$" }, - { "documentId", @"^[0-9a-zA-Z\-\_]+\.[a-zA-Z]+$" } - }); + var pattern = new { Id = 1, Name = "Test" }; + var matcher = new SystemTextJsonPartialMatcher(MatchBehaviour.AcceptOnMatch, pattern, ignoreCase: true, regex: true); // Act var model = _sut.Map(matcher)!; // Assert - using (new AssertionScope()) - { - model.Should().NotBeNull(); - model.Name.Should().Be(nameof(CustomPathParamMatcher)); - - var matcherParams = JsonConvert.DeserializeObject((string)model.Pattern!)!; - matcherParams.Path.Should().Be("/customer/{customerId}/document/{documentId}"); - matcherParams.PathParams.Should().BeEquivalentTo(new Dictionary(2) - { - { "customerId", @"^[0-9]+$" }, - { "documentId", @"^[0-9a-zA-Z\-\_]+\.[a-zA-Z]+$" } - }); - } + model.Name.Should().Be(nameof(SystemTextJsonPartialMatcher)); + model.Pattern.Should().BeEquivalentTo(pattern); + model.IgnoreCase.Should().BeTrue(); + model.Regex.Should().BeTrue(); + model.RejectOnMatch.Should().BeNull(); } [Fact] - public void MatcherMapper_Map_MatcherModel_GraphQLMatcher() + public void MatcherMapper_Map_Matcher_SystemTextJsonPartialMatcher_RejectOnMatch_To_MatcherModel() { - // Arrange - const string testSchema = @" - scalar DateTime - scalar MyCustomScalar + // Assign + var pattern = "{ \"Id\": 1 }"; + var matcher = new SystemTextJsonPartialMatcher(MatchBehaviour.RejectOnMatch, pattern); - type Message { - id: ID! - } + // Act + var model = _sut.Map(matcher)!; - type Mutation { - createMessage(x: MyCustomScalar, dt: DateTime): Message - }"; + // Assert + model.Name.Should().Be(nameof(SystemTextJsonPartialMatcher)); + model.Pattern.Should().Be(pattern); + model.RejectOnMatch.Should().BeTrue(); + model.Regex.Should().BeFalse(); + } - var customScalars = new Dictionary { { "MyCustomScalar", typeof(string) } }; + #endregion + + #region SystemTextJsonPartialWildcardMatcher + + [Fact] + public void MatcherMapper_Map_MatcherModel_SystemTextJsonPartialWildcardMatcher_Pattern_As_Object() + { + // Assign + object pattern = new { X = "*" }; var model = new MatcherModel { - Name = nameof(GraphQLMatcher), - Pattern = testSchema, - CustomScalars = customScalars + Name = "SystemTextJsonPartialWildcardMatcher", + Pattern = pattern, + Regex = false }; // Act - var matcher = (IGraphQLMatcher)_sut.Map(model)!; + var matcher = (SystemTextJsonPartialWildcardMatcher)_sut.Map(model)!; // Assert - matcher.GetPatterns().Should().HaveElementAt(0, testSchema); - matcher.Name.Should().Be(nameof(GraphQLMatcher)); - matcher.CustomScalars.Should().BeEquivalentTo(customScalars); + matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); + matcher.Value.Should().BeEquivalentTo(pattern); + matcher.Regex.Should().BeFalse(); } [Fact] - public void MatcherMapper_Map_MatcherModel_ProtoBufMatcher() + public void MatcherMapper_Map_MatcherModel_SystemTextJsonPartialWildcardMatcher_Pattern_As_String() { - // Arrange - const string protoDefinition = @" -syntax = ""proto3""; - -package greet; - -service Greeter { - rpc SayHello (HelloRequest) returns (HelloReply); -} - -message HelloRequest { - string name = 1; -} - -message HelloReply { - string message = 1; -} -"; - const string messageType = "greet.HelloRequest"; - - var jsonMatcherPattern = new { name = "stef" }; - + // Assign + var pattern = "{ \"Name\": \"T*\" }"; var model = new MatcherModel { - Name = nameof(ProtoBufMatcher), - Pattern = protoDefinition, - ProtoBufMessageType = messageType, - ContentMatcher = new MatcherModel - { - Name = nameof(JsonMatcher), - Pattern = jsonMatcherPattern - } + Name = "SystemTextJsonPartialWildcardMatcher", + Pattern = pattern }; // Act - var matcher = (ProtoBufMatcher)_sut.Map(model)!; + var matcher = (SystemTextJsonPartialWildcardMatcher)_sut.Map(model)!; // Assert - matcher.ProtoDefinition().Texts.Should().ContainSingle(protoDefinition); - matcher.Name.Should().Be(nameof(ProtoBufMatcher)); - matcher.MessageType.Should().Be(messageType); - matcher.Matcher?.Value.Should().Be(jsonMatcherPattern); + matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); + matcher.Value.Should().Be(pattern); } + + [Fact] + public void MatcherMapper_Map_MatcherModel_SystemTextJsonPartialWildcardMatcher_RegexTrue() + { + // Assign + var pattern = "{ \"x\": \"^\\\\d+$\" }"; + var model = new MatcherModel + { + Name = "SystemTextJsonPartialWildcardMatcher", + Regex = true, + Pattern = pattern + }; + + // Act + var matcher = (SystemTextJsonPartialWildcardMatcher)_sut.Map(model)!; + + // Assert + matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); + matcher.Regex.Should().BeTrue(); + matcher.Value.Should().Be(pattern); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_SystemTextJsonPartialWildcardMatcher_RejectOnMatch() + { + // Assign + var pattern = "{ \"x\": \"*\" }"; + var model = new MatcherModel + { + Name = "SystemTextJsonPartialWildcardMatcher", + Pattern = pattern, + RejectOnMatch = true + }; + + // Act + var matcher = (SystemTextJsonPartialWildcardMatcher)_sut.Map(model)!; + + // Assert + matcher.MatchBehaviour.Should().Be(MatchBehaviour.RejectOnMatch); + matcher.Value.Should().Be(pattern); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_SystemTextJsonPartialWildcardMatcher_IgnoreCaseTrue() + { + // Assign + var pattern = "{ \"name\": \"t*\" }"; + var model = new MatcherModel + { + Name = "SystemTextJsonPartialWildcardMatcher", + Pattern = pattern, + IgnoreCase = true + }; + + // Act + var matcher = (SystemTextJsonPartialWildcardMatcher)_sut.Map(model)!; + + // Assert + matcher.IgnoreCase.Should().BeTrue(); + matcher.Value.Should().Be(pattern); + } + + [Fact] + public void MatcherMapper_Map_Matcher_SystemTextJsonPartialWildcardMatcher_To_MatcherModel() + { + // Assign + var pattern = new { Id = 1, Name = "T*" }; + var matcher = new SystemTextJsonPartialWildcardMatcher(MatchBehaviour.AcceptOnMatch, pattern, ignoreCase: true); + + // Act + var model = _sut.Map(matcher)!; + + // Assert + model.Name.Should().Be(nameof(SystemTextJsonPartialWildcardMatcher)); + model.Pattern.Should().BeEquivalentTo(pattern); + model.IgnoreCase.Should().BeTrue(); + model.Regex.Should().BeFalse(); + model.RejectOnMatch.Should().BeNull(); + } + + [Fact] + public void MatcherMapper_Map_Matcher_SystemTextJsonPartialWildcardMatcher_RejectOnMatch_To_MatcherModel() + { + // Assign + var pattern = "{ \"Name\": \"T*\" }"; + var matcher = new SystemTextJsonPartialWildcardMatcher(MatchBehaviour.RejectOnMatch, pattern); + + // Act + var model = _sut.Map(matcher)!; + + // Assert + model.Name.Should().Be(nameof(SystemTextJsonPartialWildcardMatcher)); + model.Pattern.Should().Be(pattern); + model.RejectOnMatch.Should().BeTrue(); + model.Regex.Should().BeFalse(); + } + + #endregion + + #region SystemTextJsonPathMatcher + + [Fact] + public void MatcherMapper_Map_MatcherModel_SystemTextJsonPathMatcher_SinglePattern() + { + // Arrange + var model = new MatcherModel + { + Name = "SystemTextJsonPathMatcher", + Pattern = "$.Id" + }; + + // Act + var matcher = (SystemTextJsonPathMatcher)_sut.Map(model)!; + + // Assert + matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch); + matcher.GetPatterns().Should().ContainSingle().Which.First.Should().Be("$.Id"); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_SystemTextJsonPathMatcher_MultiplePatterns() + { + // Arrange + var model = new MatcherModel + { + Name = "SystemTextJsonPathMatcher", + Patterns = ["$.Id", "$.Name"], + MatchOperator = "And" + }; + + // Act + var matcher = (SystemTextJsonPathMatcher)_sut.Map(model)!; + + // Assert + matcher.MatchOperator.Should().Be(MatchOperator.And); + matcher.GetPatterns().Select(p => p.First).Should().BeEquivalentTo("$.Id", "$.Name"); + } + + [Fact] + public void MatcherMapper_Map_MatcherModel_SystemTextJsonPathMatcher_RejectOnMatch() + { + // Arrange + var model = new MatcherModel + { + Name = "SystemTextJsonPathMatcher", + Pattern = "$.Id", + RejectOnMatch = true + }; + + // Act + var matcher = (SystemTextJsonPathMatcher)_sut.Map(model)!; + + // Assert + matcher.MatchBehaviour.Should().Be(MatchBehaviour.RejectOnMatch); + } + + [Fact] + public void MatcherMapper_Map_Matcher_SystemTextJsonPathMatcher_To_MatcherModel_SinglePattern() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher("$.Id"); + + // Act + var model = _sut.Map(matcher)!; + + // Assert + model.Name.Should().Be(nameof(SystemTextJsonPathMatcher)); + model.Pattern.Should().Be("$.Id"); + model.Patterns.Should().BeNull(); + model.RejectOnMatch.Should().BeNull(); + } + + [Fact] + public void MatcherMapper_Map_Matcher_SystemTextJsonPathMatcher_To_MatcherModel_MultiplePatterns() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.And, "$.Id", "$.Name"); + + // Act + var model = _sut.Map(matcher)!; + + // Assert + model.Name.Should().Be(nameof(SystemTextJsonPathMatcher)); + model.Pattern.Should().BeNull(); + model.Patterns.Should().BeEquivalentTo(["$.Id", "$.Name"]); + model.MatchOperator.Should().Be("And"); + } + + [Fact] + public void MatcherMapper_Map_Matcher_SystemTextJsonPathMatcher_RejectOnMatch_To_MatcherModel() + { + // Arrange + var matcher = new SystemTextJsonPathMatcher(MatchBehaviour.RejectOnMatch, MatchOperator.Or, "$.Id"); + + // Act + var model = _sut.Map(matcher)!; + + // Assert + model.Name.Should().Be(nameof(SystemTextJsonPathMatcher)); + model.Pattern.Should().Be("$.Id"); + model.RejectOnMatch.Should().BeTrue(); + } + + #endregion } \ No newline at end of file