diff --git a/src/WireMock.Net.Minimal/Matchers/AbstractSystemTextJsonPartialMatcher.cs b/src/WireMock.Net.Minimal/Matchers/AbstractSystemTextJsonPartialMatcher.cs new file mode 100644 index 00000000..76a70cd5 --- /dev/null +++ b/src/WireMock.Net.Minimal/Matchers/AbstractSystemTextJsonPartialMatcher.cs @@ -0,0 +1,173 @@ +// Copyright © WireMock.Net + +using System.Text.Json; +using WireMock.Util; + +namespace WireMock.Matchers; + +/// +/// Generic AbstractSystemTextJsonPartialMatcher - uses System.Text.Json instead of Newtonsoft.Json. +/// +public abstract class AbstractSystemTextJsonPartialMatcher : SystemTextJsonMatcher +{ + /// + /// Initializes a new instance of the class. + /// + protected AbstractSystemTextJsonPartialMatcher(string value, bool ignoreCase = false, bool regex = false) + : base(value, ignoreCase, regex) + { + } + + /// + /// Initializes a new instance of the class. + /// + protected AbstractSystemTextJsonPartialMatcher(object value, bool ignoreCase = false, bool regex = false) + : base(value, ignoreCase, regex) + { + } + + /// + /// Initializes a new instance of the class. + /// + protected AbstractSystemTextJsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false) + : base(matchBehaviour, value, ignoreCase, regex) + { + } + + /// + protected override bool IsMatch(JsonElement value, JsonElement? input) + { + if (input == null) + { + return false; + } + + var inputElement = input.Value; + + // Regex on a string value + if (Regex && value.ValueKind == JsonValueKind.String) + { + var valueAsString = value.GetString()!; + var inputAsString = GetStringValue(inputElement); + + var (valid, result) = RegexUtils.MatchRegex(valueAsString, inputAsString); + if (valid) + { + return result; + } + } + + // Guid comparison: both strings, both parseable as Guid + if (value.ValueKind == JsonValueKind.String && inputElement.ValueKind == JsonValueKind.String) + { + var valueStr = value.GetString()!; + var inputStr = inputElement.GetString()!; + if (Guid.TryParse(valueStr, out var vg) && Guid.TryParse(inputStr, out var ig)) + { + return IsMatch(vg.ToString(), ig.ToString()); + } + } + + // Type mismatch (after regex/guid checks) + if (value.ValueKind != inputElement.ValueKind) + { + return false; + } + + switch (value.ValueKind) + { + case JsonValueKind.Object: + { + var nestedValues = value.EnumerateObject().ToArray(); + if (nestedValues.Length == 0) + { + return true; + } + + return nestedValues.All(pair => + { + var selected = SelectElement(inputElement, pair.Name); + return selected != null && IsMatch(pair.Value, selected.Value); + }); + } + + case JsonValueKind.Array: + { + var valuesArray = value.EnumerateArray().ToArray(); + if (valuesArray.Length == 0) + { + return true; + } + + var tokenArray = inputElement.EnumerateArray().ToArray(); + if (tokenArray.Length == 0) + { + return false; + } + + return valuesArray.All(subFilter => tokenArray.Any(subToken => IsMatch(subFilter, subToken))); + } + + default: + return IsMatch(GetStringValue(value), GetStringValue(inputElement)); + } + } + + /// + /// Check if two strings are a match (matching can be done exact or wildcard). + /// + protected abstract bool IsMatch(string value, string input); + + /// + /// Selects a from an object using a key that may be a plain property name, + /// a dotted path (e.g. "a.b.c") or bracket notation (e.g. "['name.with.dot']"), + /// mirroring Newtonsoft's SelectToken + direct indexer fallback. + /// + private static JsonElement? SelectElement(JsonElement input, string key) + { + if (input.ValueKind != JsonValueKind.Object) + { + return null; + } + + // Direct property access (also handles keys containing colons or dots that are literal property names) + if (input.TryGetProperty(key, out var direct)) + { + return direct; + } + + // Bracket notation: ['property.name.with.dots'] + if (key.StartsWith("['") && key.EndsWith("']")) + { + var propertyName = key.Substring(2, key.Length - 4); + return input.TryGetProperty(propertyName, out var bracketValue) ? bracketValue : null; + } + + // Dotted path: a.b.c + if (key.Contains('.')) + { + var parts = key.Split('.'); + var current = input; + foreach (var part in parts) + { + if (current.ValueKind != JsonValueKind.Object || !current.TryGetProperty(part, out var next)) + { + return null; + } + + current = next; + } + + return current; + } + + return null; + } + + private static string GetStringValue(JsonElement element) + { + return element.ValueKind == JsonValueKind.String + ? element.GetString()! + : element.GetRawText(); + } +} diff --git a/src/WireMock.Net.Minimal/Matchers/SystemTextJsonPartialMatcher.cs b/src/WireMock.Net.Minimal/Matchers/SystemTextJsonPartialMatcher.cs new file mode 100644 index 00000000..e431f89c --- /dev/null +++ b/src/WireMock.Net.Minimal/Matchers/SystemTextJsonPartialMatcher.cs @@ -0,0 +1,52 @@ +// Copyright © WireMock.Net + +using WireMock.Extensions; +using WireMock.Util; + +namespace WireMock.Matchers; + +/// +/// SystemTextJsonPartialMatcher - uses System.Text.Json instead of Newtonsoft.Json. +/// +public class SystemTextJsonPartialMatcher : AbstractSystemTextJsonPartialMatcher +{ + /// + public override string Name => nameof(SystemTextJsonPartialMatcher); + + /// + public SystemTextJsonPartialMatcher(string value, bool ignoreCase = false, bool regex = false) + : base(value, ignoreCase, regex) + { + } + + /// + public SystemTextJsonPartialMatcher(object value, bool ignoreCase = false, bool regex = false) + : base(value, ignoreCase, regex) + { + } + + /// + public SystemTextJsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false) + : base(matchBehaviour, value, ignoreCase, regex) + { + } + + /// + protected override bool IsMatch(string value, string input) + { + var exactStringMatcher = new ExactMatcher(MatchBehaviour.AcceptOnMatch, IgnoreCase, MatchOperator.Or, value); + return exactStringMatcher.IsMatch(input).IsPerfect(); + } + + /// + public override string GetCSharpCodeArguments() + { + return $"new {Name}" + + $"(" + + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + + $"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " + + $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " + + $"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" + + $")"; + } +} diff --git a/src/WireMock.Net.Minimal/Matchers/SystemTextJsonPartialWildcardMatcher.cs b/src/WireMock.Net.Minimal/Matchers/SystemTextJsonPartialWildcardMatcher.cs new file mode 100644 index 00000000..924c9434 --- /dev/null +++ b/src/WireMock.Net.Minimal/Matchers/SystemTextJsonPartialWildcardMatcher.cs @@ -0,0 +1,52 @@ +// Copyright © WireMock.Net + +using WireMock.Extensions; +using WireMock.Util; + +namespace WireMock.Matchers; + +/// +/// SystemTextJsonPartialWildcardMatcher - uses System.Text.Json instead of Newtonsoft.Json. +/// +public class SystemTextJsonPartialWildcardMatcher : AbstractSystemTextJsonPartialMatcher +{ + /// + public override string Name => nameof(SystemTextJsonPartialWildcardMatcher); + + /// + public SystemTextJsonPartialWildcardMatcher(string value, bool ignoreCase = false, bool regex = false) + : base(value, ignoreCase, regex) + { + } + + /// + public SystemTextJsonPartialWildcardMatcher(object value, bool ignoreCase = false, bool regex = false) + : base(value, ignoreCase, regex) + { + } + + /// + public SystemTextJsonPartialWildcardMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false) + : base(matchBehaviour, value, ignoreCase, regex) + { + } + + /// + protected override bool IsMatch(string value, string input) + { + var wildcardStringMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, value, IgnoreCase); + return wildcardStringMatcher.IsMatch(input).IsPerfect(); + } + + /// + public override string GetCSharpCodeArguments() + { + return $"new {Name}" + + $"(" + + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + + $"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " + + $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " + + $"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" + + $")"; + } +} diff --git a/test/WireMock.Net.Tests/Matchers/SystemTextJsonPartialMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/SystemTextJsonPartialMatcherTests.cs new file mode 100644 index 00000000..809ec318 --- /dev/null +++ b/test/WireMock.Net.Tests/Matchers/SystemTextJsonPartialMatcherTests.cs @@ -0,0 +1,410 @@ +// Copyright © WireMock.Net + +using System.Text.Json; +using WireMock.Matchers; + +namespace WireMock.Net.Tests.Matchers; + +public class SystemTextJsonPartialMatcherTests +{ + [Fact] + public void SystemTextJsonPartialMatcher_GetName() + { + // Assign + var matcher = new SystemTextJsonPartialMatcher("{}"); + + // Act + string name = matcher.Name; + + // Assert + name.Should().Be("SystemTextJsonPartialMatcher"); + } + + [Fact] + public void SystemTextJsonPartialMatcher_GetValue() + { + // Assign + var matcher = new SystemTextJsonPartialMatcher("{}"); + + // Act + object value = matcher.Value; + + // Assert + value.Should().Be("{}"); + } + + [Fact] + public void SystemTextJsonPartialMatcher_WithInvalidStringValue_Should_ThrowException() + { + // Act + Action action = () => new SystemTextJsonPartialMatcher(MatchBehaviour.AcceptOnMatch, "{ \"Id\""); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void SystemTextJsonPartialMatcher_WithInvalidObjectValue_Should_ThrowException() + { + // Act + Action action = () => new SystemTextJsonPartialMatcher(MatchBehaviour.AcceptOnMatch, new MemoryStream()); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_WithInvalidValue_Should_ReturnMismatch_And_Exception_ShouldBeSet() + { + // Assign + var matcher = new SystemTextJsonPartialMatcher("{}"); + + // Act + var result = matcher.IsMatch(new MemoryStream()); + + // Assert + result.Score.Should().Be(MatchScores.Mismatch); + result.Exception.Should().NotBeNull(); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_ByteArray() + { + // Assign + var bytes = new byte[0]; + var matcher = new SystemTextJsonPartialMatcher("{}"); + + // Act + double match = matcher.IsMatch(bytes).Score; + + // Assert + match.Should().Be(0); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_NullString() + { + // Assign + string? s = null; + var matcher = new SystemTextJsonPartialMatcher("{}"); + + // Act + double match = matcher.IsMatch(s).Score; + + // Assert + match.Should().Be(0); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_NullObject() + { + // Assign + object? o = null; + var matcher = new SystemTextJsonPartialMatcher("{}"); + + // Act + double match = matcher.IsMatch(o).Score; + + // Assert + match.Should().Be(0); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_JsonArray() + { + // Assign + var matcher = new SystemTextJsonPartialMatcher(new[] { "x", "y" }); + + // Act + double match = matcher.IsMatch("[ \"x\", \"y\" ]").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_JsonObject() + { + // Assign + var matcher = new SystemTextJsonPartialMatcher(new { Id = 1, Name = "Test" }); + + // Act + double match = matcher.IsMatch("{ \"Id\" : 1, \"Name\" : \"Test\" }").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_WithRegexTrue() + { + // Assign + var matcher = new SystemTextJsonPartialMatcher(new { Id = "^\\d+$", Name = "Test" }, false, true); + + // Act + double match = matcher.IsMatch("{ \"Id\" : \"1\", \"Name\" : \"Test\" }").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_WithRegexFalse() + { + // Assign + var matcher = new SystemTextJsonPartialMatcher(new { Id = "^\\d+$", Name = "Test" }); + + // Act + double match = matcher.IsMatch("{ \"Id\" : 1, \"Name\" : \"Test\" }").Score; + + // Assert + Assert.Equal(0.0, match); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_GuidAsString_UsingRegex() + { + var guid = new Guid("1111238e-b775-44a9-a263-95e570135c94"); + var matcher = new SystemTextJsonPartialMatcher(new + { + Id = 1, + Name = "^1111[a-fA-F0-9]{4}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$" + }, false, true); + + // Act + double match = matcher.IsMatch($"{{ \"Id\" : 1, \"Name\" : \"{guid}\" }}").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_WithIgnoreCaseTrue_JsonObject() + { + // Assign + var matcher = new SystemTextJsonPartialMatcher(new { id = 1, Name = "test" }, true); + + // Act + double match = matcher.IsMatch("{ \"Id\" : 1, \"NaMe\" : \"Test\" }").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_JsonObjectParsed() + { + // Assign + var matcher = new SystemTextJsonPartialMatcher(new { Id = 1, Name = "Test" }); + + // Act + double match = matcher.IsMatch("{ \"Id\" : 1, \"Name\" : \"Test\" }").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_WithIgnoreCaseTrue_JsonObjectParsed() + { + // Assign + var matcher = new SystemTextJsonPartialMatcher(new { Id = 1, Name = "TESt" }, true); + + // Act + double match = matcher.IsMatch("{ \"Id\" : 1, \"Name\" : \"Test\" }").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_JsonArrayAsString() + { + // Assign + var matcher = new SystemTextJsonPartialMatcher("[ \"x\", \"y\" ]"); + + // Act + double match = matcher.IsMatch("[ \"x\", \"y\" ]").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_JsonObjectAsString() + { + // Assign + var matcher = new SystemTextJsonPartialMatcher("{ \"Id\" : 1, \"Name\" : \"Test\" }"); + + // Act + double match = matcher.IsMatch("{ \"Id\" : 1, \"Name\" : \"Test\" }").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_JsonObjectAsStringWithDottedPropertyName() + { + // Assign + var matcher = new SystemTextJsonPartialMatcher("{ \"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User\" : \"Test\" }"); + + // Act + double match = matcher.IsMatch("{ \"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User\" : \"Test\" }").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_GuidAsString() + { + // Assign + var guid = Guid.NewGuid(); + var matcher = new SystemTextJsonPartialMatcher(new { Id = 1, Name = guid }); + + // Act + double match = matcher.IsMatch($"{{ \"Id\" : 1, \"Name\" : \"{guid}\" }}").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_WithIgnoreCaseTrue_JsonObjectAsString() + { + // Assign + var matcher = new SystemTextJsonPartialMatcher("{ \"Id\" : 1, \"Name\" : \"test\" }", true); + + // Act + double match = matcher.IsMatch("{ \"Id\" : 1, \"Name\" : \"Test\" }").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_JsonObjectAsString_RejectOnMatch() + { + // Assign + var matcher = new SystemTextJsonPartialMatcher(MatchBehaviour.RejectOnMatch, "{ \"Id\" : 1, \"Name\" : \"Test\" }"); + + // Act + double match = matcher.IsMatch("{ \"Id\" : 1, \"Name\" : \"Test\" }").Score; + + // Assert + Assert.Equal(0.0, match); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_JsonObjectWithDateTimeOffsetAsString() + { + // Assign + var matcher = new SystemTextJsonPartialMatcher("{ \"preferredAt\" : \"2019-11-21T10:32:53.2210009+00:00\" }"); + + // Act + double match = matcher.IsMatch("{ \"preferredAt\" : \"2019-11-21T10:32:53.2210009+00:00\" }").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Theory] + [InlineData("{\"test\":\"abc\"}", "{\"test\":\"abc\",\"other\":\"xyz\"}")] + [InlineData("\"test\"", "\"test\"")] + [InlineData("123", "123")] + [InlineData("[\"test\"]", "[\"test\"]")] + [InlineData("[\"test\"]", "[\"test\", \"other\"]")] + [InlineData("[123]", "[123]")] + [InlineData("[123]", "[123, 456]")] + [InlineData("{ \"test\":\"value\" }", "{\"test\":\"value\",\"other\":123}")] + [InlineData("{ \"test\":\"value\" }", "{\"test\":\"value\"}")] + [InlineData("{\"test\":{\"nested\":\"value\"}}", "{\"test\":{\"nested\":\"value\"}}")] + public void SystemTextJsonPartialMatcher_IsMatch_StringInputValidMatch(string value, string input) + { + // Assign + var matcher = new SystemTextJsonPartialMatcher(value); + + // Act + double match = matcher.IsMatch(input).Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Theory] + [InlineData("\"test\"", null)] + [InlineData("\"test1\"", "\"test2\"")] + [InlineData("123", "1234")] + [InlineData("[\"test\"]", "[\"test1\"]")] + [InlineData("[\"test\"]", "[\"test1\", \"test2\"]")] + [InlineData("[123]", "[1234]")] + [InlineData("{}", "\"test\"")] + [InlineData("{ \"test\":\"value\" }", "{\"test\":\"value2\"}")] + [InlineData("{ \"test.nested\":\"value\" }", "{\"test\":{\"nested\":\"value1\"}}")] + [InlineData("{\"test\":{\"test1\":\"value\"}}", "{\"test\":{\"test1\":\"value1\"}}")] + [InlineData("[{ \"test.nested\":\"value\" }]", "[{\"test\":{\"nested\":\"value1\"}}]")] + public void SystemTextJsonPartialMatcher_IsMatch_StringInputWithInvalidMatch(string value, string? input) + { + // Assign + var matcher = new SystemTextJsonPartialMatcher(value); + + // Act + double match = matcher.IsMatch(input).Score; + + // Assert + Assert.Equal(0.0, match); + } + + [Theory] + [InlineData("{ \"test.nested\":123 }", "{\"test\":{\"nested\":123}}")] + [InlineData("{ \"test.nested\":[123, 456] }", "{\"test\":{\"nested\":[123, 456]}}")] + [InlineData("{ \"test.nested\":\"value\" }", "{\"test\":{\"nested\":\"value\"}}")] + [InlineData("{ \"['name.with.dot']\":\"value\" }", "{\"name.with.dot\":\"value\"}")] + [InlineData("[{ \"test.nested\":\"value\" }]", "[{\"test\":{\"nested\":\"value\"}}]")] + [InlineData("[{ \"['name.with.dot']\":\"value\" }]", "[{\"name.with.dot\":\"value\"}]")] + public void SystemTextJsonPartialMatcher_IsMatch_ValueAsPathValidMatch(string value, string input) + { + // Assign + var matcher = new SystemTextJsonPartialMatcher(value); + + // Act + double match = matcher.IsMatch(input).Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Theory] + [InlineData("{ \"test.nested\":123 }", "{\"test\":{\"nested\":456}}")] + [InlineData("{ \"test.nested\":[123, 456] }", "{\"test\":{\"nested\":[1, 2]}}")] + [InlineData("{ \"test.nested\":\"value\" }", "{\"test\":{\"nested\":\"value1\"}}")] + [InlineData("{ \"['name.with.dot']\":\"value\" }", "{\"name.with.dot\":\"value1\"}")] + [InlineData("[{ \"test.nested\":\"value\" }]", "[{\"test\":{\"nested\":\"value1\"}}]")] + [InlineData("[{ \"['name.with.dot']\":\"value\" }]", "[{\"name.with.dot\":\"value1\"}]")] + public void SystemTextJsonPartialMatcher_IsMatch_ValueAsPathInvalidMatch(string value, string input) + { + // Assign + var matcher = new SystemTextJsonPartialMatcher(value); + + // Act + double match = matcher.IsMatch(input).Score; + + // Assert + Assert.Equal(0.0, match); + } + + [Fact] + public void SystemTextJsonPartialMatcher_IsMatch_JsonElement_ShouldMatch() + { + // Assign + var matcher = new SystemTextJsonPartialMatcher(new { Id = 1, Name = "Test" }); + + // Act + var jsonElement = JsonDocument.Parse("{ \"Id\" : 1, \"Name\" : \"Test\", \"Extra\" : \"value\" }").RootElement; + double match = matcher.IsMatch(jsonElement).Score; + + // Assert + Assert.Equal(1.0, match); + } +} diff --git a/test/WireMock.Net.Tests/Matchers/SystemTextJsonPartialWildcardMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/SystemTextJsonPartialWildcardMatcherTests.cs new file mode 100644 index 00000000..f5d936a7 --- /dev/null +++ b/test/WireMock.Net.Tests/Matchers/SystemTextJsonPartialWildcardMatcherTests.cs @@ -0,0 +1,381 @@ +// Copyright © WireMock.Net + +using System.Text.Json; +using WireMock.Matchers; + +namespace WireMock.Net.Tests.Matchers; + +public class SystemTextJsonPartialWildcardMatcherTests +{ + [Fact] + public void SystemTextJsonPartialWildcardMatcher_GetName() + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher("{}"); + + // Act + var name = matcher.Name; + + // Assert + name.Should().Be("SystemTextJsonPartialWildcardMatcher"); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_GetValue() + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher("{}"); + + // Act + var value = matcher.Value; + + // Assert + value.Should().Be("{}"); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_WithInvalidStringValue_Should_ThrowException() + { + // Act + Action action = () => new SystemTextJsonPartialWildcardMatcher(MatchBehaviour.AcceptOnMatch, "{ \"Id\""); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_WithInvalidObjectValue_Should_ThrowException() + { + // Act + Action action = () => new SystemTextJsonPartialWildcardMatcher(MatchBehaviour.AcceptOnMatch, new MemoryStream()); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_WithInvalidValue_Should_ReturnMismatch_And_Exception_ShouldBeSet() + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher("{}"); + + // Act + var result = matcher.IsMatch(new MemoryStream()); + + // Assert + result.Score.Should().Be(MatchScores.Mismatch); + result.Exception.Should().NotBeNull(); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_ByteArray() + { + // Assign + var bytes = new byte[0]; + var matcher = new SystemTextJsonPartialWildcardMatcher("{}"); + + // Act + var match = matcher.IsMatch(bytes).Score; + + // Assert + match.Should().Be(0); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_NullString() + { + // Assign + string? s = null; + var matcher = new SystemTextJsonPartialWildcardMatcher("{}"); + + // Act + var match = matcher.IsMatch(s).Score; + + // Assert + match.Should().Be(0); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_NullObject() + { + // Assign + object? o = null; + var matcher = new SystemTextJsonPartialWildcardMatcher("{}"); + + // Act + var match = matcher.IsMatch(o).Score; + + // Assert + match.Should().Be(0); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_JsonArray() + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher(new[] { "x", "y" }); + + // Act + var match = matcher.IsMatch("[ \"x\", \"y\" ]").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_JsonObject() + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher(new { Id = 1, Name = "Test" }); + + // Act + var match = matcher.IsMatch("{ \"Id\" : 1, \"Name\" : \"Test\" }").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_WithIgnoreCaseTrue_JsonObject() + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher(new { id = 1, Name = "test" }, true); + + // Act + var match = matcher.IsMatch("{ \"Id\" : 1, \"NaMe\" : \"Test\" }").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_JsonObjectParsed() + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher(new { Id = 1, Name = "Test" }); + + // Act + var match = matcher.IsMatch("{ \"Id\" : 1, \"Name\" : \"Test\" }").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_WithIgnoreCaseTrue_JsonObjectParsed() + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher(new { Id = 1, Name = "TESt" }, true); + + // Act + var match = matcher.IsMatch("{ \"Id\" : 1, \"Name\" : \"Test\" }").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_JsonArrayAsString() + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher("[ \"x\", \"y\" ]"); + + // Act + var match = matcher.IsMatch("[ \"x\", \"y\" ]").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_JsonObjectAsString() + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher("{ \"Id\" : 1, \"Name\" : \"Test\" }"); + + // Act + var match = matcher.IsMatch("{ \"Id\" : 1, \"Name\" : \"Test\" }").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_WithIgnoreCaseTrue_JsonObjectAsString() + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher("{ \"Id\" : 1, \"Name\" : \"test\" }", true); + + // Act + var match = matcher.IsMatch("{ \"Id\" : 1, \"Name\" : \"Test\" }").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_JsonObjectAsString_RejectOnMatch() + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher(MatchBehaviour.RejectOnMatch, "{ \"Id\" : 1, \"Name\" : \"Test\" }"); + + // Act + var match = matcher.IsMatch("{ \"Id\" : 1, \"Name\" : \"Test\" }").Score; + + // Assert + Assert.Equal(0.0, match); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_JsonObjectWithDateTimeOffsetAsString() + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher("{ \"preferredAt\" : \"2019-11-21T10:32:53.2210009+00:00\" }"); + + // Act + var match = matcher.IsMatch("{ \"preferredAt\" : \"2019-11-21T10:32:53.2210009+00:00\" }").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Theory] + [InlineData("{\"test\":\"abc\"}", "{\"test\":\"abc\",\"other\":\"xyz\"}")] + [InlineData("\"test\"", "\"test\"")] + [InlineData("123", "123")] + [InlineData("[\"test\"]", "[\"test\"]")] + [InlineData("[\"test\"]", "[\"test\", \"other\"]")] + [InlineData("[123]", "[123]")] + [InlineData("[123]", "[123, 456]")] + [InlineData("{ \"test\":\"value\" }", "{\"test\":\"value\",\"other\":123}")] + [InlineData("{ \"test\":\"value\" }", "{\"test\":\"value\"}")] + [InlineData("{\"test\":{\"nested\":\"value\"}}", "{\"test\":{\"nested\":\"value\"}}")] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_StringInput_IsValidMatch(string value, string input) + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher(value); + + // Act + var match = matcher.IsMatch(input).Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Theory] + [InlineData("{\"test\":\"*\"}", "{\"test\":\"xxx\",\"other\":\"xyz\"}")] + [InlineData("\"t*t\"", "\"test\"")] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_StringInputWithWildcard_IsValidMatch(string value, string input) + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher(value); + + // Act + var match = matcher.IsMatch(input).Score; + + // Assert + match.Should().Be(1.0); + } + + [Theory] + [InlineData("\"test\"", null)] + [InlineData("\"test1\"", "\"test2\"")] + [InlineData("123", "1234")] + [InlineData("[\"test\"]", "[\"test1\"]")] + [InlineData("[\"test\"]", "[\"test1\", \"test2\"]")] + [InlineData("[123]", "[1234]")] + [InlineData("{}", "\"test\"")] + [InlineData("{ \"test\":\"value\" }", "{\"test\":\"value2\"}")] + [InlineData("{ \"test.nested\":\"value\" }", "{\"test\":{\"nested\":\"value1\"}}")] + [InlineData("{\"test\":{\"test1\":\"value\"}}", "{\"test\":{\"test1\":\"value1\"}}")] + [InlineData("[{ \"test.nested\":\"value\" }]", "[{\"test\":{\"nested\":\"value1\"}}]")] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_StringInputWithInvalidMatch(string value, string? input) + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher(value); + + // Act + var match = matcher.IsMatch(input).Score; + + // Assert + Assert.Equal(0.0, match); + } + + [Theory] + [InlineData("{ \"test.nested\":123 }", "{\"test\":{\"nested\":123}}")] + [InlineData("{ \"test.nested\":[123, 456] }", "{\"test\":{\"nested\":[123, 456]}}")] + [InlineData("{ \"test.nested\":\"value\" }", "{\"test\":{\"nested\":\"value\"}}")] + [InlineData("{ \"['name.with.dot']\":\"value\" }", "{\"name.with.dot\":\"value\"}")] + [InlineData("[{ \"test.nested\":\"value\" }]", "[{\"test\":{\"nested\":\"value\"}}]")] + [InlineData("[{ \"['name.with.dot']\":\"value\" }]", "[{\"name.with.dot\":\"value\"}]")] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_ValueAsPathValidMatch(string value, string input) + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher(value); + + // Act + var match = matcher.IsMatch(input).Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Theory] + [InlineData("{ \"test.nested\":123 }", "{\"test\":{\"nested\":456}}")] + [InlineData("{ \"test.nested\":[123, 456] }", "{\"test\":{\"nested\":[1, 2]}}")] + [InlineData("{ \"test.nested\":\"value\" }", "{\"test\":{\"nested\":\"value1\"}}")] + [InlineData("{ \"['name.with.dot']\":\"value\" }", "{\"name.with.dot\":\"value1\"}")] + [InlineData("[{ \"test.nested\":\"value\" }]", "[{\"test\":{\"nested\":\"value1\"}}]")] + [InlineData("[{ \"['name.with.dot']\":\"value\" }]", "[{\"name.with.dot\":\"value1\"}]")] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_ValueAsPathInvalidMatch(string value, string input) + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher(value); + + // Act + var match = matcher.IsMatch(input).Score; + + // Assert + Assert.Equal(0.0, match); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_WithIgnoreCaseTrueAndRegexTrue_JsonObject1() + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher(new { id = 1, Number = "^\\d+$" }, ignoreCase: true, regex: true); + + // Act + var match = matcher.IsMatch("{ \"Id\" : 1, \"Number\" : \"42\" }").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_WithIgnoreCaseTrueAndRegexTrue_JsonObject2() + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher(new { method = "initialize", id = "^[a-f0-9]{32}-[0-9]$" }, ignoreCase: true, regex: true); + + // Act + var match = matcher.IsMatch("{\"jsonrpc\":\"2.0\",\"id\":\"ec475f56d4694b48bc737500ba575b35-1\",\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{},\"clientInfo\":{\"name\":\"GitHub Test\",\"version\":\"1.0.0\"}}}").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonPartialWildcardMatcher_IsMatch_JsonElement_ShouldMatch() + { + // Assign + var matcher = new SystemTextJsonPartialWildcardMatcher(new { Id = 1, Name = "Test" }); + + // Act + var jsonElement = JsonDocument.Parse("{ \"Id\" : 1, \"Name\" : \"Test\", \"Extra\" : \"value\" }").RootElement; + var match = matcher.IsMatch(jsonElement).Score; + + // Assert + Assert.Equal(1.0, match); + } +}