From 6e8803001b3a0132788795fe5a17710b5e6a77a4 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 9 Mar 2024 09:26:46 +0100 Subject: [PATCH] Make WireMockAssertions extendable --- .../Assertions/WireMockAssertions.AtUrl.cs | 60 +++++++++ .../WireMockAssertions.FromClientIP.cs | 33 +++++ .../WireMockAssertions.UsingMethod.cs | 6 +- .../Assertions/WireMockAssertions.WithBody.cs | 18 +-- .../WireMockAssertions.WithHeader.cs | 28 ++-- .../WireMockAssertions.WithProxy.cs | 34 +++++ .../Assertions/WireMockAssertions.cs | 127 ++---------------- .../WireMockAssertionsExtensions.cs | 37 +++++ .../WireMockAssertionsTests.cs | 12 +- 9 files changed, 215 insertions(+), 140 deletions(-) create mode 100644 src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.AtUrl.cs create mode 100644 src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.FromClientIP.cs create mode 100644 src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithProxy.cs create mode 100644 test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsExtensions.cs diff --git a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.AtUrl.cs b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.AtUrl.cs new file mode 100644 index 000000000..39451e215 --- /dev/null +++ b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.AtUrl.cs @@ -0,0 +1,60 @@ +#pragma warning disable CS1591 +using System; + +// ReSharper disable once CheckNamespace +namespace WireMock.FluentAssertions; + +public partial class WireMockAssertions +{ + [CustomAssertion] + public AndWhichConstraint AtAbsoluteUrl(string absoluteUrl, string because = "", params object[] becauseArgs) + { + var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.AbsoluteUrl, absoluteUrl, StringComparison.OrdinalIgnoreCase)); + + Execute.Assertion + .BecauseOf(because, becauseArgs) + .Given(() => RequestMessages) + .ForCondition(requests => CallsCount == 0 || requests.Any()) + .FailWith( + "Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but no calls were made.", + absoluteUrl + ) + .Then + .ForCondition(condition) + .FailWith( + "Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but didn't find it among the calls to {1}.", + _ => absoluteUrl, + requests => requests.Select(request => request.AbsoluteUrl) + ); + + FilterRequestMessages(filter); + + return new AndWhichConstraint(this, absoluteUrl); + } + + [CustomAssertion] + public AndWhichConstraint AtUrl(string url, string because = "", params object[] becauseArgs) + { + var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.Url, url, StringComparison.OrdinalIgnoreCase)); + + Execute.Assertion + .BecauseOf(because, becauseArgs) + .Given(() => RequestMessages) + .ForCondition(requests => CallsCount == 0 || requests.Any()) + .FailWith( + "Expected {context:wiremockserver} to have been called at address matching the url {0}{reason}, but no calls were made.", + url + ) + .Then + .ForCondition(condition) + .FailWith( + "Expected {context:wiremockserver} to have been called at address matching the url {0}{reason}, but didn't find it among the calls to {1}.", + _ => url, + requests => requests.Select(request => request.Url) + ); + + FilterRequestMessages(filter); + + return new AndWhichConstraint(this, url); + } +} \ No newline at end of file diff --git a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.FromClientIP.cs b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.FromClientIP.cs new file mode 100644 index 000000000..e7651bc1f --- /dev/null +++ b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.FromClientIP.cs @@ -0,0 +1,33 @@ +#pragma warning disable CS1591 +using System; + +// ReSharper disable once CheckNamespace +namespace WireMock.FluentAssertions; + +public partial class WireMockAssertions +{ + [CustomAssertion] + public AndWhichConstraint FromClientIP(string clientIP, string because = "", params object[] becauseArgs) + { + var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.ClientIP, clientIP, StringComparison.OrdinalIgnoreCase)); + + Execute.Assertion + .BecauseOf(because, becauseArgs) + .Given(() => RequestMessages) + .ForCondition(requests => CallsCount == 0 || requests.Any()) + .FailWith( + "Expected {context:wiremockserver} to have been called from client IP {0}{reason}, but no calls were made.", + clientIP + ) + .Then + .ForCondition(condition) + .FailWith( + "Expected {context:wiremockserver} to have been called from client IP {0}{reason}, but didn't find it among the calls from IP(s) {1}.", + _ => clientIP, requests => requests.Select(request => request.ClientIP) + ); + + FilterRequestMessages(filter); + + return new AndWhichConstraint(this, clientIP); + } +} \ No newline at end of file diff --git a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.UsingMethod.cs b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.UsingMethod.cs index 410ad46fd..3b29d992b 100644 --- a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.UsingMethod.cs +++ b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.UsingMethod.cs @@ -58,8 +58,8 @@ public AndConstraint UsingMethod(string method, string becau Execute.Assertion .BecauseOf(because, becauseArgs) - .Given(() => _requestMessages) - .ForCondition(requests => _callsCount == 0 || requests.Any()) + .Given(() => RequestMessages) + .ForCondition(requests => CallsCount == 0 || requests.Any()) .FailWith( "Expected {context:wiremockserver} to have been called using method {0}{reason}, but no calls were made.", method @@ -72,7 +72,7 @@ public AndConstraint UsingMethod(string method, string becau requests => requests.Select(request => request.Method) ); - _requestMessages = filter(_requestMessages).ToList(); + FilterRequestMessages(filter); return new AndConstraint(this); } diff --git a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithBody.cs b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithBody.cs index e1078f744..129c1bab0 100644 --- a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithBody.cs +++ b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithBody.cs @@ -70,8 +70,8 @@ private AndConstraint ExecuteAssertionWithBodyStringMatcher( { Execute.Assertion .BecauseOf(because, becauseArgs) - .Given(() => _requestMessages) - .ForCondition(requests => _callsCount == 0 || requests.Any()) + .Given(() => RequestMessages) + .ForCondition(requests => CallsCount == 0 || requests.Any()) .FailWith( MessageFormatNoCalls, matcher.GetPatterns() @@ -84,7 +84,7 @@ private AndConstraint ExecuteAssertionWithBodyStringMatcher( requests => requests.Select(expression) ); - _requestMessages = filter(_requestMessages).ToList(); + FilterRequestMessages(filter); return new AndConstraint(this); } @@ -100,8 +100,8 @@ private AndConstraint ExecuteAssertionWithBodyAsIObjectMatch { Execute.Assertion .BecauseOf(because, becauseArgs) - .Given(() => _requestMessages) - .ForCondition(requests => _callsCount == 0 || requests.Any()) + .Given(() => RequestMessages) + .ForCondition(requests => CallsCount == 0 || requests.Any()) .FailWith( MessageFormatNoCalls, matcher.Value @@ -114,7 +114,7 @@ private AndConstraint ExecuteAssertionWithBodyAsIObjectMatch requests => requests.Select(expression) ); - _requestMessages = filter(_requestMessages).ToList(); + FilterRequestMessages(filter); return new AndConstraint(this); } @@ -130,8 +130,8 @@ private AndConstraint ExecuteAssertionWithBodyAsBytesExactOb { Execute.Assertion .BecauseOf(because, becauseArgs) - .Given(() => _requestMessages) - .ForCondition(requests => _callsCount == 0 || requests.Any()) + .Given(() => RequestMessages) + .ForCondition(requests => CallsCount == 0 || requests.Any()) .FailWith( MessageFormatNoCalls, matcher.Value @@ -144,7 +144,7 @@ private AndConstraint ExecuteAssertionWithBodyAsBytesExactOb requests => requests.Select(expression) ); - _requestMessages = filter(_requestMessages).ToList(); + FilterRequestMessages(filter); return new AndConstraint(this); } diff --git a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithHeader.cs b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithHeader.cs index d6ca8940a..9e90c5def 100644 --- a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithHeader.cs +++ b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithHeader.cs @@ -8,7 +8,7 @@ namespace WireMock.FluentAssertions; public partial class WireMockAssertions { [CustomAssertion] - public AndConstraint WitHeaderKey(string expectedKey, string because = "", params object[] becauseArgs) + public AndWhichConstraint WitHeaderKey(string expectedKey, string because = "", params object[] becauseArgs) { var (filter, condition) = BuildFilterAndCondition(request => { @@ -17,8 +17,8 @@ public AndConstraint WitHeaderKey(string expectedKey, string Execute.Assertion .BecauseOf(because, becauseArgs) - .Given(() => _requestMessages) - .ForCondition(requests => _callsCount == 0 || requests.Any()) + .Given(() => RequestMessages) + .ForCondition(requests => CallsCount == 0 || requests.Any()) .FailWith( "Expected {context:wiremockserver} to have been called with Header {0}{reason}.", expectedKey @@ -31,9 +31,9 @@ public AndConstraint WitHeaderKey(string expectedKey, string requests => requests.Select(request => request.Headers) ); - _requestMessages = filter(_requestMessages).ToList(); + FilterRequestMessages(filter); - return new AndConstraint(this); + return new AndWhichConstraint(this, expectedKey); } [CustomAssertion] @@ -60,8 +60,8 @@ public AndConstraint WithHeader(string expectedKey, string[] Execute.Assertion .BecauseOf(because, becauseArgs) - .Given(() => _requestMessages) - .ForCondition(requests => _callsCount == 0 || requests.Any()) + .Given(() => RequestMessages) + .ForCondition(requests => CallsCount == 0 || requests.Any()) .FailWith( "Expected {context:wiremockserver} to have been called with Header {0} and Values {1}{reason}.", expectedKey, @@ -76,7 +76,7 @@ public AndConstraint WithHeader(string expectedKey, string[] requests => requests.Select(request => request.Headers) ); - _requestMessages = filter(_requestMessages).ToList(); + FilterRequestMessages(filter); return new AndConstraint(this); } @@ -91,8 +91,8 @@ public AndConstraint WithoutHeaderKey(string unexpectedKey, Execute.Assertion .BecauseOf(because, becauseArgs) - .Given(() => _requestMessages) - .ForCondition(requests => _callsCount == 0 || requests.Any()) + .Given(() => RequestMessages) + .ForCondition(requests => CallsCount == 0 || requests.Any()) .FailWith( "Expected {context:wiremockserver} not to have been called with Header {0}{reason}.", unexpectedKey @@ -105,7 +105,7 @@ public AndConstraint WithoutHeaderKey(string unexpectedKey, requests => requests.Select(request => request.Headers) ); - _requestMessages = filter(_requestMessages).ToList(); + FilterRequestMessages(filter); return new AndConstraint(this); } @@ -134,8 +134,8 @@ public AndConstraint WithoutHeader(string unexpectedKey, str Execute.Assertion .BecauseOf(because, becauseArgs) - .Given(() => _requestMessages) - .ForCondition(requests => _callsCount == 0 || requests.Any()) + .Given(() => RequestMessages) + .ForCondition(requests => CallsCount == 0 || requests.Any()) .FailWith( "Expected {context:wiremockserver} not to have been called with Header {0} and Values {1}{reason}.", unexpectedKey, @@ -150,7 +150,7 @@ public AndConstraint WithoutHeader(string unexpectedKey, str requests => requests.Select(request => request.Headers) ); - _requestMessages = filter(_requestMessages).ToList(); + FilterRequestMessages(filter); return new AndConstraint(this); } diff --git a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithProxy.cs b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithProxy.cs new file mode 100644 index 000000000..b820b0a62 --- /dev/null +++ b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithProxy.cs @@ -0,0 +1,34 @@ +#pragma warning disable CS1591 +using System; + +// ReSharper disable once CheckNamespace +namespace WireMock.FluentAssertions; + +public partial class WireMockAssertions +{ + [CustomAssertion] + public AndWhichConstraint WithProxyUrl(string proxyUrl, string because = "", params object[] becauseArgs) + { + var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.ProxyUrl, proxyUrl, StringComparison.OrdinalIgnoreCase)); + + Execute.Assertion + .BecauseOf(because, becauseArgs) + .Given(() => RequestMessages) + .ForCondition(requests => CallsCount == 0 || requests.Any()) + .FailWith( + "Expected {context:wiremockserver} to have been called with proxy url {0}{reason}, but no calls were made.", + proxyUrl + ) + .Then + .ForCondition(condition) + .FailWith( + "Expected {context:wiremockserver} to have been called with proxy url {0}{reason}, but didn't find it among the calls with {1}.", + _ => proxyUrl, + requests => requests.Select(request => request.ProxyUrl) + ); + + FilterRequestMessages(filter); + + return new AndWhichConstraint(this, proxyUrl); + } +} \ No newline at end of file diff --git a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.cs b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.cs index 692ca198e..b59181b09 100644 --- a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.cs +++ b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.cs @@ -9,135 +9,36 @@ namespace WireMock.FluentAssertions; public partial class WireMockAssertions { - private const string Any = "*"; - private readonly int? _callsCount; - private IReadOnlyList _requestMessages; - //private readonly IReadOnlyList>> _headers; + public const string Any = "*"; - public WireMockAssertions(IWireMockServer subject, int? callsCount) - { - _callsCount = callsCount; - _requestMessages = subject.LogEntries.Select(logEntry => logEntry.RequestMessage).ToList(); - // _headers = _requestMessages.SelectMany(req => req.Headers).ToList(); - } - - [CustomAssertion] - public AndWhichConstraint AtAbsoluteUrl(string absoluteUrl, string because = "", params object[] becauseArgs) - { - var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.AbsoluteUrl, absoluteUrl, StringComparison.OrdinalIgnoreCase)); - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .Given(() => _requestMessages) - .ForCondition(requests => _callsCount == 0 || requests.Any()) - .FailWith( - "Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but no calls were made.", - absoluteUrl - ) - .Then - .ForCondition(condition) - .FailWith( - "Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but didn't find it among the calls to {1}.", - _ => absoluteUrl, - requests => requests.Select(request => request.AbsoluteUrl) - ); - - _requestMessages = filter(_requestMessages).ToList(); - - return new AndWhichConstraint(this, absoluteUrl); - } - - [CustomAssertion] - public AndWhichConstraint AtUrl(string url, string because = "", params object[] becauseArgs) - { - var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.Url, url, StringComparison.OrdinalIgnoreCase)); - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .Given(() => _requestMessages) - .ForCondition(requests => _callsCount == 0 || requests.Any()) - .FailWith( - "Expected {context:wiremockserver} to have been called at address matching the url {0}{reason}, but no calls were made.", - url - ) - .Then - .ForCondition(condition) - .FailWith( - "Expected {context:wiremockserver} to have been called at address matching the url {0}{reason}, but didn't find it among the calls to {1}.", - _ => url, - requests => requests.Select(request => request.Url) - ); - - _requestMessages = filter(_requestMessages).ToList(); + public int? CallsCount { get; } + public IReadOnlyList RequestMessages { get; private set; } - return new AndWhichConstraint(this, url); - } - - [CustomAssertion] - public AndWhichConstraint WithProxyUrl(string proxyUrl, string because = "", params object[] becauseArgs) + public WireMockAssertions(IWireMockServer subject, int? callsCount) { - var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.ProxyUrl, proxyUrl, StringComparison.OrdinalIgnoreCase)); - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .Given(() => _requestMessages) - .ForCondition(requests => _callsCount == 0 || requests.Any()) - .FailWith( - "Expected {context:wiremockserver} to have been called with proxy url {0}{reason}, but no calls were made.", - proxyUrl - ) - .Then - .ForCondition(condition) - .FailWith( - "Expected {context:wiremockserver} to have been called with proxy url {0}{reason}, but didn't find it among the calls with {1}.", - _ => proxyUrl, - requests => requests.Select(request => request.ProxyUrl) - ); - - _requestMessages = filter(_requestMessages).ToList(); - - return new AndWhichConstraint(this, proxyUrl); + CallsCount = callsCount; + RequestMessages = subject.LogEntries.Select(logEntry => logEntry.RequestMessage).ToList(); } - [CustomAssertion] - public AndWhichConstraint FromClientIP(string clientIP, string because = "", params object[] becauseArgs) + public (Func, IReadOnlyList> Filter, Func, bool> Condition) BuildFilterAndCondition(Func predicate) { - var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.ClientIP, clientIP, StringComparison.OrdinalIgnoreCase)); - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .Given(() => _requestMessages) - .ForCondition(requests => _callsCount == 0 || requests.Any()) - .FailWith( - "Expected {context:wiremockserver} to have been called from client IP {0}{reason}, but no calls were made.", - clientIP - ) - .Then - .ForCondition(condition) - .FailWith( - "Expected {context:wiremockserver} to have been called from client IP {0}{reason}, but didn't find it among the calls from IP(s) {1}.", - _ => clientIP, requests => requests.Select(request => request.ClientIP) - ); - - _requestMessages = filter(_requestMessages).ToList(); + Func, IReadOnlyList> filter = requests => requests.Where(predicate).ToList(); - return new AndWhichConstraint(this, clientIP); + return (filter, requests => (CallsCount is null && filter(requests).Any()) || CallsCount == filter(requests).Count); } - private (Func, IReadOnlyList> Filter, Func, bool> Condition) BuildFilterAndCondition(Func predicate) + public (Func, IReadOnlyList> Filter, Func, bool> Condition) BuildFilterAndCondition(Func expression, IStringMatcher matcher) { - Func, IReadOnlyList> filter = requests => requests.Where(predicate).ToList(); - - return (filter, requests => (_callsCount is null && filter(requests).Any()) || _callsCount == filter(requests).Count); + return BuildFilterAndCondition(r => matcher.IsMatch(expression(r)).IsPerfect()); } - private (Func, IReadOnlyList> Filter, Func, bool> Condition) BuildFilterAndCondition(Func expression, IStringMatcher matcher) + public (Func, IReadOnlyList> Filter, Func, bool> Condition) BuildFilterAndCondition(Func expression, IObjectMatcher matcher) { return BuildFilterAndCondition(r => matcher.IsMatch(expression(r)).IsPerfect()); } - private (Func, IReadOnlyList> Filter, Func, bool> Condition) BuildFilterAndCondition(Func expression, IObjectMatcher matcher) + public void FilterRequestMessages(Func, IReadOnlyList> filter) { - return BuildFilterAndCondition(r => matcher.IsMatch(expression(r)).IsPerfect()); + RequestMessages = filter(RequestMessages).ToList(); } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsExtensions.cs b/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsExtensions.cs new file mode 100644 index 000000000..4d890d5b1 --- /dev/null +++ b/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsExtensions.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; +using FluentAssertions; +using FluentAssertions.Execution; + +// ReSharper disable once CheckNamespace +namespace WireMock.FluentAssertions; + +public static class WireMockAssertionsExtensions +{ + [CustomAssertion] + public static AndWhichConstraint AtAbsoluteUrl2(this WireMockAssertions assertions, + string absoluteUrl, string because = "", params object[] becauseArgs) + { + var (filter, condition) = assertions.BuildFilterAndCondition(request => string.Equals(request.AbsoluteUrl, absoluteUrl, StringComparison.OrdinalIgnoreCase)); + + Execute.Assertion + .BecauseOf(because, becauseArgs) + .Given(() => assertions.RequestMessages) + .ForCondition(requests => assertions.CallsCount == 0 || requests.Any()) + .FailWith( + "Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but no calls were made.", + absoluteUrl + ) + .Then + .ForCondition(condition) + .FailWith( + "Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but didn't find it among the calls to {1}.", + _ => absoluteUrl, + requests => requests.Select(request => request.AbsoluteUrl) + ); + + assertions.FilterRequestMessages(filter); + + return new AndWhichConstraint(assertions, absoluteUrl); + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs b/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs index 7961a459c..266ef6883 100644 --- a/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs +++ b/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs @@ -61,6 +61,16 @@ public async Task HaveReceived1Calls_AtAbsoluteUrl_WhenACallWasMadeToAbsoluteUrl .AtAbsoluteUrl($"http://localhost:{_portUsed}/anyurl"); } + [Fact] + public async Task HaveReceived1Calls_AtAbsoluteUrl2_WhenACallWasMadeToAbsoluteUrl_Should_BeOK() + { + await _httpClient.GetAsync("anyurl").ConfigureAwait(false); + + _server.Should() + .HaveReceived(1).Calls() + .AtAbsoluteUrl2($"http://localhost:{_portUsed}/anyurl"); + } + [Fact] public async Task HaveReceived1Calls_AtAbsoluteUrlUsingPost_WhenAPostCallWasMadeToAbsoluteUrl_Should_BeOK() { @@ -129,7 +139,7 @@ public async Task HaveReceivedACall_WithHeader_WhenACallWasMadeWithExpectedHeade _server.Should() .HaveReceivedACall() - .WitHeaderKey("Authorization"); + .WitHeaderKey("Authorization").Which.Should().StartWith("A"); } [Fact]