From 80f396c8e31894d60bae365ab2ea80e67a9758c6 Mon Sep 17 00:00:00 2001
From: skwasjer <11424653+skwasjer@users.noreply.github.com>
Date: Sun, 19 Nov 2023 04:59:36 +0100
Subject: [PATCH 1/4] refactor: move Uri normalization to helper
---
src/MockHttp/Http/UriExtensions.cs | 34 ++++++++++++++
src/MockHttp/Matchers/RequestUriMatcher.cs | 32 ++++----------
.../MockHttp.Tests/Http/UriExtensionsTests.cs | 44 +++++++++++++++++++
3 files changed, 86 insertions(+), 24 deletions(-)
create mode 100644 src/MockHttp/Http/UriExtensions.cs
create mode 100644 test/MockHttp.Tests/Http/UriExtensionsTests.cs
diff --git a/src/MockHttp/Http/UriExtensions.cs b/src/MockHttp/Http/UriExtensions.cs
new file mode 100644
index 00000000..3c1c74df
--- /dev/null
+++ b/src/MockHttp/Http/UriExtensions.cs
@@ -0,0 +1,34 @@
+namespace MockHttp.Http;
+
+internal static class UriExtensions
+{
+ private const char UriSegmentDelimiter = '/';
+ internal static readonly UriKind DotNetRelativeOrAbsolute = Type.GetType("Mono.Runtime") == null ? UriKind.RelativeOrAbsolute : (UriKind)300;
+
+ ///
+ /// If a relative URI, ensures it starts with a forward slash (/). If not, returns the original .
+ ///
+ ///
+ ///
+ ///
+ internal static Uri EnsureIsRooted(this Uri uri)
+ {
+ if (uri is null)
+ {
+ throw new ArgumentNullException(nameof(uri));
+ }
+
+ if (uri.IsAbsoluteUri)
+ {
+ return uri;
+ }
+
+ string relUri = uri.ToString();
+ if (relUri.Length > 0 && relUri[0] != UriSegmentDelimiter)
+ {
+ return new Uri($"{UriSegmentDelimiter}{relUri}", UriKind.Relative);
+ }
+
+ return uri;
+ }
+}
diff --git a/src/MockHttp/Matchers/RequestUriMatcher.cs b/src/MockHttp/Matchers/RequestUriMatcher.cs
index b9996dd9..2e36e934 100644
--- a/src/MockHttp/Matchers/RequestUriMatcher.cs
+++ b/src/MockHttp/Matchers/RequestUriMatcher.cs
@@ -1,6 +1,8 @@
using System.Diagnostics;
+using MockHttp.Http;
using MockHttp.Matchers.Patterns;
using MockHttp.Responses;
+using static MockHttp.Http.UriExtensions;
namespace MockHttp.Matchers;
@@ -9,14 +11,10 @@ namespace MockHttp.Matchers;
///
public class RequestUriMatcher : HttpRequestMatcher
{
- private static readonly UriKind DotNetRelativeOrAbsolute = Type.GetType("Mono.Runtime") == null ? UriKind.RelativeOrAbsolute : (UriKind)300;
-
- private const char UriSegmentDelimiter = '/';
-
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
- private Uri _requestUri = default!;
+ private readonly Uri _requestUri = default!;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
- private string _formattedUri = default!;
+ private readonly string _formattedUri;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly PatternMatcher? _uriPatternMatcher;
@@ -26,7 +24,8 @@ public class RequestUriMatcher : HttpRequestMatcher
/// The request URI.
public RequestUriMatcher(Uri uri)
{
- SetRequestUri(uri);
+ _requestUri = uri.EnsureIsRooted();
+ _formattedUri = _requestUri.ToString();
}
///
@@ -51,26 +50,11 @@ public RequestUriMatcher(string uriString, bool allowWildcards = true)
else
{
// If no wildcards, then must be actual uri.
- SetRequestUri(new Uri(uriString, DotNetRelativeOrAbsolute));
+ _requestUri = new Uri(uriString, DotNetRelativeOrAbsolute).EnsureIsRooted();
+ _formattedUri = _requestUri.ToString();
}
}
- private void SetRequestUri(Uri uri)
- {
- _requestUri = uri ?? throw new ArgumentNullException(nameof(uri));
-
- if (!_requestUri.IsAbsoluteUri)
- {
- string relUri = _requestUri.ToString();
- if (relUri.Length > 0 && _requestUri.ToString()[0] != UriSegmentDelimiter)
- {
- _requestUri = new Uri($"{UriSegmentDelimiter}{_requestUri}", UriKind.Relative);
- }
- }
-
- _formattedUri = _requestUri.ToString();
- }
-
///
public override bool IsMatch(MockHttpRequestContext requestContext)
{
diff --git a/test/MockHttp.Tests/Http/UriExtensionsTests.cs b/test/MockHttp.Tests/Http/UriExtensionsTests.cs
new file mode 100644
index 00000000..2e612736
--- /dev/null
+++ b/test/MockHttp.Tests/Http/UriExtensionsTests.cs
@@ -0,0 +1,44 @@
+using static MockHttp.Http.UriExtensions;
+
+namespace MockHttp.Http;
+
+public abstract class UriExtensionsTests
+{
+ public class EnsureIsRootedTests : UriExtensionsTests
+ {
+ [Theory]
+ [InlineData("relative/path", "/relative/path")]
+ [InlineData("/relative/path", "/relative/path")]
+ [InlineData("http://0.0.0.0/relative/path", "http://0.0.0.0/relative/path")]
+ public void Given_that_uri_does_not_start_with_forward_slash_when_ensuringIsRooted_it_should_modify_it(string uriStr, string expectedUriStr)
+ {
+ var uri = new Uri(uriStr, DotNetRelativeOrAbsolute);
+ var expectedUri = new Uri(expectedUriStr, DotNetRelativeOrAbsolute);
+
+ // Act
+ Uri actual = uri.EnsureIsRooted();
+
+ // Assert
+ actual.Should().Be(expectedUri);
+ if (actual == uri)
+ {
+ actual.Should().BeSameAs(uri);
+ }
+ }
+
+ [Fact]
+ public void Given_that_uri_is_null_when_ensuringIsRooted_it_should_throw()
+ {
+ Uri? uri = null;
+
+ // Act
+ Func act = () => uri!.EnsureIsRooted();
+
+ // Assert
+ act.Should()
+ .Throw()
+ .Which.ParamName.Should()
+ .Be(nameof(uri));
+ }
+ }
+}
From 89c7c3f03a17def5af448f431e723c57c0ab65ac Mon Sep 17 00:00:00 2001
From: skwasjer <11424653+skwasjer@users.noreply.github.com>
Date: Sun, 19 Nov 2023 05:00:02 +0100
Subject: [PATCH 2/4] refactor: extract interface and make pattern matcher
generic
---
src/MockHttp/Http/HttpHeaderEqualityComparer.cs | 4 ++--
src/MockHttp/Matchers/Patterns/IPatternMatcher.cs | 6 ++++++
src/MockHttp/Matchers/Patterns/PatternMatcher.cs | 2 +-
3 files changed, 9 insertions(+), 3 deletions(-)
create mode 100644 src/MockHttp/Matchers/Patterns/IPatternMatcher.cs
diff --git a/src/MockHttp/Http/HttpHeaderEqualityComparer.cs b/src/MockHttp/Http/HttpHeaderEqualityComparer.cs
index 9ce89c0f..8abed384 100644
--- a/src/MockHttp/Http/HttpHeaderEqualityComparer.cs
+++ b/src/MockHttp/Http/HttpHeaderEqualityComparer.cs
@@ -22,7 +22,7 @@ internal enum HttpHeaderMatchType
internal sealed class HttpHeaderEqualityComparer : IEqualityComparer>>
{
private readonly HttpHeaderMatchType? _matchType;
- private readonly PatternMatcher? _valuePatternMatcher;
+ private readonly IPatternMatcher? _valuePatternMatcher;
public HttpHeaderEqualityComparer(HttpHeaderMatchType matchType)
{
@@ -34,7 +34,7 @@ public HttpHeaderEqualityComparer(HttpHeaderMatchType matchType)
_matchType = matchType;
}
- public HttpHeaderEqualityComparer(PatternMatcher valuePatternMatcher)
+ public HttpHeaderEqualityComparer(IPatternMatcher valuePatternMatcher)
{
_valuePatternMatcher = valuePatternMatcher ?? throw new ArgumentNullException(nameof(valuePatternMatcher));
}
diff --git a/src/MockHttp/Matchers/Patterns/IPatternMatcher.cs b/src/MockHttp/Matchers/Patterns/IPatternMatcher.cs
new file mode 100644
index 00000000..cd99da36
--- /dev/null
+++ b/src/MockHttp/Matchers/Patterns/IPatternMatcher.cs
@@ -0,0 +1,6 @@
+namespace MockHttp.Matchers.Patterns;
+
+internal interface IPatternMatcher
+{
+ bool IsMatch(T value);
+}
diff --git a/src/MockHttp/Matchers/Patterns/PatternMatcher.cs b/src/MockHttp/Matchers/Patterns/PatternMatcher.cs
index 33bd20e0..c76d89dc 100644
--- a/src/MockHttp/Matchers/Patterns/PatternMatcher.cs
+++ b/src/MockHttp/Matchers/Patterns/PatternMatcher.cs
@@ -1,6 +1,6 @@
namespace MockHttp.Matchers.Patterns;
-internal abstract class PatternMatcher
+internal abstract class PatternMatcher : IPatternMatcher
{
///
/// Tests if the specified matches the pattern.
From 542e603810330431bac0c664cef07e2ee99ff71b Mon Sep 17 00:00:00 2001
From: skwasjer <11424653+skwasjer@users.noreply.github.com>
Date: Sat, 7 Sep 2024 16:02:29 +0200
Subject: [PATCH 3/4] feat: set up uri matcher for reusability so we can more
simply add other types of matchers that check portions of the URI, like
path/route values, etc.
---
.../Matchers/Patterns/IPatternMatcher.cs | 5 ++
.../Matchers/Patterns/PatternMatcher.cs | 14 -----
.../Matchers/Patterns/RegexPatternMatcher.cs | 4 +-
.../RelativeOrAbsoluteUriPatternMatcher.cs | 34 +++++++++++
.../Patterns/UriStringPatternMatcher.cs | 19 ++++++
src/MockHttp/Matchers/RequestUriMatcher.cs | 2 +-
src/MockHttp/Matchers/UriMatcher.cs | 59 +++++++++++++++++++
7 files changed, 120 insertions(+), 17 deletions(-)
delete mode 100644 src/MockHttp/Matchers/Patterns/PatternMatcher.cs
create mode 100644 src/MockHttp/Matchers/Patterns/RelativeOrAbsoluteUriPatternMatcher.cs
create mode 100644 src/MockHttp/Matchers/Patterns/UriStringPatternMatcher.cs
create mode 100644 src/MockHttp/Matchers/UriMatcher.cs
diff --git a/src/MockHttp/Matchers/Patterns/IPatternMatcher.cs b/src/MockHttp/Matchers/Patterns/IPatternMatcher.cs
index cd99da36..b2560b68 100644
--- a/src/MockHttp/Matchers/Patterns/IPatternMatcher.cs
+++ b/src/MockHttp/Matchers/Patterns/IPatternMatcher.cs
@@ -2,5 +2,10 @@
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/PatternMatcher.cs b/src/MockHttp/Matchers/Patterns/PatternMatcher.cs
deleted file mode 100644
index c76d89dc..00000000
--- a/src/MockHttp/Matchers/Patterns/PatternMatcher.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace MockHttp.Matchers.Patterns;
-
-internal abstract class PatternMatcher : IPatternMatcher
-{
- ///
- /// Tests if the specified matches the pattern.
- ///
- /// The value to test.
- /// Returns true if the value matches the pattern; otherwise returns false.
- public abstract bool IsMatch(string value);
-
- ///
- public abstract override string ToString();
-}
diff --git a/src/MockHttp/Matchers/Patterns/RegexPatternMatcher.cs b/src/MockHttp/Matchers/Patterns/RegexPatternMatcher.cs
index 56b93f06..7456a64c 100644
--- a/src/MockHttp/Matchers/Patterns/RegexPatternMatcher.cs
+++ b/src/MockHttp/Matchers/Patterns/RegexPatternMatcher.cs
@@ -3,7 +3,7 @@
namespace MockHttp.Matchers.Patterns;
-internal class RegexPatternMatcher : PatternMatcher
+internal class RegexPatternMatcher : IPatternMatcher
{
public RegexPatternMatcher
(
@@ -24,7 +24,7 @@ public RegexPatternMatcher(Regex regex)
internal Regex Regex { get; }
///
- public override bool IsMatch(string value)
+ public virtual bool IsMatch(string value)
{
return Regex.IsMatch(value);
}
diff --git a/src/MockHttp/Matchers/Patterns/RelativeOrAbsoluteUriPatternMatcher.cs b/src/MockHttp/Matchers/Patterns/RelativeOrAbsoluteUriPatternMatcher.cs
new file mode 100644
index 00000000..fb84d4b5
--- /dev/null
+++ b/src/MockHttp/Matchers/Patterns/RelativeOrAbsoluteUriPatternMatcher.cs
@@ -0,0 +1,34 @@
+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
new file mode 100644
index 00000000..8a677e73
--- /dev/null
+++ b/src/MockHttp/Matchers/Patterns/UriStringPatternMatcher.cs
@@ -0,0 +1,19 @@
+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/RequestUriMatcher.cs b/src/MockHttp/Matchers/RequestUriMatcher.cs
index 2e36e934..782e810c 100644
--- a/src/MockHttp/Matchers/RequestUriMatcher.cs
+++ b/src/MockHttp/Matchers/RequestUriMatcher.cs
@@ -16,7 +16,7 @@ public class RequestUriMatcher : HttpRequestMatcher
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly string _formattedUri;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
- private readonly PatternMatcher? _uriPatternMatcher;
+ private readonly WildcardPatternMatcher? _uriPatternMatcher;
///
/// Initializes a new instance of the class using specified .
diff --git a/src/MockHttp/Matchers/UriMatcher.cs b/src/MockHttp/Matchers/UriMatcher.cs
new file mode 100644
index 00000000..866c4ed0
--- /dev/null
+++ b/src/MockHttp/Matchers/UriMatcher.cs
@@ -0,0 +1,59 @@
+using System.Runtime.CompilerServices;
+using MockHttp.Matchers.Patterns;
+using MockHttp.Responses;
+
+namespace MockHttp.Matchers;
+
+///
+/// Matches a request by the URI.
+///
+internal class UriMatcher : HttpRequestMatcher
+{
+ private readonly IPatternMatcher _patternMatcher;
+ private readonly string _name;
+ private readonly string _patternDescription;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A matcher implementation that validates the URI.
+ /// A description of the pattern.
+ /// The name of this matcher.
+ /// Thrown when a required argument is .
+ internal UriMatcher
+ (
+ IPatternMatcher patternMatcher,
+ string patternDescription,
+ [CallerMemberName] string? name = null
+ )
+ {
+ _patternMatcher = patternMatcher ?? throw new ArgumentNullException(nameof(patternMatcher));
+ _patternDescription = patternDescription ?? throw new ArgumentNullException(nameof(patternDescription));
+
+ name ??= GetType().Name;
+ if (name.EndsWith("Matcher", StringComparison.Ordinal))
+ {
+ name = name.Remove(name.Length - "Matcher".Length);
+ }
+
+ _name = name;
+ }
+
+ ///
+ public override bool IsMatch(MockHttpRequestContext requestContext)
+ {
+ if (requestContext is null)
+ {
+ throw new ArgumentNullException(nameof(requestContext));
+ }
+
+ Uri? uri = requestContext.Request.RequestUri;
+ return uri is not null && _patternMatcher.IsMatch(uri);
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"{_name}: '{_patternDescription}'";
+ }
+}
From 91a6fbb18526c7bf8b48a345333ff939e88e5801 Mon Sep 17 00:00:00 2001
From: skwasjer <11424653+skwasjer@users.noreply.github.com>
Date: Sat, 7 Sep 2024 18:32:28 +0200
Subject: [PATCH 4/4] refactor: replaced RequestUriMatcher with UriMatcher.
Additionally, added option to disable wildcards on request URI matching.
---
.../Extensions/RequestMatchingExtensions.cs | 27 ++-
src/MockHttp/Matchers/RequestUriMatcher.cs | 97 ----------
.../RequestMatchingExtensionsTests.cs | 182 ++++++++++++++++--
.../Matchers/AnyMatcherTests.cs | 8 +-
test/MockHttp.Tests/Matchers/BoolMatcher.cs | 23 +++
.../Matchers/RequestUriMatcherTests.cs | 109 -----------
6 files changed, 219 insertions(+), 227 deletions(-)
delete mode 100644 src/MockHttp/Matchers/RequestUriMatcher.cs
create mode 100644 test/MockHttp.Tests/Matchers/BoolMatcher.cs
delete mode 100644 test/MockHttp.Tests/Matchers/RequestUriMatcherTests.cs
diff --git a/src/MockHttp/Extensions/RequestMatchingExtensions.cs b/src/MockHttp/Extensions/RequestMatchingExtensions.cs
index 2f4aba6a..7038c877 100644
--- a/src/MockHttp/Extensions/RequestMatchingExtensions.cs
+++ b/src/MockHttp/Extensions/RequestMatchingExtensions.cs
@@ -6,6 +6,8 @@
using System.Text;
using MockHttp.Http;
using MockHttp.Matchers;
+using MockHttp.Matchers.Patterns;
+using static MockHttp.Http.UriExtensions;
namespace MockHttp;
@@ -14,13 +16,30 @@ namespace MockHttp;
///
public static class RequestMatchingExtensions
{
+ private static bool ContainsWildcard(this string value)
+ {
+ if (value is null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+#if NETSTANDARD2_0 || NETFRAMEWORK
+ return value.Contains("*");
+#else
+ return value.Contains('*', StringComparison.InvariantCultureIgnoreCase);
+#endif
+ }
+
///
/// Matches a request by specified .
///
/// The request matching builder instance.
/// The request URI or a URI wildcard.
+ /// to allow wildcards, or if exact matching.
/// The request matching builder instance.
- public static RequestMatching RequestUri(this RequestMatching builder, string requestUri)
+#pragma warning disable CA1054
+ public static RequestMatching RequestUri(this RequestMatching builder, string requestUri, bool allowWildcards = true)
+#pragma warning restore CA1054
{
if (builder is null)
{
@@ -32,7 +51,9 @@ public static RequestMatching RequestUri(this RequestMatching builder, string re
throw new ArgumentNullException(nameof(requestUri));
}
- return builder.With(new RequestUriMatcher(requestUri, true));
+ return allowWildcards && requestUri.ContainsWildcard()
+ ? builder.With(new UriMatcher(new UriStringPatternMatcher(uri => uri.ToString(), new WildcardPatternMatcher(requestUri)), requestUri))
+ : builder.RequestUri(new Uri(requestUri, DotNetRelativeOrAbsolute));
}
///
@@ -53,7 +74,7 @@ public static RequestMatching RequestUri(this RequestMatching builder, Uri reque
throw new ArgumentNullException(nameof(requestUri));
}
- return builder.With(new RequestUriMatcher(requestUri));
+ return builder.With(new UriMatcher(new RelativeOrAbsoluteUriPatternMatcher(requestUri), requestUri.ToString()));
}
///
diff --git a/src/MockHttp/Matchers/RequestUriMatcher.cs b/src/MockHttp/Matchers/RequestUriMatcher.cs
deleted file mode 100644
index 782e810c..00000000
--- a/src/MockHttp/Matchers/RequestUriMatcher.cs
+++ /dev/null
@@ -1,97 +0,0 @@
-using System.Diagnostics;
-using MockHttp.Http;
-using MockHttp.Matchers.Patterns;
-using MockHttp.Responses;
-using static MockHttp.Http.UriExtensions;
-
-namespace MockHttp.Matchers;
-
-///
-/// Matches a request by the request URI.
-///
-public class RequestUriMatcher : HttpRequestMatcher
-{
- [DebuggerBrowsable(DebuggerBrowsableState.Never)]
- private readonly Uri _requestUri = default!;
- [DebuggerBrowsable(DebuggerBrowsableState.Never)]
- private readonly string _formattedUri;
- [DebuggerBrowsable(DebuggerBrowsableState.Never)]
- private readonly WildcardPatternMatcher? _uriPatternMatcher;
-
- ///
- /// Initializes a new instance of the class using specified .
- ///
- /// The request URI.
- public RequestUriMatcher(Uri uri)
- {
- _requestUri = uri.EnsureIsRooted();
- _formattedUri = _requestUri.ToString();
- }
-
- ///
- /// Initializes a new instance of the class using specified .
- ///
- /// The request URI or a URI wildcard.
- /// to allow wildcards, or if exact matching.
- public RequestUriMatcher(string uriString, bool allowWildcards = true)
- {
- _formattedUri = uriString ?? throw new ArgumentNullException(nameof(uriString));
-
- if (allowWildcards
-#if NETSTANDARD2_0 || NETFRAMEWORK
- && uriString.Contains("*")
-#else
- && uriString.Contains('*', StringComparison.InvariantCultureIgnoreCase)
-#endif
- )
- {
- _uriPatternMatcher = new WildcardPatternMatcher(uriString);
- }
- else
- {
- // If no wildcards, then must be actual uri.
- _requestUri = new Uri(uriString, DotNetRelativeOrAbsolute).EnsureIsRooted();
- _formattedUri = _requestUri.ToString();
- }
- }
-
- ///
- public override bool IsMatch(MockHttpRequestContext requestContext)
- {
- if (requestContext is null)
- {
- throw new ArgumentNullException(nameof(requestContext));
- }
-
- Uri? requestUri = requestContext.Request.RequestUri;
- if (requestUri is null)
- {
- return false;
- }
-
- if (_uriPatternMatcher is null)
- {
- return IsAbsoluteUriMatch(requestUri) || IsRelativeUriMatch(requestUri);
- }
-
- return _uriPatternMatcher.IsMatch(requestUri.ToString());
- }
-
- private bool IsAbsoluteUriMatch(Uri uri)
- {
- return _requestUri.IsAbsoluteUri && uri.Equals(_requestUri);
- }
-
- private bool IsRelativeUriMatch(Uri uri)
- {
- return !_requestUri.IsAbsoluteUri
- && uri.IsBaseOf(_requestUri)
- && uri.ToString().EndsWith(_requestUri.ToString(), StringComparison.Ordinal);
- }
-
- ///
- public override string ToString()
- {
- return $"RequestUri: '{_formattedUri}'";
- }
-}
diff --git a/test/MockHttp.Tests/Extensions/RequestMatchingExtensionsTests.cs b/test/MockHttp.Tests/Extensions/RequestMatchingExtensionsTests.cs
index 4ba231e0..bd876f64 100644
--- a/test/MockHttp.Tests/Extensions/RequestMatchingExtensionsTests.cs
+++ b/test/MockHttp.Tests/Extensions/RequestMatchingExtensionsTests.cs
@@ -1,4 +1,5 @@
-using System.Collections.Specialized;
+using System;
+using System.Collections.Specialized;
using System.Linq.Expressions;
using System.Net.Http.Headers;
using System.Text;
@@ -6,6 +7,7 @@
using MockHttp.Http;
using MockHttp.Matchers;
using MockHttp.Responses;
+using static MockHttp.Http.UriExtensions;
namespace MockHttp.Extensions;
@@ -20,33 +22,184 @@ protected RequestMatchingExtensionsTests()
public class RequestUri : RequestMatchingExtensionsTests
{
- [Fact]
- public async Task When_configuring_requestUri_as_string_should_match()
+ [Theory]
+ [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)]
+ [InlineData("relative.htm", false, "http://127.0.0.1/relative.htm?query=string", false)]
+
+ [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)]
+ [InlineData("/relative.htm", false, "http://127.0.0.1/relative.htm?query=string", false)]
+
+ [InlineData("relative.htm?query=string", true, "http://127.0.0.1/relative.htm?query=string", true)]
+ [InlineData("relative.htm?query=string", false, "http://127.0.0.1/relative.htm?query=string", true)]
+ [InlineData("http://127.0.0.1/absolute.htm?query=string", true, "http://127.0.0.1/absolute.htm?query=string", true)]
+ [InlineData("http://127.0.0.1/absolute.htm?query=string", false, "http://127.0.0.1/absolute.htm?query=string", true)]
+
+ [InlineData("/folder/relative.htm", true, "http://127.0.0.1/relative.htm", false)]
+ [InlineData("/folder/relative.htm", true, "http://127.0.0.1/relative.htm?query=string", false)]
+ [InlineData("/folder/relative.htm", false, "http://127.0.0.1/relative.htm", false)]
+ [InlineData("/folder/relative.htm", false, "http://127.0.0.1/relative.htm?query=string", false)]
+
+ [InlineData("relative.htm", true, "http://127.0.0.1/folder/relative.htm", false)]
+ [InlineData("relative.htm", true, "http://127.0.0.1/folder/relative.htm?query=string", false)]
+ [InlineData("relative.htm", false, "http://127.0.0.1/folder/relative.htm", false)]
+ [InlineData("relative.htm", false, "http://127.0.0.1/folder/relative.htm?query=string", false)]
+
+ [InlineData("folder/relative.htm", true, "http://127.0.0.1/folder/relative.htm", true)]
+ [InlineData("folder/relative.htm", true, "http://127.0.0.1/folder/relative.htm?query=string", false)]
+ [InlineData("folder/relative.htm", false, "http://127.0.0.1/folder/relative.htm", true)]
+ [InlineData("folder/relative.htm", false, "http://127.0.0.1/folder/relative.htm?query=string", false)]
+
+ [InlineData("/folder/relative.htm", true, "http://127.0.0.1/folder/relative.htm", true)]
+ [InlineData("/folder/relative.htm", true, "http://127.0.0.1/folder/relative.htm?query=string", false)]
+ [InlineData("/folder/relative.htm", false, "http://127.0.0.1/folder/relative.htm", true)]
+ [InlineData("/folder/relative.htm", false, "http://127.0.0.1/folder/relative.htm?query=string", false)]
+
+ [InlineData("http://127.0.0.1/absolute.htm", true, "http://127.0.0.1/absolute.htm", true)]
+ [InlineData("http://127.0.0.1/absolute.htm", true, "http://127.0.0.1/absolute.htm?query=string", false)]
+ [InlineData("http://127.0.0.1/absolute.htm", false, "http://127.0.0.1/absolute.htm", true)]
+ [InlineData("http://127.0.0.1/absolute.htm", false, "http://127.0.0.1/absolute.htm?query=string", false)]
+
+ [InlineData("http://127.0.0.1/absolute.htm", true, "http://127.0.0.1/folder/absolute.htm", false)]
+ [InlineData("http://127.0.0.1/absolute.htm", true, "http://127.0.0.1/folder/absolute.htm?query=string", false)]
+ [InlineData("http://127.0.0.1/absolute.htm", false, "http://127.0.0.1/folder/absolute.htm", false)]
+ [InlineData("http://127.0.0.1/absolute.htm", false, "http://127.0.0.1/folder/absolute.htm?query=string", false)]
+
+ [InlineData("http://127.0.0.1/folder/absolute.htm", true, "http://127.0.0.1/folder/absolute.htm", true)]
+ [InlineData("http://127.0.0.1/folder/absolute.htm", true, "http://127.0.0.1/folder/absolute.htm?query=string", false)]
+ [InlineData("http://127.0.0.1/folder/absolute.htm", false, "http://127.0.0.1/folder/absolute.htm", true)]
+ [InlineData("http://127.0.0.1/folder/absolute.htm", false, "http://127.0.0.1/folder/absolute.htm?query=string", false)]
+
+ [InlineData("*.htm", true, "http://127.0.0.1/relative.htm", true)]
+ [InlineData("*.htm", true, "http://127.0.0.1/relative.htm?query=string", false)]
+ [InlineData("*.htm", false, "http://127.0.0.1/relative.htm", false)]
+
+ [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", false)]
+
+ [InlineData("/*/relative.htm", true, "http://127.0.0.1/folder/relative.htm", false)]
+ [InlineData("/*/relative.htm", true, "http://127.0.0.1/folder/relative.htm?query=string", false)]
+ [InlineData("/*/relative.htm", false, "http://127.0.0.1/folder/relative.htm", false)]
+
+ [InlineData("/*/relative.htm", true, "http://127.0.0.1/relative.htm", false)]
+ [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", false)]
+
+ [InlineData("/folder/*.htm", true, "http://127.0.0.1/folder/relative.htm", false)]
+ [InlineData("/folder/*.htm", true, "http://127.0.0.1/folder/relative.htm?query=string", false)]
+ [InlineData("/folder/*.htm", false, "http://127.0.0.1/folder/relative.htm", false)]
+
+ [InlineData("*/folder/*.htm", true, "http://127.0.0.1/folder/relative.htm", true)]
+ [InlineData("*/folder/*.htm", true, "http://127.0.0.1/folder/relative.htm?query=string", false)]
+ [InlineData("*/folder/*.htm", false, "http://127.0.0.1/folder/relative.htm", false)]
+
+ [InlineData("/folder/*.htm", true, "http://127.0.0.1/relative.htm", false)]
+ [InlineData("/folder/*.htm", true, "http://127.0.0.1/relative.htm?query=string", false)]
+ [InlineData("/folder/*.htm", false, "http://127.0.0.1/relative.htm", false)]
+
+ [InlineData("/*/*/relative.*", true, "http://127.0.0.1/folder1/folder2/relative.htm", false)]
+ [InlineData("/*/*/relative.*", true, "http://127.0.0.1/folder1/folder2/relative.htm?query=string", false)]
+ [InlineData("/*/*/relative.*", false, "http://127.0.0.1/folder1/folder2/relative.htm", false)]
+
+ [InlineData("*/folder1/*/relative.*", true, "http://127.0.0.1/folder1/folder2/relative.htm", true)]
+ [InlineData("*/folder1/*/relative.*", true, "http://127.0.0.1/folder1/folder2/relative.htm?query=string", true)]
+ [InlineData("*/folder1/*/relative.*", false, "http://127.0.0.1/folder1/folder2/relative.htm", false)]
+
+ [InlineData("/*/*/relative.*", true, "http://127.0.0.1/folder1/relative.htm", false)]
+ [InlineData("/*/*/relative.*", true, "http://127.0.0.1/folder1/relative.htm?query=string", false)]
+ [InlineData("/*/*/relative.*", false, "http://127.0.0.1/folder1/relative.htm", false)]
+
+ [InlineData("http://127.0.0.1/*.htm", true, "http://127.0.0.1/absolute.htm", true)]
+ [InlineData("http://127.0.0.1/*.htm", true, "http://127.0.0.1/absolute.htm?query=string", false)]
+ [InlineData("http://127.0.0.1/*.htm", false, "http://127.0.0.1/absolute.htm", false)]
+
+ [InlineData("http://127.0.0.1/*.htm", true, "http://127.0.0.1/folder/absolute.htm", true)]
+ [InlineData("http://127.0.0.1/*.htm", true, "http://127.0.0.1/folder/absolute.htm?query=string", false)]
+ [InlineData("http://127.0.0.1/*.htm", false, "http://127.0.0.1/folder/absolute.htm", false)]
+ public async Task When_configuring_requestUri_as_string_it_should_match(string uriString, bool allowWildcards, string requestUri, bool isMatch)
{
- var request = new HttpRequestMessage { RequestUri = new Uri("http://127.0.0.1/") };
+ var request = new HttpRequestMessage { RequestUri = new Uri(requestUri, UriKind.Absolute) };
// Act
- _sut.RequestUri("http://127.0.0.1/");
+ _sut.RequestUri(uriString, allowWildcards);
IReadOnlyCollection matchers = _sut.Build();
// Assert
- matchers.Should().HaveCount(1).And.AllBeOfType();
- (await matchers.AnyAsync(new MockHttpRequestContext(request))).Should().BeTrue();
+ matchers.Should().HaveCount(1).And.AllBeAssignableTo();
+ (await matchers.AnyAsync(new MockHttpRequestContext(request))).Should().Be(isMatch);
}
- [Fact]
- public async Task When_configuring_requestUri_as_uri_should_match()
+ [Theory]
+ [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)]
+ [InlineData("folder/relative.htm", UriKind.Relative, "http://127.0.0.1/folder/relative.htm", true)]
+ [InlineData("/folder/relative.htm", UriKind.Relative, "http://127.0.0.1/folder/relative.htm", true)]
+ [InlineData("http://127.0.0.1/absolute.htm", UriKind.Absolute, "http://127.0.0.1/absolute.htm", true)]
+ [InlineData("http://127.0.0.1/absolute.htm", UriKind.Absolute, "http://127.0.0.1/folder/absolute.htm", false)]
+ public async Task When_configuring_requestUri_as_uri_it_should_match(string matchUri, UriKind uriKind, string requestUri, bool isMatch)
{
- var uri = new Uri("http://127.0.0.1/");
- var request = new HttpRequestMessage { RequestUri = new Uri("http://127.0.0.1/") };
+ var uri = new Uri(matchUri, uriKind);
+ var request = new HttpRequestMessage { RequestUri = new Uri(requestUri, UriKind.Absolute) };
// Act
_sut.RequestUri(uri);
IReadOnlyCollection matchers = _sut.Build();
// Assert
- matchers.Should().HaveCount(1).And.AllBeOfType();
- (await matchers.AnyAsync(new MockHttpRequestContext(request))).Should().BeTrue();
+ matchers.Should().HaveCount(1).And.AllBeAssignableTo();
+ (await matchers.AnyAsync(new MockHttpRequestContext(request))).Should().Be(isMatch);
+ }
+
+ [Theory]
+ [InlineData("*/controller/*", false)]
+ [InlineData("*/controller/*", true)]
+ [InlineData("file.jpg", true)]
+ [InlineData("file.jpg", false)]
+ [InlineData("http://0.0.0.0/path/file.jpg", true)]
+ [InlineData("http://0.0.0.0/path/file.jpg", false)]
+ public void When_formatting_uriString_matcher_it_should_return_human_readable_representation(string uriString, bool allowWildcards)
+ {
+ string expectedText = $"RequestUri: '{uriString}'";
+
+ // Act
+ _sut.RequestUri(uriString, allowWildcards);
+ IReadOnlyCollection matchers = _sut.Build();
+ string displayText = matchers.Should()
+ .ContainSingle()
+ .Which.Should()
+ .BeAssignableTo()
+ .Which.ToString();
+
+ // Assert
+ displayText.Should().Be(expectedText);
+ }
+
+ [Theory]
+ [InlineData("*/controller/*")]
+ [InlineData("file.jpg")]
+ [InlineData("http://0.0.0.0/path/file.jpg")]
+ public void When_formatting_uri_matcher_it_should_return_human_readable_representation(string uriString)
+ {
+ string expectedText = $"RequestUri: '{uriString}'";
+ var uri = new Uri(uriString, DotNetRelativeOrAbsolute);
+
+ // Act
+ _sut.RequestUri(uri);
+ IReadOnlyCollection matchers = _sut.Build();
+ string displayText = matchers.Should()
+ .ContainSingle()
+ .Which.Should()
+ .BeAssignableTo()
+ .Which.ToString();
+
+ // Assert
+ displayText.Should().Be(expectedText);
}
}
@@ -692,7 +845,8 @@ public void Given_null_argument_when_executing_method_it_should_throw(params obj
DelegateTestCase.Create(
RequestMatchingExtensions.RequestUri,
instance,
- uri.ToString()),
+ uri.ToString(),
+ true),
DelegateTestCase.Create(
RequestMatchingExtensions.RequestUri,
instance,
diff --git a/test/MockHttp.Tests/Matchers/AnyMatcherTests.cs b/test/MockHttp.Tests/Matchers/AnyMatcherTests.cs
index 2491861a..2615588b 100644
--- a/test/MockHttp.Tests/Matchers/AnyMatcherTests.cs
+++ b/test/MockHttp.Tests/Matchers/AnyMatcherTests.cs
@@ -18,8 +18,8 @@ public AnyMatcherTests()
[InlineData("url2")]
public async Task Given_request_uri_equals_one_of_the_matchers_when_matching_should_match(string requestUrl)
{
- _matchers.Add(new RequestUriMatcher("*url1"));
- _matchers.Add(new RequestUriMatcher("*url2"));
+ _matchers.Add(new BoolMatcher(requestUrl == "url1"));
+ _matchers.Add(new BoolMatcher(requestUrl == "url2"));
var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
@@ -32,8 +32,8 @@ public async Task Given_request_uri_equals_one_of_the_matchers_when_matching_sho
[Fact]
public async Task Given_request_uri_matches_none_of_the_matchers_when_matching_should_not_match()
{
- _matchers.Add(new RequestUriMatcher("http://127.0.0.1/"));
- _matchers.Add(new RequestUriMatcher("http://127.0.0.2/"));
+ _matchers.Add(new BoolMatcher(false));
+ _matchers.Add(new BoolMatcher(false));
var request = new HttpRequestMessage(HttpMethod.Get, "http://127.0.0.3/");
diff --git a/test/MockHttp.Tests/Matchers/BoolMatcher.cs b/test/MockHttp.Tests/Matchers/BoolMatcher.cs
new file mode 100644
index 00000000..7ff182da
--- /dev/null
+++ b/test/MockHttp.Tests/Matchers/BoolMatcher.cs
@@ -0,0 +1,23 @@
+using MockHttp.Responses;
+
+namespace MockHttp.Matchers;
+
+internal sealed class BoolMatcher : HttpRequestMatcher
+{
+ private readonly bool _shouldMatch;
+
+ public BoolMatcher(bool shouldMatch)
+ {
+ _shouldMatch = shouldMatch;
+ }
+
+ public override bool IsMatch(MockHttpRequestContext requestContext)
+ {
+ return _shouldMatch;
+ }
+
+ public override string ToString()
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/test/MockHttp.Tests/Matchers/RequestUriMatcherTests.cs b/test/MockHttp.Tests/Matchers/RequestUriMatcherTests.cs
deleted file mode 100644
index 0a0943ad..00000000
--- a/test/MockHttp.Tests/Matchers/RequestUriMatcherTests.cs
+++ /dev/null
@@ -1,109 +0,0 @@
-using MockHttp.Responses;
-
-namespace MockHttp.Matchers;
-
-public class RequestUriMatcherTests
-{
- [Theory]
- [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)]
- [InlineData("folder/relative.htm", UriKind.Relative, "http://127.0.0.1/folder/relative.htm", true)]
- [InlineData("/folder/relative.htm", UriKind.Relative, "http://127.0.0.1/folder/relative.htm", true)]
- [InlineData("http://127.0.0.1/absolute.htm", UriKind.Absolute, "http://127.0.0.1/absolute.htm", true)]
- [InlineData("http://127.0.0.1/absolute.htm", UriKind.Absolute, "http://127.0.0.1/folder/absolute.htm", false)]
- public void Given_uri_when_matching_should_match(string matchUri, UriKind uriKind, string requestUri, bool isMatch)
- {
- var request = new HttpRequestMessage { RequestUri = new Uri(requestUri, UriKind.Absolute) };
- var sut = new RequestUriMatcher(new Uri(matchUri, uriKind));
-
- // Act & assert
- sut.IsMatch(new MockHttpRequestContext(request)).Should().Be(isMatch);
- }
-
- [Theory]
- [InlineData("relative.htm", true, "http://127.0.0.1/relative.htm", true)]
- [InlineData("/folder/relative.htm", true, "http://127.0.0.1/relative.htm", false)]
- [InlineData("relative.htm", true, "http://127.0.0.1/folder/relative.htm", false)]
- [InlineData("folder/relative.htm", true, "http://127.0.0.1/folder/relative.htm", true)]
- [InlineData("/folder/relative.htm", true, "http://127.0.0.1/folder/relative.htm", true)]
- [InlineData("http://127.0.0.1/absolute.htm", true, "http://127.0.0.1/absolute.htm", true)]
- [InlineData("http://127.0.0.1/absolute.htm", true, "http://127.0.0.1/folder/absolute.htm", false)]
- [InlineData("*.htm", true, "http://127.0.0.1/relative.htm", true)]
- [InlineData("*/relative.htm", true, "http://127.0.0.1/relative.htm", true)]
- [InlineData("/*/relative.htm", true, "http://127.0.0.1/folder/relative.htm", false)]
- [InlineData("/*/relative.htm", true, "http://127.0.0.1/relative.htm", false)]
- [InlineData("/folder/*.htm", true, "http://127.0.0.1/folder/relative.htm", false)]
- [InlineData("*/folder/*.htm", true, "http://127.0.0.1/folder/relative.htm", true)]
- [InlineData("/folder/*.htm", true, "http://127.0.0.1/relative.htm", false)]
- [InlineData("/*/*/relative.*", true, "http://127.0.0.1/folder1/folder2/relative.htm", false)]
- [InlineData("*/folder1/*/relative.*", true, "http://127.0.0.1/folder1/folder2/relative.htm", true)]
- [InlineData("/*/*/relative.*", true, "http://127.0.0.1/folder1/relative.htm", false)]
- [InlineData("http://127.0.0.1/*.htm", true, "http://127.0.0.1/absolute.htm", true)]
- [InlineData("http://127.0.0.1/*.htm", true, "http://127.0.0.1/folder/absolute.htm", true)]
- public void Given_uriString_when_matching_should_match(string uriString, bool hasWildcard, string requestUri, bool isMatch)
- {
- var request = new HttpRequestMessage { RequestUri = new Uri(requestUri, UriKind.Absolute) };
- var sut = new RequestUriMatcher(uriString, hasWildcard);
-
- // Act & assert
- sut.IsMatch(new MockHttpRequestContext(request)).Should().Be(isMatch);
- }
-
- [Fact]
- public void Given_null_uri_when_creating_matcher_should_throw()
- {
- Uri? uri = null;
-
- // Act
- Func act = () => new RequestUriMatcher(uri!);
-
- // Assert
- act.Should()
- .Throw()
- .WithParameterName(nameof(uri));
- }
-
- [Fact]
- public void Given_null_uriString_when_creating_matcher_should_throw()
- {
- string? uriString = null;
-
- // Act
- Func act = () => new RequestUriMatcher(uriString!, false);
-
- // Assert
- act.Should()
- .Throw()
- .WithParameterName(nameof(uriString));
- }
-
- [Fact]
- public void When_formatting_should_return_human_readable_representation()
- {
- const string expectedText = "RequestUri: '*/controller/*'";
- var sut = new RequestUriMatcher("*/controller/*");
-
- // Act
- string displayText = sut.ToString();
-
- // Assert
- displayText.Should().Be(expectedText);
- }
-
- [Fact]
- public void Given_null_context_when_matching_it_should_throw()
- {
- var sut = new RequestUriMatcher("*/controller/*");
- MockHttpRequestContext? requestContext = null;
-
- // Act
- Action act = () => sut.IsMatch(requestContext!);
-
- // Assert
- act.Should()
- .Throw()
- .WithParameterName(nameof(requestContext));
- }
-}