From 5cb39f86e65bf8d2f7554d40745627d32625b390 Mon Sep 17 00:00:00 2001 From: skwasjer <11424653+skwasjer@users.noreply.github.com> Date: Sun, 8 Sep 2024 20:00:55 +0200 Subject: [PATCH] refactor: add new Pattern type that replaces IPatternMatcher<> making the API a lot simpler. --- src/Directory.Build.targets | 8 +++ .../Extensions/RequestMatchingExtensions.cs | 56 +++++++++++---- .../Http/HttpHeaderEqualityComparer.cs | 12 ++-- src/MockHttp/Matchers/HttpHeadersMatcher.cs | 9 ++- .../Matchers/Patterns/IPatternMatcher.cs | 11 --- .../Matchers/Patterns/RegexPatternMatcher.cs | 37 ---------- .../RelativeOrAbsoluteUriPatternMatcher.cs | 34 --------- .../Patterns/UriStringPatternMatcher.cs | 19 ----- src/MockHttp/Matchers/UriMatcher.cs | 29 +++++--- src/MockHttp/Patterns/IPattern.cs | 7 ++ src/MockHttp/Patterns/Pattern.cs | 22 ++++++ src/MockHttp/Patterns/RegexPattern.cs | 53 ++++++++++++++ .../WildcardPattern.cs} | 48 +++++++++---- .../RequestMatchingExtensionsTests.cs | 12 ++++ .../Patterns/RegexPatternTests.cs | 71 +++++++++++++++++++ .../WildcardPatternTests.cs} | 45 ++++++++++-- 16 files changed, 318 insertions(+), 155 deletions(-) delete mode 100644 src/MockHttp/Matchers/Patterns/IPatternMatcher.cs delete mode 100644 src/MockHttp/Matchers/Patterns/RegexPatternMatcher.cs delete mode 100644 src/MockHttp/Matchers/Patterns/RelativeOrAbsoluteUriPatternMatcher.cs delete mode 100644 src/MockHttp/Matchers/Patterns/UriStringPatternMatcher.cs create mode 100644 src/MockHttp/Patterns/IPattern.cs create mode 100644 src/MockHttp/Patterns/Pattern.cs create mode 100644 src/MockHttp/Patterns/RegexPattern.cs rename src/MockHttp/{Matchers/Patterns/WildcardPatternMatcher.cs => Patterns/WildcardPattern.cs} (54%) create mode 100644 test/MockHttp.Tests/Patterns/RegexPatternTests.cs rename test/MockHttp.Tests/{Matchers/Patterns/WildcardPatternMatcherTests.cs => Patterns/WildcardPatternTests.cs} (60%) diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 7180fa4b..a1be6125 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -17,6 +17,14 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/MockHttp/Extensions/RequestMatchingExtensions.cs b/src/MockHttp/Extensions/RequestMatchingExtensions.cs index 7038c877..c78883fe 100644 --- a/src/MockHttp/Extensions/RequestMatchingExtensions.cs +++ b/src/MockHttp/Extensions/RequestMatchingExtensions.cs @@ -6,7 +6,7 @@ using System.Text; using MockHttp.Http; using MockHttp.Matchers; -using MockHttp.Matchers.Patterns; +using MockHttp.Patterns; using static MockHttp.Http.UriExtensions; namespace MockHttp; @@ -41,18 +41,13 @@ private static bool ContainsWildcard(this string value) public static RequestMatching RequestUri(this RequestMatching builder, string requestUri, bool allowWildcards = true) #pragma warning restore CA1054 { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } - if (requestUri is null) { throw new ArgumentNullException(nameof(requestUri)); } return allowWildcards && requestUri.ContainsWildcard() - ? builder.With(new UriMatcher(new UriStringPatternMatcher(uri => uri.ToString(), new WildcardPatternMatcher(requestUri)), requestUri)) + ? builder.RequestUri(WildcardPattern.Create(requestUri)) : builder.RequestUri(new Uri(requestUri, DotNetRelativeOrAbsolute)); } @@ -64,17 +59,54 @@ public static RequestMatching RequestUri(this RequestMatching builder, string re /// The request matching builder instance. public static RequestMatching RequestUri(this RequestMatching builder, Uri requestUri) { - if (builder is null) + if (requestUri is null) { - throw new ArgumentNullException(nameof(builder)); + throw new ArgumentNullException(nameof(requestUri)); } - if (requestUri is null) + return builder.RequestUri(GetAbsoluteOrRelativePattern(requestUri)); + + static Pattern GetAbsoluteOrRelativePattern(Uri pattern) { - throw new ArgumentNullException(nameof(requestUri)); + Uri patternRooted = pattern.EnsureIsRooted(); + return new Pattern + { + Value = pattern.ToString(), + IsMatch = input => + { + var uri = new Uri(input, DotNetRelativeOrAbsolute); + return IsAbsoluteUriMatch(patternRooted, uri) || IsRelativeUriMatch(patternRooted, uri); + } + }; + + static bool IsAbsoluteUriMatch(Uri pattern, Uri uri) + { + return pattern.IsAbsoluteUri && uri.Equals(pattern); + } + + static bool IsRelativeUriMatch(Uri pattern, Uri uri) + { + return !pattern.IsAbsoluteUri + && uri.IsBaseOf(pattern) + && uri.ToString().EndsWith(pattern.ToString(), StringComparison.Ordinal); + } + } + } + + /// + /// Matches a request by specified . + /// + /// The request matching builder instance. + /// The pattern that must match the request URI. + /// The request matching builder instance. + private static RequestMatching RequestUri(this RequestMatching builder, Pattern pattern) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); } - return builder.With(new UriMatcher(new RelativeOrAbsoluteUriPatternMatcher(requestUri), requestUri.ToString())); + return builder.With(new UriMatcher(pattern)); } /// diff --git a/src/MockHttp/Http/HttpHeaderEqualityComparer.cs b/src/MockHttp/Http/HttpHeaderEqualityComparer.cs index 8abed384..97d8eb1a 100644 --- a/src/MockHttp/Http/HttpHeaderEqualityComparer.cs +++ b/src/MockHttp/Http/HttpHeaderEqualityComparer.cs @@ -1,5 +1,5 @@ using System.ComponentModel; -using MockHttp.Matchers.Patterns; +using MockHttp.Patterns; namespace MockHttp.Http; @@ -22,7 +22,7 @@ internal enum HttpHeaderMatchType internal sealed class HttpHeaderEqualityComparer : IEqualityComparer>> { private readonly HttpHeaderMatchType? _matchType; - private readonly IPatternMatcher? _valuePatternMatcher; + private readonly Pattern? _valuePatternMatcher; public HttpHeaderEqualityComparer(HttpHeaderMatchType matchType) { @@ -34,9 +34,9 @@ public HttpHeaderEqualityComparer(HttpHeaderMatchType matchType) _matchType = matchType; } - public HttpHeaderEqualityComparer(IPatternMatcher valuePatternMatcher) + public HttpHeaderEqualityComparer(Pattern valuePatternMatcher) { - _valuePatternMatcher = valuePatternMatcher ?? throw new ArgumentNullException(nameof(valuePatternMatcher)); + _valuePatternMatcher = valuePatternMatcher; } public bool Equals(KeyValuePair> x, KeyValuePair> y) @@ -62,8 +62,8 @@ public bool Equals(KeyValuePair> x, KeyValuePair { string[] headerValues = HttpHeadersCollection.ParseHttpHeaderValue(yValue).ToArray(); - return _valuePatternMatcher is null && headerValues.Contains(xValue) - || (_valuePatternMatcher is not null && headerValues.Any(_valuePatternMatcher.IsMatch)); + return !_valuePatternMatcher.HasValue && headerValues.Contains(xValue) + || (_valuePatternMatcher.HasValue && headerValues.Any(_valuePatternMatcher.Value.IsMatch)); }) )) { diff --git a/src/MockHttp/Matchers/HttpHeadersMatcher.cs b/src/MockHttp/Matchers/HttpHeadersMatcher.cs index c4ddb85b..918abfc9 100644 --- a/src/MockHttp/Matchers/HttpHeadersMatcher.cs +++ b/src/MockHttp/Matchers/HttpHeadersMatcher.cs @@ -1,6 +1,6 @@ using System.Net.Http.Headers; using MockHttp.Http; -using MockHttp.Matchers.Patterns; +using MockHttp.Patterns; using MockHttp.Responses; namespace MockHttp.Matchers; @@ -41,10 +41,9 @@ public HttpHeadersMatcher(string name, string value, bool allowWildcards = false throw new ArgumentNullException(nameof(name)); } - WildcardPatternMatcher? patternMatcher = allowWildcards ? new WildcardPatternMatcher(value) : null; - _equalityComparer = patternMatcher is null - ? new HttpHeaderEqualityComparer(HttpHeaderMatchType.HeaderNameAndPartialValues) - : new HttpHeaderEqualityComparer(patternMatcher); + _equalityComparer = allowWildcards + ? new HttpHeaderEqualityComparer(WildcardPattern.Create(value)) + : new HttpHeaderEqualityComparer(HttpHeaderMatchType.HeaderNameAndPartialValues); Value.Add(name, value); } diff --git a/src/MockHttp/Matchers/Patterns/IPatternMatcher.cs b/src/MockHttp/Matchers/Patterns/IPatternMatcher.cs deleted file mode 100644 index b2560b68..00000000 --- a/src/MockHttp/Matchers/Patterns/IPatternMatcher.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace MockHttp.Matchers.Patterns; - -internal interface IPatternMatcher -{ - /// - /// Tests if the specified matches the pattern. - /// - /// The value to test. - /// Returns true if the value matches the pattern; otherwise returns false. - bool IsMatch(T value); -} diff --git a/src/MockHttp/Matchers/Patterns/RegexPatternMatcher.cs b/src/MockHttp/Matchers/Patterns/RegexPatternMatcher.cs deleted file mode 100644 index 7456a64c..00000000 --- a/src/MockHttp/Matchers/Patterns/RegexPatternMatcher.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Text.RegularExpressions; - -namespace MockHttp.Matchers.Patterns; - -internal class RegexPatternMatcher : IPatternMatcher -{ - public RegexPatternMatcher - ( -#if NET8_0_OR_GREATER - [StringSyntax(StringSyntaxAttribute.Regex)] -#endif - string regex - ) - : this(new Regex(regex, RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Singleline)) - { - } - - public RegexPatternMatcher(Regex regex) - { - Regex = regex ?? throw new ArgumentNullException(nameof(regex)); - } - - internal Regex Regex { get; } - - /// - public virtual bool IsMatch(string value) - { - return Regex.IsMatch(value); - } - - /// - public override string ToString() - { - return Regex.ToString(); - } -} diff --git a/src/MockHttp/Matchers/Patterns/RelativeOrAbsoluteUriPatternMatcher.cs b/src/MockHttp/Matchers/Patterns/RelativeOrAbsoluteUriPatternMatcher.cs deleted file mode 100644 index fb84d4b5..00000000 --- a/src/MockHttp/Matchers/Patterns/RelativeOrAbsoluteUriPatternMatcher.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Diagnostics; -using MockHttp.Http; - -namespace MockHttp.Matchers.Patterns; - -[DebuggerDisplay($"{{{nameof(_originalUri)}}}")] -internal sealed class RelativeOrAbsoluteUriPatternMatcher : IPatternMatcher -{ - private readonly Uri _originalUri; - private readonly Uri _uri; - - public RelativeOrAbsoluteUriPatternMatcher(Uri uri) - { - _originalUri = uri; - _uri = uri.EnsureIsRooted(); - } - - public bool IsMatch(Uri value) - { - return IsAbsoluteUriMatch(value) || IsRelativeUriMatch(value); - } - - private bool IsAbsoluteUriMatch(Uri uri) - { - return _uri.IsAbsoluteUri && uri.Equals(_uri); - } - - private bool IsRelativeUriMatch(Uri uri) - { - return !_uri.IsAbsoluteUri - && uri.IsBaseOf(_uri) - && uri.ToString().EndsWith(_uri.ToString(), StringComparison.Ordinal); - } -} diff --git a/src/MockHttp/Matchers/Patterns/UriStringPatternMatcher.cs b/src/MockHttp/Matchers/Patterns/UriStringPatternMatcher.cs deleted file mode 100644 index 8a677e73..00000000 --- a/src/MockHttp/Matchers/Patterns/UriStringPatternMatcher.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace MockHttp.Matchers.Patterns; - -internal class UriStringPatternMatcher : IPatternMatcher -{ - private readonly Func _selector; - private readonly IPatternMatcher _stringPatternMatcher; - - public UriStringPatternMatcher(Func selector, IPatternMatcher stringPatternMatcher) - { - _selector = selector ?? throw new ArgumentNullException(nameof(selector)); - _stringPatternMatcher = stringPatternMatcher ?? throw new ArgumentNullException(nameof(stringPatternMatcher)); - } - - public bool IsMatch(Uri value) - { - string v = _selector(value); - return _stringPatternMatcher.IsMatch(v); - } -} diff --git a/src/MockHttp/Matchers/UriMatcher.cs b/src/MockHttp/Matchers/UriMatcher.cs index 866c4ed0..4716672d 100644 --- a/src/MockHttp/Matchers/UriMatcher.cs +++ b/src/MockHttp/Matchers/UriMatcher.cs @@ -1,5 +1,6 @@ -using System.Runtime.CompilerServices; -using MockHttp.Matchers.Patterns; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; +using MockHttp.Patterns; using MockHttp.Responses; namespace MockHttp.Matchers; @@ -9,26 +10,28 @@ namespace MockHttp.Matchers; /// internal class UriMatcher : HttpRequestMatcher { - private readonly IPatternMatcher _patternMatcher; private readonly string _name; + private readonly Pattern _pattern; private readonly string _patternDescription; + private readonly Func? _selectorFn; /// /// Initializes a new instance of the class. /// - /// A matcher implementation that validates the URI. - /// A description of the pattern. + /// The pattern to match. + /// An expression to extract the part of the URI to match against. If , uses the complete URI. /// The name of this matcher. /// Thrown when a required argument is . internal UriMatcher ( - IPatternMatcher patternMatcher, - string patternDescription, + Pattern pattern, + Expression>? selector = null, [CallerMemberName] string? name = null ) { - _patternMatcher = patternMatcher ?? throw new ArgumentNullException(nameof(patternMatcher)); - _patternDescription = patternDescription ?? throw new ArgumentNullException(nameof(patternDescription)); + _pattern = pattern; + _selectorFn = selector?.Compile(); + _patternDescription = pattern.Value; name ??= GetType().Name; if (name.EndsWith("Matcher", StringComparison.Ordinal)) @@ -48,7 +51,13 @@ public override bool IsMatch(MockHttpRequestContext requestContext) } Uri? uri = requestContext.Request.RequestUri; - return uri is not null && _patternMatcher.IsMatch(uri); + if (uri is null) + { + return false; + } + + string value = _selectorFn?.Invoke(uri) ?? uri.ToString(); + return _pattern.IsMatch(value); } /// diff --git a/src/MockHttp/Patterns/IPattern.cs b/src/MockHttp/Patterns/IPattern.cs new file mode 100644 index 00000000..2e66d21a --- /dev/null +++ b/src/MockHttp/Patterns/IPattern.cs @@ -0,0 +1,7 @@ +namespace MockHttp.Patterns; + +internal interface IPattern +{ + string Value { get; } + Func IsMatch { get; } +} diff --git a/src/MockHttp/Patterns/Pattern.cs b/src/MockHttp/Patterns/Pattern.cs new file mode 100644 index 00000000..053b582d --- /dev/null +++ b/src/MockHttp/Patterns/Pattern.cs @@ -0,0 +1,22 @@ +namespace MockHttp.Patterns; + +internal readonly record struct Pattern : IPattern +{ + public required string Value { get; internal init; } + public required Func IsMatch { get; internal init; } + + /// + public override string ToString() + { + return Value; + } + + public static Pattern MatchExactly(string value) + { + return new Pattern + { + Value = value, + IsMatch = input => StringComparer.Ordinal.Equals(value, input) + }; + } +} diff --git a/src/MockHttp/Patterns/RegexPattern.cs b/src/MockHttp/Patterns/RegexPattern.cs new file mode 100644 index 00000000..ecbee42e --- /dev/null +++ b/src/MockHttp/Patterns/RegexPattern.cs @@ -0,0 +1,53 @@ +using System.Text.RegularExpressions; + +namespace MockHttp.Patterns; + +internal readonly record struct RegexPattern : IPattern +{ + public required string Value { get; internal init; } + public required Func IsMatch { get; internal init; } + + /// + public override string ToString() + { + return Value; + } + + public static implicit operator Pattern(RegexPattern pattern) + { + return new Pattern + { + Value = pattern.Value, + IsMatch = pattern.IsMatch + }; + } + + public static RegexPattern Create + ( +#if NET8_0_OR_GREATER + [System.Diagnostics.CodeAnalysis.StringSyntax(System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.Regex)] +#endif + string pattern + ) + { + return Create(new Regex( + pattern, + RegexOptions.CultureInvariant | RegexOptions.Singleline, + TimeSpan.FromSeconds(5)) + ); + } + + public static RegexPattern Create(Regex pattern) + { + if (pattern is null) + { + throw new ArgumentNullException(nameof(pattern)); + } + + return new RegexPattern + { + Value = pattern.ToString(), + IsMatch = pattern.IsMatch + }; + } +} diff --git a/src/MockHttp/Matchers/Patterns/WildcardPatternMatcher.cs b/src/MockHttp/Patterns/WildcardPattern.cs similarity index 54% rename from src/MockHttp/Matchers/Patterns/WildcardPatternMatcher.cs rename to src/MockHttp/Patterns/WildcardPattern.cs index 8ce1e300..86bda616 100644 --- a/src/MockHttp/Matchers/Patterns/WildcardPatternMatcher.cs +++ b/src/MockHttp/Patterns/WildcardPattern.cs @@ -1,37 +1,53 @@ using System.Text; -namespace MockHttp.Matchers.Patterns; +namespace MockHttp.Patterns; -internal sealed class WildcardPatternMatcher : RegexPatternMatcher +internal readonly record struct WildcardPattern : IPattern { private static readonly char[] SpecialRegexChars = ['.', '+', '*', '?', '^', '$', '(', ')', '[', ']', '{', '}', '|', '\\']; - private readonly string _pattern; + internal RegexPattern RegexPattern { get; private init; } + public required string Value { get; internal init; } + public required Func IsMatch { get; internal init; } - public WildcardPatternMatcher(string pattern) - : base(GetMatchPattern(pattern)) + /// + public override string ToString() { - _pattern = pattern; + return Value; } - /// - public override string ToString() + public static implicit operator Pattern(WildcardPattern pattern) { - return _pattern; + return new Pattern + { + Value = pattern.Value, + IsMatch = pattern.IsMatch + }; } - private static string GetMatchPattern(string pattern) + public static WildcardPattern Create(string pattern) { if (pattern is null) { throw new ArgumentNullException(nameof(pattern)); } + var regexPattern = RegexPattern.Create(GetMatchPattern(pattern)); + return new WildcardPattern + { + RegexPattern = regexPattern, + Value = pattern, + IsMatch = regexPattern.IsMatch + }; + } + + private static string GetMatchPattern(string pattern) + { var sb = new StringBuilder(); bool startsWithWildcard = pattern[0] == '*'; if (startsWithWildcard) { - pattern = pattern.TrimStart('*'); + pattern = pattern.Substring(1); sb.Append(".*"); } else @@ -49,7 +65,7 @@ private static string GetMatchPattern(string pattern) IEnumerable matchGroups = pattern .Split('*') .Where(s => !string.IsNullOrEmpty(s)) - .Select(s => $"({RegexUriEscape(s)})"); + .Select(s => $"({EscapeSpecialRegexChars(s)})"); sb.Append(string.Join(".+", matchGroups)); @@ -58,19 +74,21 @@ private static string GetMatchPattern(string pattern) return sb.ToString(); } - private static string RegexUriEscape(string s) + private static string EscapeSpecialRegexChars(string s) { - var sb = new StringBuilder(); + bool isStrModified = false; + var sb = new StringBuilder(Math.Min(s.Length, 128)); foreach (char ch in s) { if (SpecialRegexChars.Contains(ch)) { sb.Append('\\'); + isStrModified = true; } sb.Append(ch); } - return sb.ToString(); + return isStrModified ? sb.ToString() : s; } } diff --git a/test/MockHttp.Tests/Extensions/RequestMatchingExtensionsTests.cs b/test/MockHttp.Tests/Extensions/RequestMatchingExtensionsTests.cs index bd876f64..ecf526dc 100644 --- a/test/MockHttp.Tests/Extensions/RequestMatchingExtensionsTests.cs +++ b/test/MockHttp.Tests/Extensions/RequestMatchingExtensionsTests.cs @@ -23,6 +23,15 @@ protected RequestMatchingExtensionsTests() public class RequestUri : RequestMatchingExtensionsTests { [Theory] + [InlineData("", false, "http://127.0.0.1", true)] + [InlineData("", false, "http://127.0.0.1/", true)] + [InlineData("", true, "http://127.0.0.1", true)] + [InlineData("", true, "http://127.0.0.1/", true)] + [InlineData("/", false, "http://127.0.0.1", true)] + [InlineData("/", false, "http://127.0.0.1/", true)] + [InlineData("/", true, "http://127.0.0.1", true)] + [InlineData("/", true, "http://127.0.0.1/", true)] + [InlineData("relative.htm", true, "http://127.0.0.1/relative.htm", true)] [InlineData("relative.htm", true, "http://127.0.0.1/relative.htm?query=string", false)] [InlineData("relative.htm", false, "http://127.0.0.1/relative.htm", true)] @@ -134,7 +143,10 @@ public async Task When_configuring_requestUri_as_string_it_should_match(string u } [Theory] + [InlineData("", UriKind.Relative, "http://127.0.0.1", true)] [InlineData("", UriKind.Relative, "http://127.0.0.1/", true)] + [InlineData("/", UriKind.Relative, "http://127.0.0.1", true)] + [InlineData("/", UriKind.Relative, "http://127.0.0.1/", true)] [InlineData("relative.htm", UriKind.Relative, "http://127.0.0.1/relative.htm", true)] [InlineData("/folder/relative.htm", UriKind.Relative, "http://127.0.0.1/relative.htm", false)] [InlineData("relative.htm", UriKind.Relative, "http://127.0.0.1/folder/relative.htm", false)] diff --git a/test/MockHttp.Tests/Patterns/RegexPatternTests.cs b/test/MockHttp.Tests/Patterns/RegexPatternTests.cs new file mode 100644 index 00000000..19cda88d --- /dev/null +++ b/test/MockHttp.Tests/Patterns/RegexPatternTests.cs @@ -0,0 +1,71 @@ +using System.Text.RegularExpressions; + +namespace MockHttp.Patterns; + +public sealed class RegexPatternTests +{ + [Fact] + public void Given_that_pattern_is_null_when_creating_instance_it_should_throw() + { + string? pattern = null; + + // Act + Func act = () => RegexPattern.Create(pattern!); + + // Assert + act.Should() + .Throw() + .WithParameterName(nameof(pattern)); + } + + [Fact] + public void Given_that_regex_is_null_when_creating_instance_it_should_throw() + { + Regex? pattern = null; + + // Act + Func act = () => RegexPattern.Create(pattern!); + + // Assert + act.Should() + .Throw() + .WithParameterName(nameof(pattern)); + } + + [Theory] + [InlineData("test")] + [InlineData("^t.+st.*")] + public void When_creating_pattern_it_should_return_expected_instance(string regexPattern) + { + // Act + var sut = RegexPattern.Create(regexPattern); + + // Assert + sut.Value.Should() + .Be(sut.ToString()) + .And.Be(regexPattern); + } + + [Theory] + [InlineData("^123$", "123", true)] + [InlineData("^123$", "1234", false)] + [InlineData("^123", "1234", true)] + public void Given_that_value_matches_pattern_when_matching_it_should_pass(string regexPattern, string value, bool expected) + { + var sut = RegexPattern.Create(regexPattern); + sut.IsMatch(value).Should().Be(expected); + } + + [Fact] + public void When_casting_to_pattern_it_should_return_expected_instance() + { + var sut = RegexPattern.Create("test"); + + // Act + Pattern actual = sut; + + // Assert + actual.Value.Should().BeSameAs(sut.Value); + actual.IsMatch.Should().BeSameAs(sut.IsMatch); + } +} diff --git a/test/MockHttp.Tests/Matchers/Patterns/WildcardPatternMatcherTests.cs b/test/MockHttp.Tests/Patterns/WildcardPatternTests.cs similarity index 60% rename from test/MockHttp.Tests/Matchers/Patterns/WildcardPatternMatcherTests.cs rename to test/MockHttp.Tests/Patterns/WildcardPatternTests.cs index 5e1b9754..60bffacc 100644 --- a/test/MockHttp.Tests/Matchers/Patterns/WildcardPatternMatcherTests.cs +++ b/test/MockHttp.Tests/Patterns/WildcardPatternTests.cs @@ -1,7 +1,21 @@ -namespace MockHttp.Matchers.Patterns; +namespace MockHttp.Patterns; -public sealed class WildcardPatternMatcherTests +public sealed class WildcardPatternTests { + [Fact] + public void Given_that_pattern_is_null_when_creating_instance_it_should_throw() + { + string? pattern = null; + + // Act + Func act = () => WildcardPattern.Create(pattern!); + + // Assert + act.Should() + .Throw() + .WithParameterName(nameof(pattern)); + } + [Theory] [InlineData("test", "^(test)$")] [InlineData("test*", "^(test).*")] @@ -11,10 +25,16 @@ public sealed class WildcardPatternMatcherTests [InlineData("/path?*", "^(/path\\?).*")] [InlineData("/path/file.jpg?q=*&p=1&m=[a,b]", "^(/path/file\\.jpg\\?q=).+(&p=1&m=\\[a,b\\])$")] [InlineData(".+?^$()[]{}|\\", "^(\\.\\+\\?\\^\\$\\(\\)\\[\\]\\{\\}\\|\\\\)$")] - public void When_creating_matcher_it_should_generate_expected_regex(string wildcardPattern, string expectedRegex) + public void When_creating_pattern_it_should_return_expected_instance(string wildcardPattern, string expectedRegex) { - var sut = new WildcardPatternMatcher(wildcardPattern); - sut.Regex.ToString().Should().Be(expectedRegex); + // Act + var sut = WildcardPattern.Create(wildcardPattern); + + // Assert + sut.RegexPattern.Value.Should().Be(expectedRegex); + sut.Value.Should() + .Be(sut.ToString()) + .And.Be(wildcardPattern); } [Theory] @@ -40,7 +60,20 @@ public void When_creating_matcher_it_should_generate_expected_regex(string wildc [InlineData("/path/file.jpg?q=*&p=*&m=*", "/path/file.jpg?q=search%20term&p=1&m=[a,b]", true)] public void Given_that_value_matches_pattern_when_matching_it_should_pass(string wildcardPattern, string value, bool expected) { - var sut = new WildcardPatternMatcher(wildcardPattern); + var sut = WildcardPattern.Create(wildcardPattern); sut.IsMatch(value).Should().Be(expected); } + + [Fact] + public void When_casting_to_pattern_it_should_return_expected_instance() + { + var sut = WildcardPattern.Create("te*st"); + + // Act + Pattern actual = sut; + + // Assert + actual.Value.Should().BeSameAs(sut.Value); + actual.IsMatch.Should().BeSameAs(sut.IsMatch); + } }