Compare commits

...

8 Commits

Author SHA1 Message Date
Stef Heyenrath
c135854cbe 1.5.52 2024-04-06 18:14:40 +02:00
Stef Heyenrath
54fe0823dc Add RegEx support to JsonMatcher (#1091)
* json matcher regex

* better test

* regression
2024-04-06 18:08:45 +02:00
Stef Heyenrath
ef9baf3472 Add example for IRequestMessage.BodyAsMimeMessage 2024-04-06 13:59:51 +02:00
Stef Heyenrath
22f9647e88 1.5.51 2024-03-20 08:26:38 +01:00
Stef Heyenrath
d5fa385a46 Fix FluentAssertions (actual body is not displayed in error message) (#1085)
* Fix FluentAssertions (actual body is not displayed in error message)

* .

* .

* raw
2024-03-20 08:24:43 +01:00
Stef Heyenrath
27a673953d 1.5.50 2024-03-12 20:31:50 +01:00
Stef Heyenrath
511540a7f1 Make WireMockAssertions extendable (#1082) 2024-03-12 20:27:12 +01:00
Stef Heyenrath
5b609915e1 Fix FluentAssertions on Header(s) (#1080)
* Fix FluentAssertions on Header(s)

* ...
2024-03-09 08:39:52 +01:00
23 changed files with 1053 additions and 413 deletions

View File

@@ -1,3 +1,17 @@
# 1.5.52 (06 April 2024)
- [#1091](https://github.com/WireMock-Net/WireMock.Net/pull/1091) - Add RegEx support to JsonMatcher [feature] contributed by [StefH](https://github.com/StefH)
- [#1088](https://github.com/WireMock-Net/WireMock.Net/issues/1088) - Regex support for JsonMatcher [feature]
# 1.5.51 (20 March 2024)
- [#1085](https://github.com/WireMock-Net/WireMock.Net/pull/1085) - Fix FluentAssertions (actual body is not displayed in error message) [bug] contributed by [StefH](https://github.com/StefH)
- [#1084](https://github.com/WireMock-Net/WireMock.Net/issues/1084) - FluentAssertions - Actual body is not displayed in error message when using Json Body [bug]
# 1.5.50 (12 March 2024)
- [#1080](https://github.com/WireMock-Net/WireMock.Net/pull/1080) - Fix FluentAssertions on Header(s) [bug] contributed by [StefH](https://github.com/StefH)
- [#1082](https://github.com/WireMock-Net/WireMock.Net/pull/1082) - Make WireMockAssertions extendable [feature] contributed by [StefH](https://github.com/StefH)
- [#1074](https://github.com/WireMock-Net/WireMock.Net/issues/1074) - FluentAssertions extensions do not filter headers correctly [bug]
- [#1075](https://github.com/WireMock-Net/WireMock.Net/issues/1075) - FluentAssertions extensions are not open for extension [feature]
# 1.5.49 (06 March 2024)
- [#1069](https://github.com/WireMock-Net/WireMock.Net/pull/1069) - Extend TypeLoader [feature] contributed by [StefH](https://github.com/StefH)
- [#1078](https://github.com/WireMock-Net/WireMock.Net/pull/1078) - Upgrade ProtoBufJsonConverter to fix issue with dot(s) in package name [bug] contributed by [StefH](https://github.com/StefH)

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>1.5.49</VersionPrefix>
<VersionPrefix>1.5.52</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/WireMock-Net/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>

View File

@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes
SET version=1.5.49
SET version=1.5.52
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels question invalid doc duplicate example --version %version% --token %GH_TOKEN%

View File

@@ -1,6 +1,5 @@
# 1.5.49 (06 March 2024)
- #1069 Extend TypeLoader [feature]
- #1078 Upgrade ProtoBufJsonConverter to fix issue with dot(s) in package name [bug]
- #1077 ProtoBufMatcher not working when proto package name contains dots [bug]
# 1.5.52 (06 April 2024)
- #1091 Add RegEx support to JsonMatcher [feature]
- #1088 Regex support for JsonMatcher [feature]
The full release notes can be found here: https://github.com/WireMock-Net/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -1,6 +1,8 @@
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using FluentAssertions;
using MimeKit;
using WireMock.Logging;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
@@ -18,17 +20,90 @@ internal class Program
Logger = new WireMockConsoleLogger(),
});
server.Given(Request.Create().UsingPost().WithPath("/some/endpoint"))
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.Created));
server
.Given(Request.Create()
.UsingPost()
.WithPath("/test")
)
.RespondWith(Response.Create()
.WithBody(requestMessage => requestMessage.BodyAsMimeMessage != null ?
"BodyAsMimeMessage is present" :
"BodyAsMimeMessage is not present")
);
var httpClient = new HttpClient { BaseAddress = new Uri(server.Url!) };
var requestUri = new Uri(httpClient.BaseAddress!, "some/endpoint");
var content = new StringContent(string.Empty, Encoding.UTF8, "application/json");
server
.Given(Request.Create()
.UsingPost()
.WithPath("/some/endpoint")
)
.RespondWith(Response.Create()
.WithStatusCode(HttpStatusCode.Created)
);
var httpClient = server.CreateClient();
var content = new StringContent("abc", Encoding.UTF8, "application/json");
await TestAsync(httpClient, content);
await TestNoMultiPartAsync(httpClient, content);
await TestMultiPartAsync(server);
}
private static async Task TestNoMultiPartAsync(HttpClient httpClient, StringContent content)
{
var response = await httpClient.PostAsync("/test", content);
response.StatusCode.Should().Be(HttpStatusCode.OK);
(await response.Content.ReadAsStringAsync()).Should().Be("BodyAsMimeMessage is not present");
}
private static async Task TestAsync(HttpClient httpClient, StringContent content)
{
var response = await httpClient.PostAsync("some/endpoint", content);
response.StatusCode.Should().Be(HttpStatusCode.Created);
(await response.Content.ReadAsStringAsync()).Should().BeEmpty();
}
private static async Task TestMultiPartAsync(WireMockServer server)
{
var textPlainContent = "This is some plain text";
var textPlainContentType = "text/plain";
var textJson = "{ \"Key\" : \"Value\" }";
var textJsonContentType = "text/json";
var imagePngBytes = Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC");
server
.Given(
Request.Create()
.UsingPost()
.WithPath("/multipart")
)
.RespondWith(Response.Create()
.WithBody(requestMessage => requestMessage.BodyAsMimeMessage is MimeMessage mm ?
"BodyAsMimeMessage is present: " + ((MimePart)mm.BodyParts.Last()).FileName :
"BodyAsMimeMessage is not present")
);
// Act
var actual = await httpClient.PostAsync(requestUri, content);
var formDataContent = new MultipartFormDataContent
{
{ new StringContent(textPlainContent, Encoding.UTF8, textPlainContentType), "text" },
{ new StringContent(textJson, Encoding.UTF8, textJsonContentType), "json" }
};
// Assert
actual.StatusCode.Should().Be(HttpStatusCode.Created);
var fileContent = new ByteArrayContent(imagePngBytes);
fileContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
formDataContent.Add(fileContent, "somefile", "image.png");
var client = server.CreateClient();
var response = await client.PostAsync("/multipart", formDataContent);
response.StatusCode.Should().Be(HttpStatusCode.OK);
(await response.Content.ReadAsStringAsync()).Should().Be("BodyAsMimeMessage is present: image.png");
}
}

View File

@@ -8,8 +8,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="WireMock.Net" Version="1.5.42" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="WireMock.Net" Version="1.5.51" />
</ItemGroup>
</Project>

View File

@@ -48,7 +48,7 @@ public class MatcherModel
/// </summary>
public string? MatchOperator { get; set; }
#region JsonPartialMatcher and JsonPartialWildcardMatcher
#region JsonMatcher, JsonPartialMatcher and JsonPartialWildcardMatcher
/// <summary>
/// Support Regex.
/// </summary>

View File

@@ -0,0 +1,60 @@
#pragma warning disable CS1591
using System;
// ReSharper disable once CheckNamespace
namespace WireMock.FluentAssertions;
public partial class WireMockAssertions
{
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> 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<WireMockAssertions, string>(this, absoluteUrl);
}
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> 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<WireMockAssertions, string>(this, url);
}
}

View File

@@ -0,0 +1,33 @@
#pragma warning disable CS1591
using System;
// ReSharper disable once CheckNamespace
namespace WireMock.FluentAssertions;
public partial class WireMockAssertions
{
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> 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<WireMockAssertions, string>(this, clientIP);
}
}

View File

@@ -58,8 +58,8 @@ public partial class WireMockAssertions
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 partial class WireMockAssertions
requests => requests.Select(request => request.Method)
);
_requestMessages = filter(_requestMessages).ToList();
FilterRequestMessages(filter);
return new AndConstraint<WireMockAssertions>(this);
}

View File

@@ -1,7 +1,12 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using AnyOfTypes;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WireMock.Extensions;
using WireMock.Matchers;
using WireMock.Models;
// ReSharper disable once CheckNamespace
namespace WireMock.FluentAssertions;
@@ -9,7 +14,7 @@ namespace WireMock.FluentAssertions;
public partial class WireMockAssertions
{
private const string MessageFormatNoCalls = "Expected {context:wiremockserver} to have been called using body {0}{reason}, but no calls were made.";
private const string MessageFormat = "Expected {context:wiremockserver} to have been called using body {0}{reason}, but didn't find it among the body {1}.";
private const string MessageFormat = "Expected {context:wiremockserver} to have been called using body {0}{reason}, but didn't find it among the body/bodies {1}.";
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithBody(string body, string because = "", params object[] becauseArgs)
@@ -56,7 +61,7 @@ public partial class WireMockAssertions
{
var (filter, condition) = BuildFilterAndCondition(r => r.BodyAsBytes, matcher);
return ExecuteAssertionWithBodyAsBytesExactObjectMatcher(matcher, because, becauseArgs, condition, filter, r => r.BodyAsBytes);
return ExecuteAssertionWithBodyAsIObjectMatcher(matcher, because, becauseArgs, condition, filter, r => r.BodyAsBytes);
}
private AndConstraint<WireMockAssertions> ExecuteAssertionWithBodyStringMatcher(
@@ -70,21 +75,21 @@ public partial class WireMockAssertions
{
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()
FormatBody(matcher.GetPatterns())
)
.Then
.ForCondition(condition)
.FailWith(
MessageFormat,
_ => matcher.GetPatterns(),
requests => requests.Select(expression)
_ => FormatBody(matcher.GetPatterns()),
requests => FormatBodies(requests.Select(expression))
);
_requestMessages = filter(_requestMessages).ToList();
FilterRequestMessages(filter);
return new AndConstraint<WireMockAssertions>(this);
}
@@ -100,52 +105,41 @@ public partial class WireMockAssertions
{
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
FormatBody(matcher.Value)
)
.Then
.ForCondition(condition)
.FailWith(
MessageFormat,
_ => matcher.Value,
requests => requests.Select(expression)
_ => FormatBody(matcher.Value),
requests => FormatBodies(requests.Select(expression))
);
_requestMessages = filter(_requestMessages).ToList();
FilterRequestMessages(filter);
return new AndConstraint<WireMockAssertions>(this);
}
private AndConstraint<WireMockAssertions> ExecuteAssertionWithBodyAsBytesExactObjectMatcher(
ExactObjectMatcher matcher,
string because,
object[] becauseArgs,
Func<IReadOnlyList<IRequestMessage>, bool> condition,
Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> filter,
Func<IRequestMessage, object?> expression
)
private static string? FormatBody(object? body)
{
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => _requestMessages)
.ForCondition(requests => _callsCount == 0 || requests.Any())
.FailWith(
MessageFormatNoCalls,
matcher.Value
)
.Then
.ForCondition(condition)
.FailWith(
MessageFormat,
_ => matcher.Value,
requests => requests.Select(expression)
);
return body switch
{
null => null,
string str => str,
AnyOf<string, StringPattern>[] stringPatterns => FormatBodies(stringPatterns.Select(p => p.GetPattern())),
byte[] bytes => $"byte[{bytes.Length}] {{...}}",
JToken jToken => jToken.ToString(Formatting.None),
_ => JToken.FromObject(body).ToString(Formatting.None)
};
}
_requestMessages = filter(_requestMessages).ToList();
return new AndConstraint<WireMockAssertions>(this);
private static string? FormatBodies(IEnumerable<object?> bodies)
{
var valueAsArray = bodies as object[] ?? bodies.ToArray();
return valueAsArray.Length == 1 ? FormatBody(valueAsArray.First()) : $"[ {string.Join(", ", valueAsArray.Select(FormatBody))} ]";
}
}

View File

@@ -0,0 +1,157 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using WireMock.Types;
// ReSharper disable once CheckNamespace
namespace WireMock.FluentAssertions;
public partial class WireMockAssertions
{
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> WitHeaderKey(string expectedKey, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(request =>
{
return request.Headers?.Any(h => h.Key == expectedKey) == true;
});
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => RequestMessages)
.ForCondition(requests => CallsCount == 0 || requests.Any())
.FailWith(
"Expected {context:wiremockserver} to have been called with Header {0}{reason}.",
expectedKey
)
.Then
.ForCondition(condition)
.FailWith(
"Expected {context:wiremockserver} to have been called with Header {0}{reason}, but didn't find it among the calls with Header(s) {1}.",
_ => expectedKey,
requests => requests.Select(request => request.Headers)
);
FilterRequestMessages(filter);
return new AndWhichConstraint<WireMockAssertions, string>(this, expectedKey);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithHeader(string expectedKey, string value, string because = "", params object[] becauseArgs)
=> WithHeader(expectedKey, new[] { value }, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithHeader(string expectedKey, string[] expectedValues, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(request =>
{
var headers = request.Headers?.ToArray() ?? new KeyValuePair<string, WireMockList<string>>[0];
var matchingHeaderValues = headers.Where(h => h.Key == expectedKey).SelectMany(h => h.Value.ToArray()).ToArray();
if (expectedValues.Length == 1 && matchingHeaderValues.Length == 1)
{
return matchingHeaderValues[0] == expectedValues[0];
}
var trimmedHeaderValues = string.Join(",", matchingHeaderValues.Select(x => x)).Split(',').Select(x => x.Trim()).ToArray();
return expectedValues.Any(trimmedHeaderValues.Contains);
});
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => RequestMessages)
.ForCondition(requests => CallsCount == 0 || requests.Any())
.FailWith(
"Expected {context:wiremockserver} to have been called with Header {0} and Values {1}{reason}.",
expectedKey,
expectedValues
)
.Then
.ForCondition(condition)
.FailWith(
"Expected {context:wiremockserver} to have been called with Header {0} and Values {1}{reason}, but didn't find it among the calls with Header(s) {2}.",
_ => expectedKey,
_ => expectedValues,
requests => requests.Select(request => request.Headers)
);
FilterRequestMessages(filter);
return new AndConstraint<WireMockAssertions>(this);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithoutHeaderKey(string unexpectedKey, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(request =>
{
return request.Headers?.Any(h => h.Key == unexpectedKey) != true;
});
Execute.Assertion
.BecauseOf(because, becauseArgs)
.Given(() => RequestMessages)
.ForCondition(requests => CallsCount == 0 || requests.Any())
.FailWith(
"Expected {context:wiremockserver} not to have been called with Header {0}{reason}.",
unexpectedKey
)
.Then
.ForCondition(condition)
.FailWith(
"Expected {context:wiremockserver} not to have been called with Header {0}{reason}, but found it among the calls with Header(s) {1}.",
_ => unexpectedKey,
requests => requests.Select(request => request.Headers)
);
FilterRequestMessages(filter);
return new AndConstraint<WireMockAssertions>(this);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithoutHeader(string unexpectedKey, string value, string because = "", params object[] becauseArgs)
=> WithoutHeader(unexpectedKey, new[] { value }, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithoutHeader(string unexpectedKey, string[] expectedValues, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(request =>
{
var headers = request.Headers?.ToArray() ?? new KeyValuePair<string, WireMockList<string>>[0];
var matchingHeaderValues = headers.Where(h => h.Key == unexpectedKey).SelectMany(h => h.Value.ToArray()).ToArray();
if (expectedValues.Length == 1 && matchingHeaderValues.Length == 1)
{
return matchingHeaderValues[0] != expectedValues[0];
}
var trimmedHeaderValues = string.Join(",", matchingHeaderValues.Select(x => x)).Split(',').Select(x => x.Trim()).ToArray();
return !expectedValues.Any(trimmedHeaderValues.Contains);
});
Execute.Assertion
.BecauseOf(because, becauseArgs)
.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,
expectedValues
)
.Then
.ForCondition(condition)
.FailWith(
"Expected {context:wiremockserver} not to have been called with Header {0} and Values {1}{reason}, but found it among the calls with Header(s) {2}.",
_ => unexpectedKey,
_ => expectedValues,
requests => requests.Select(request => request.Headers)
);
FilterRequestMessages(filter);
return new AndConstraint<WireMockAssertions>(this);
}
}

View File

@@ -0,0 +1,34 @@
#pragma warning disable CS1591
using System;
// ReSharper disable once CheckNamespace
namespace WireMock.FluentAssertions;
public partial class WireMockAssertions
{
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> 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<WireMockAssertions, string>(this, proxyUrl);
}
}

View File

@@ -3,220 +3,42 @@ using System;
using System.Collections.Generic;
using WireMock.Matchers;
using WireMock.Server;
using WireMock.Types;
// ReSharper disable once CheckNamespace
namespace WireMock.FluentAssertions;
public partial class WireMockAssertions
{
private const string Any = "*";
private readonly int? _callsCount;
private IReadOnlyList<IRequestMessage> _requestMessages;
private readonly IReadOnlyList<KeyValuePair<string, WireMockList<string>>> _headers;
public const string Any = "*";
public int? CallsCount { get; }
public IReadOnlyList<IRequestMessage> RequestMessages { get; private set; }
public WireMockAssertions(IWireMockServer subject, int? callsCount)
{
_callsCount = callsCount;
_requestMessages = subject.LogEntries.Select(logEntry => logEntry.RequestMessage).ToList();
_headers = _requestMessages.SelectMany(req => req.Headers).ToList();
CallsCount = callsCount;
RequestMessages = subject.LogEntries.Select(logEntry => logEntry.RequestMessage).ToList();
}
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> 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<WireMockAssertions, string>(this, absoluteUrl);
}
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> 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();
return new AndWhichConstraint<WireMockAssertions, string>(this, url);
}
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> 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)
);
_requestMessages = filter(_requestMessages).ToList();
return new AndWhichConstraint<WireMockAssertions, string>(this, proxyUrl);
}
[CustomAssertion]
public AndWhichConstraint<WireMockAssertions, string> 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)
);
_requestMessages = filter(_requestMessages).ToList();
return new AndWhichConstraint<WireMockAssertions, string>(this, clientIP);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WitHeaderKey(string expectedKey, string because = "", params object[] becauseArgs)
{
using (new AssertionScope("headers from requests sent"))
{
_headers.Select(h => h.Key).Should().Contain(expectedKey, because, becauseArgs);
}
return new AndConstraint<WireMockAssertions>(this);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithHeader(string expectedKey, string value, string because = "", params object[] becauseArgs)
=> WithHeader(expectedKey, new[] { value }, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithHeader(string expectedKey, string[] expectedValues, string because = "", params object[] becauseArgs)
{
using (new AssertionScope($"header \"{expectedKey}\" from requests sent with value(s)"))
{
var matchingHeaderValues = _headers.Where(h => h.Key == expectedKey).SelectMany(h => h.Value.ToArray())
.ToArray();
if (expectedValues.Length == 1)
{
matchingHeaderValues.Should().Contain(expectedValues.First(), because, becauseArgs);
}
else
{
var trimmedHeaderValues = string.Join(",", matchingHeaderValues.Select(x => x)).Split(',').Select(x => x.Trim()).ToList();
foreach (var expectedValue in expectedValues)
{
trimmedHeaderValues.Should().Contain(expectedValue, because, becauseArgs);
}
}
}
return new AndConstraint<WireMockAssertions>(this);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithoutHeaderKey(string unexpectedKey, string because = "", params object[] becauseArgs)
{
using (new AssertionScope("headers from requests sent"))
{
_headers.Select(h => h.Key).Should().NotContain(unexpectedKey, because, becauseArgs);
}
return new AndConstraint<WireMockAssertions>(this);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithoutHeader(string unexpectedKey, string value, string because = "", params object[] becauseArgs)
=> WithoutHeader(unexpectedKey, new[] { value }, because, becauseArgs);
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithoutHeader(string unexpectedKey, string[] expectedValues, string because = "", params object[] becauseArgs)
{
using (new AssertionScope($"header \"{unexpectedKey}\" from requests sent with value(s)"))
{
var matchingHeaderValues = _headers.Where(h => h.Key == unexpectedKey).SelectMany(h => h.Value.ToArray()).ToArray();
if (expectedValues.Length == 1)
{
matchingHeaderValues.Should().NotContain(expectedValues.First(), because, becauseArgs);
}
else
{
var trimmedHeaderValues = string.Join(",", matchingHeaderValues.Select(x => x)).Split(',').Select(x => x.Trim()).ToList();
foreach (var expectedValue in expectedValues)
{
trimmedHeaderValues.Should().NotContain(expectedValue, because, becauseArgs);
}
}
}
return new AndConstraint<WireMockAssertions>(this);
}
private (Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> Filter, Func<IReadOnlyList<IRequestMessage>, bool> Condition) BuildFilterAndCondition(Func<IRequestMessage, bool> predicate)
public (Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> Filter, Func<IReadOnlyList<IRequestMessage>, bool> Condition) BuildFilterAndCondition(Func<IRequestMessage, bool> predicate)
{
Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> filter = requests => requests.Where(predicate).ToList();
return (filter, requests => (_callsCount is null && filter(requests).Any()) || _callsCount == filter(requests).Count);
return (filter, requests => (CallsCount is null && filter(requests).Any()) || CallsCount == filter(requests).Count);
}
private (Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> Filter, Func<IReadOnlyList<IRequestMessage>, bool> Condition) BuildFilterAndCondition(Func<IRequestMessage, string?> expression, IStringMatcher matcher)
public (Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> Filter, Func<IReadOnlyList<IRequestMessage>, bool> Condition) BuildFilterAndCondition(Func<IRequestMessage, string?> expression, IStringMatcher matcher)
{
return BuildFilterAndCondition(r => matcher.IsMatch(expression(r)).IsPerfect());
}
private (Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> Filter, Func<IReadOnlyList<IRequestMessage>, bool> Condition) BuildFilterAndCondition(Func<IRequestMessage, object?> expression, IObjectMatcher matcher)
public (Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> Filter, Func<IReadOnlyList<IRequestMessage>, bool> Condition) BuildFilterAndCondition(Func<IRequestMessage, object?> expression, IObjectMatcher matcher)
{
return BuildFilterAndCondition(r => matcher.IsMatch(expression(r)).IsPerfect());
}
public void FilterRequestMessages(Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> filter)
{
RequestMessages = filter(RequestMessages).ToList();
}
}

View File

@@ -5,18 +5,36 @@ using WireMock.Models;
namespace WireMock.Extensions;
internal static class AnyOfExtensions
/// <summary>
/// Some extensions for AnyOf.
/// </summary>
public static class AnyOfExtensions
{
/// <summary>
/// Gets the pattern.
/// </summary>
/// <param name="value">AnyOf type</param>
/// <returns>string value</returns>
public static string GetPattern(this AnyOf<string, StringPattern> value)
{
return value.IsFirst ? value.First : value.Second.Pattern;
}
/// <summary>
/// Converts a string-patterns to AnyOf patterns.
/// </summary>
/// <param name="patterns">The string patterns</param>
/// <returns>The AnyOf patterns</returns>
public static AnyOf<string, StringPattern>[] ToAnyOfPatterns(this IEnumerable<string> patterns)
{
return patterns.Select(p => p.ToAnyOfPattern()).ToArray();
}
/// <summary>
/// Converts a string-pattern to AnyOf pattern.
/// </summary>
/// <param name="pattern">The string pattern</param>
/// <returns>The AnyOf pattern</returns>
public static AnyOf<string, StringPattern> ToAnyOfPattern(this string pattern)
{
return new AnyOf<string, StringPattern>(pattern);

View File

@@ -10,20 +10,15 @@ namespace WireMock.Matchers;
/// </summary>
public abstract class AbstractJsonPartialMatcher : JsonMatcher
{
/// <summary>
/// Support Regex
/// </summary>
public bool Regex { get; }
/// <summary>
/// Initializes a new instance of the <see cref="AbstractJsonPartialMatcher"/> class.
/// </summary>
/// <param name="value">The string value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="regex">Support Regex.</param>
protected AbstractJsonPartialMatcher(string value, bool ignoreCase = false, bool regex = false) : base(value, ignoreCase)
protected AbstractJsonPartialMatcher(string value, bool ignoreCase = false, bool regex = false) :
base(value, ignoreCase, regex)
{
Regex = regex;
}
/// <summary>
@@ -32,9 +27,9 @@ public abstract class AbstractJsonPartialMatcher : JsonMatcher
/// <param name="value">The object value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="regex">Support Regex.</param>
protected AbstractJsonPartialMatcher(object value, bool ignoreCase = false, bool regex = false) : base(value, ignoreCase)
protected AbstractJsonPartialMatcher(object value, bool ignoreCase = false, bool regex = false) :
base(value, ignoreCase, regex)
{
Regex = regex;
}
/// <summary>
@@ -44,15 +39,15 @@ public abstract class AbstractJsonPartialMatcher : JsonMatcher
/// <param name="value">The value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="regex">Support Regex.</param>
protected AbstractJsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false) : base(matchBehaviour, value, ignoreCase)
protected AbstractJsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false) :
base(matchBehaviour, value, ignoreCase, regex)
{
Regex = regex;
}
/// <inheritdoc />
protected override bool IsMatch(JToken? value, JToken? input)
protected override bool IsMatch(JToken value, JToken? input)
{
if (value == null || value == input)
if (value == input)
{
return true;
}
@@ -72,7 +67,7 @@ public abstract class AbstractJsonPartialMatcher : JsonMatcher
((value.Type == JTokenType.Guid && input.Type == JTokenType.String) ||
(value.Type == JTokenType.String && input.Type == JTokenType.Guid)))
{
return IsMatch(value.ToString(), input.ToString());
return IsMatch(value.ToString().ToUpperInvariant(), input.ToString().ToUpperInvariant());
}
if (input == null || value.Type != input.Type)

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Stef.Validation;
using WireMock.Util;
using JsonUtils = WireMock.Util.JsonUtils;
namespace WireMock.Matchers;
@@ -23,6 +25,11 @@ public class JsonMatcher : IJsonMatcher
/// <inheritdoc cref="IIgnoreCaseMatcher.IgnoreCase"/>
public bool IgnoreCase { get; }
/// <summary>
/// Support Regex
/// </summary>
public bool Regex { get; }
private readonly JToken _valueAsJToken;
private readonly Func<JToken, JToken> _jTokenConverter;
@@ -31,7 +38,8 @@ public class JsonMatcher : IJsonMatcher
/// </summary>
/// <param name="value">The string value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
public JsonMatcher(string value, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase)
/// <param name="regex">Support Regex.</param>
public JsonMatcher(string value, bool ignoreCase = false, bool regex = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex)
{
}
@@ -40,7 +48,8 @@ public class JsonMatcher : IJsonMatcher
/// </summary>
/// <param name="value">The object value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
public JsonMatcher(object value, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase)
/// <param name="regex">Support Regex.</param>
public JsonMatcher(object value, bool ignoreCase = false, bool regex = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex)
{
}
@@ -50,12 +59,14 @@ public class JsonMatcher : IJsonMatcher
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="value">The value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false)
/// <param name="regex">Support Regex.</param>
public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false)
{
Guard.NotNull(value);
MatchBehaviour = matchBehaviour;
IgnoreCase = ignoreCase;
Regex = regex;
Value = value;
_valueAsJToken = JsonUtils.ConvertValueToJToken(value);
@@ -93,9 +104,79 @@ public class JsonMatcher : IJsonMatcher
/// <param name="value">Matcher value</param>
/// <param name="input">Input value</param>
/// <returns></returns>
protected virtual bool IsMatch(JToken value, JToken input)
protected virtual bool IsMatch(JToken value, JToken? input)
{
return JToken.DeepEquals(value, input);
// If equal, return true.
if (input == value)
{
return true;
}
// If input is null, return false.
if (input == null)
{
return false;
}
// If using Regex and the value is a string, use the MatchRegex method.
if (Regex && value.Type == JTokenType.String)
{
var valueAsString = value.ToString();
var (valid, result) = RegexUtils.MatchRegex(valueAsString, input.ToString());
if (valid)
{
return result;
}
}
// If the value is a Guid and the input is a string, or vice versa, convert them to strings and compare the string values.
if ((value.Type == JTokenType.Guid && input.Type == JTokenType.String) || (value.Type == JTokenType.String && input.Type == JTokenType.Guid))
{
return JToken.DeepEquals(value.ToString().ToUpperInvariant(), input.ToString().ToUpperInvariant());
}
switch (value.Type)
{
// If the value is an object, compare all properties.
case JTokenType.Object:
var valueProperties = value.ToObject<Dictionary<string, JToken>>() ?? new Dictionary<string, JToken>();
var inputProperties = input.ToObject<Dictionary<string, JToken>>() ?? new Dictionary<string, JToken>();
// If the number of properties is different, return false.
if (valueProperties.Count != inputProperties.Count)
{
return false;
}
// Compare all properties. The input must match all properties of the value.
foreach (var pair in valueProperties)
{
if (!IsMatch(pair.Value, inputProperties[pair.Key]))
{
return false;
}
}
return true;
// If the value is an array, compare all elements.
case JTokenType.Array:
var valueArray = value.ToObject<JToken[]>() ?? EmptyArray<JToken>.Value;
var inputArray = input.ToObject<JToken[]>() ?? EmptyArray<JToken>.Value;
// If the number of elements is different, return false.
if (valueArray.Length != inputArray.Length)
{
return false;
}
return !valueArray.Where((valueToken, index) => !IsMatch(valueToken, inputArray[index])).Any();
default:
// Use JToken.DeepEquals() for all other types.
return JToken.DeepEquals(value, input);
}
}
private static string? ToUpper(string? input)

View File

@@ -84,7 +84,7 @@ internal class MatcherMapper
case nameof(JsonMatcher):
var valueForJsonMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
return new JsonMatcher(matchBehaviour, valueForJsonMatcher!, ignoreCase);
return new JsonMatcher(matchBehaviour, valueForJsonMatcher!, ignoreCase, useRegex);
case nameof(JsonPartialMatcher):
var valueForJsonPartialMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
@@ -152,12 +152,8 @@ internal class MatcherMapper
switch (matcher)
{
case JsonPartialMatcher jsonPartialMatcher:
model.Regex = jsonPartialMatcher.Regex;
break;
case JsonPartialWildcardMatcher jsonPartialWildcardMatcher:
model.Regex = jsonPartialWildcardMatcher.Regex;
case JsonMatcher jsonMatcher:
model.Regex = jsonMatcher.Regex;
break;
case XPathMatcher xpathMatcher:

View File

@@ -0,0 +1,37 @@
using System;
using System.Linq;
using FluentAssertions;
using FluentAssertions.Execution;
using WireMock.FluentAssertions;
namespace WireMock.Net.Tests.FluentAssertions;
public static class WireMockAssertionsExtensions
{
[CustomAssertion]
public static AndWhichConstraint<WireMockAssertions, string> 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<WireMockAssertions, string>(assertions, absoluteUrl);
}
}

View File

@@ -12,7 +12,6 @@ using WireMock.ResponseBuilders;
using WireMock.Server;
using WireMock.Settings;
using Xunit;
using static System.Environment;
namespace WireMock.Net.Tests.FluentAssertions;
@@ -62,6 +61,16 @@ public class WireMockAssertionsTests : IDisposable
.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()
{
@@ -103,10 +112,9 @@ public class WireMockAssertionsTests : IDisposable
.HaveReceivedACall()
.AtAbsoluteUrl("anyurl");
act.Should().Throw<Exception>()
.And.Message.Should()
.Be(
"Expected _server to have been called at address matching the absolute url \"anyurl\", but no calls were made.");
act.Should()
.Throw<Exception>()
.WithMessage("Expected _server to have been called at address matching the absolute url \"anyurl\", but no calls were made.");
}
[Fact]
@@ -118,10 +126,9 @@ public class WireMockAssertionsTests : IDisposable
.HaveReceivedACall()
.AtAbsoluteUrl("anyurl");
act.Should().Throw<Exception>()
.And.Message.Should()
.Be(
$"Expected _server to have been called at address matching the absolute url \"anyurl\", but didn't find it among the calls to {{\"http://localhost:{_portUsed}/\"}}.");
act.Should()
.Throw<Exception>()
.WithMessage($"Expected _server to have been called at address matching the absolute url \"anyurl\", but didn't find it among the calls to {{\"http://localhost:{_portUsed}/\"}}.");
}
[Fact]
@@ -132,7 +139,7 @@ public class WireMockAssertionsTests : IDisposable
_server.Should()
.HaveReceivedACall()
.WitHeaderKey("Authorization");
.WitHeaderKey("Authorization").Which.Should().StartWith("A");
}
[Fact]
@@ -172,9 +179,9 @@ public class WireMockAssertionsTests : IDisposable
.HaveReceivedACall()
.WithHeader("Authorization", "value");
act.Should().Throw<Exception>()
.And.Message.Should()
.Contain("\"Authorization\"");
act.Should()
.Throw<Exception>()
.WithMessage("*\"Authorization\"*");
}
[Fact]
@@ -188,38 +195,27 @@ public class WireMockAssertionsTests : IDisposable
.HaveReceivedACall()
.WithHeader("Accept", "missing-value");
var sentHeaders = _server.LogEntries.SelectMany(x => x.RequestMessage.Headers)
.ToDictionary(x => x.Key, x => x.Value)["Accept"]
.Select(x => $"\"{x}\"")
.ToList();
var sentHeaderString = "{" + string.Join(", ", sentHeaders) + "}";
act.Should().Throw<Exception>()
.And.Message.Should()
.Be(
$"Expected header \"Accept\" from requests sent with value(s) {sentHeaderString} to contain \"missing-value\".{NewLine}");
act.Should()
.Throw<Exception>()
.WithMessage("Expected _server to have been called with Header \"Accept\" and Values {\"missing-value\"}, but didn't find it among the calls with Header(s) {{[\"Accept\"] = {\"application/xml, application/json\"}, [\"Host\"] = {\"localhost:*\"}}}.");
}
[Fact]
public async Task HaveReceivedACall_WithHeader_Should_ThrowWhenNoCallsMatchingTheHeaderWithMultipleValuesWereMade()
{
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
await _httpClient.GetAsync("").ConfigureAwait(false);
using var httpClient = new HttpClient { BaseAddress = new Uri(_server.Url!) };
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
await httpClient.GetAsync("").ConfigureAwait(false);
Action act = () => _server.Should()
.HaveReceivedACall()
.WithHeader("Accept", new[] { "missing-value1", "missing-value2" });
const string missingValue1Message =
"Expected header \"Accept\" from requests sent with value(s) {\"application/xml\", \"application/json\"} to contain \"missing-value1\".";
const string missingValue2Message =
"Expected header \"Accept\" from requests sent with value(s) {\"application/xml\", \"application/json\"} to contain \"missing-value2\".";
act.Should().Throw<Exception>()
.And.Message.Should()
.Be($"{string.Join(NewLine, missingValue1Message, missingValue2Message)}{NewLine}");
act.Should()
.Throw<Exception>()
.WithMessage("Expected _server to have been called with Header \"Accept\" and Values {\"missing-value1\", \"missing-value2\"}, but didn't find it among the calls with Header(s) {{[\"Accept\"] = {\"application/xml, application/json\"}, [\"Host\"] = {\"localhost:*\"}}}.");
}
[Fact]
@@ -277,10 +273,9 @@ public class WireMockAssertionsTests : IDisposable
.HaveReceivedACall()
.AtUrl("anyurl");
act.Should().Throw<Exception>()
.And.Message.Should()
.Be(
"Expected _server to have been called at address matching the url \"anyurl\", but no calls were made.");
act.Should()
.Throw<Exception>()
.WithMessage("Expected _server to have been called at address matching the url \"anyurl\", but no calls were made.");
}
[Fact]
@@ -292,10 +287,9 @@ public class WireMockAssertionsTests : IDisposable
.HaveReceivedACall()
.AtUrl("anyurl");
act.Should().Throw<Exception>()
.And.Message.Should()
.Be(
$"Expected _server to have been called at address matching the url \"anyurl\", but didn't find it among the calls to {{\"http://localhost:{_portUsed}/\"}}.");
act.Should()
.Throw<Exception>()
.WithMessage($"Expected _server to have been called at address matching the url \"anyurl\", but didn't find it among the calls to {{\"http://localhost:{_portUsed}/\"}}.");
}
[Fact]
@@ -309,7 +303,7 @@ public class WireMockAssertionsTests : IDisposable
_server.Should()
.HaveReceivedACall()
.WithProxyUrl($"http://localhost:9999");
.WithProxyUrl("http://localhost:9999");
}
[Fact]
@@ -323,10 +317,9 @@ public class WireMockAssertionsTests : IDisposable
.HaveReceivedACall()
.WithProxyUrl("anyurl");
act.Should().Throw<Exception>()
.And.Message.Should()
.Be(
"Expected _server to have been called with proxy url \"anyurl\", but no calls were made.");
act.Should()
.Throw<Exception>()
.WithMessage("Expected _server to have been called with proxy url \"anyurl\", but no calls were made.");
}
[Fact]
@@ -342,10 +335,9 @@ public class WireMockAssertionsTests : IDisposable
.HaveReceivedACall()
.WithProxyUrl("anyurl");
act.Should().Throw<Exception>()
.And.Message.Should()
.Be(
$"Expected _server to have been called with proxy url \"anyurl\", but didn't find it among the calls with {{\"http://localhost:9999\"}}.");
act.Should()
.Throw<Exception>()
.WithMessage("Expected _server to have been called with proxy url \"anyurl\", but didn't find it among the calls with {\"http://localhost:9999\"}.");
}
[Fact]
@@ -366,10 +358,9 @@ public class WireMockAssertionsTests : IDisposable
.HaveReceivedACall()
.FromClientIP("different-ip");
act.Should().Throw<Exception>()
.And.Message.Should()
.Be(
"Expected _server to have been called from client IP \"different-ip\", but no calls were made.");
act.Should()
.Throw<Exception>()
.WithMessage("Expected _server to have been called from client IP \"different-ip\", but no calls were made.");
}
[Fact]
@@ -382,10 +373,9 @@ public class WireMockAssertionsTests : IDisposable
.HaveReceivedACall()
.FromClientIP("different-ip");
act.Should().Throw<Exception>()
.And.Message.Should()
.Be(
$"Expected _server to have been called from client IP \"different-ip\", but didn't find it among the calls from IP(s) {{\"{clientIP}\"}}.");
act.Should()
.Throw<Exception>()
.WithMessage($"Expected _server to have been called from client IP \"different-ip\", but didn't find it among the calls from IP(s) {{\"{clientIP}\"}}.");
}
[Fact]
@@ -419,10 +409,9 @@ public class WireMockAssertionsTests : IDisposable
.HaveReceivedACall()
.UsingPatch();
act.Should().Throw<Exception>()
.And.Message.Should()
.Be(
"Expected _server to have been called using method \"PATCH\", but no calls were made.");
act.Should()
.Throw<Exception>()
.WithMessage("Expected _server to have been called using method \"PATCH\", but no calls were made.");
}
[Fact]
@@ -434,10 +423,9 @@ public class WireMockAssertionsTests : IDisposable
.HaveReceivedACall()
.UsingOptions();
act.Should().Throw<Exception>()
.And.Message.Should()
.Be(
"Expected _server to have been called using method \"OPTIONS\", but didn't find it among the methods {\"POST\"}.");
act.Should()
.Throw<Exception>()
.WithMessage("Expected _server to have been called using method \"OPTIONS\", but didn't find it among the methods {\"POST\"}.");
}
#if !NET452
@@ -714,7 +702,11 @@ public class WireMockAssertionsTests : IDisposable
// Act
var httpClient = new HttpClient();
await httpClient.PostAsync($"{server.Url}/a", new StringContent(@"{ ""x"": ""y"" }"));
var requestBody = new
{
x = "y"
};
await httpClient.PostAsJsonAsync($"{server.Url}/a", requestBody);
// Assert
server
@@ -752,6 +744,103 @@ public class WireMockAssertionsTests : IDisposable
server.Stop();
}
[Fact]
public async Task WithBodyAsJson_When_NoMatch_ShouldHaveCorrectErrorMessage()
{
// Arrange
var server = WireMockServer.Start();
server
.Given(Request.Create().WithPath("/a").UsingPost())
.RespondWith(Response.Create().WithBody("A response"));
// Act
var httpClient = new HttpClient();
var requestBody = new
{
x = "123"
};
await httpClient.PostAsJsonAsync($"{server.Url}/a", requestBody);
// Assert
Action act = () => server
.Should()
.HaveReceived(1)
.Calls()
.WithBodyAsJson(new { x = "y" })
.And
.UsingPost();
act.Should()
.Throw<Exception>()
.WithMessage("""Expected wiremockserver to have been called using body "{"x":"y"}", but didn't find it among the body/bodies "{"x":"123"}".""");
server.Stop();
}
[Fact]
public async Task WithBodyAsString_When_NoMatch_ShouldHaveCorrectErrorMessage()
{
// Arrange
var server = WireMockServer.Start();
server
.Given(Request.Create().WithPath("/a").UsingPost())
.RespondWith(Response.Create().WithBody("A response"));
// Act
var httpClient = new HttpClient();
await httpClient.PostAsync($"{server.Url}/a", new StringContent("123"));
// Assert
Action act = () => server
.Should()
.HaveReceived(1)
.Calls()
.WithBody("abc")
.And
.UsingPost();
act.Should()
.Throw<Exception>()
.WithMessage("""Expected wiremockserver to have been called using body "abc", but didn't find it among the body/bodies "123".""");
server.Stop();
}
[Fact]
public async Task WithBodyAsBytes_When_NoMatch_ShouldHaveCorrectErrorMessage()
{
// Arrange
var server = WireMockServer.Start();
server
.Given(Request.Create().WithPath("/a").UsingPost())
.RespondWith(Response.Create().WithBody("A response"));
// Act
var httpClient = new HttpClient();
await httpClient.PostAsync($"{server.Url}/a", new ByteArrayContent(new byte[] { 5 }));
// Assert
Action act = () => server
.Should()
.HaveReceived(1)
.Calls()
.WithBodyAsBytes(new byte[] { 1 })
.And
.UsingPost();
act.Should()
.Throw<Exception>()
.WithMessage("""Expected wiremockserver to have been called using body "byte[1] {...}", but didn't find it among the body/bodies "byte[1] {...}".""");
server.Stop();
}
[Fact]
public async Task HaveReceived1Call_WithBodyAsBytes()
{
@@ -838,6 +927,56 @@ public class WireMockAssertionsTests : IDisposable
server.Stop();
}
[Fact]
public async Task HaveReceivedACall_WithHeader_Should_ThrowWhenHttpMethodDoesNotMatch()
{
// Arrange
using var server = WireMockServer.Start();
// Act : HTTP GET
using var httpClient = new HttpClient();
await httpClient.GetAsync(server.Url!);
// Act : HTTP POST
var request = new HttpRequestMessage(HttpMethod.Post, server.Url!);
request.Headers.Add("TestHeader", new[] { "Value", "Value2" });
await httpClient.SendAsync(request);
// Assert
server.Should().HaveReceivedACall().UsingPost().And.WithHeader("TestHeader", new[] { "Value", "Value2" });
Action act = () => server.Should().HaveReceivedACall().UsingGet().And.WithHeader("TestHeader", "Value");
act.Should()
.Throw<Exception>()
.WithMessage("Expected server to have been called with Header \"TestHeader\" and Values {\"Value\"}, but didn't find it among the calls with Header(s) {{[\"Host\"] = {\"localhost:*\"}}}.");
}
[Fact]
public async Task HaveReceivedACall_WithHeaderKey_Should_ThrowWhenHttpMethodDoesNotMatch()
{
// Arrange
using var server = WireMockServer.Start();
// Act : HTTP GET
using var httpClient = new HttpClient();
await httpClient.GetAsync(server.Url!);
// Act : HTTP POST
var request = new HttpRequestMessage(HttpMethod.Post, server.Url!);
request.Headers.Add("TestHeader", new[] { "Value", "Value2" });
await httpClient.SendAsync(request);
// Assert
server.Should().HaveReceivedACall().UsingPost().And.WitHeaderKey("TestHeader");
Action act = () => server.Should().HaveReceivedACall().UsingGet().And.WitHeaderKey("TestHeader");
act.Should()
.Throw<Exception>()
.WithMessage("Expected server to have been called with Header \"TestHeader\", but didn't find it among the calls with Header(s) {{[\"Host\"] = {\"localhost:*\"}}}.");
}
public void Dispose()
{
_server?.Stop();

View File

@@ -40,7 +40,7 @@ public class JsonMatcherTests
var matcher = new JsonMatcher("{}");
// Act
string name = matcher.Name;
var name = matcher.Name;
// Assert
Check.That(name).Equals("JsonMatcher");
@@ -53,7 +53,7 @@ public class JsonMatcherTests
var matcher = new JsonMatcher("{}");
// Act
object value = matcher.Value;
var value = matcher.Value;
// Assert
Check.That(value).Equals("{}");
@@ -90,7 +90,7 @@ public class JsonMatcherTests
// Act
var result = matcher.IsMatch(new MemoryStream());
// Assert
// Assert
result.Score.Should().Be(MatchScores.Mismatch);
result.Exception.Should().BeAssignableTo<JsonException>();
}
@@ -102,10 +102,10 @@ public class JsonMatcherTests
var bytes = EmptyArray<byte>.Value;
var matcher = new JsonMatcher("");
// Act
double match = matcher.IsMatch(bytes).Score;
// Act
var match = matcher.IsMatch(bytes).Score;
// Assert
// Assert
Check.That(match).IsEqualTo(0);
}
@@ -116,10 +116,10 @@ public class JsonMatcherTests
string? s = null;
var matcher = new JsonMatcher("");
// Act
double match = matcher.IsMatch(s).Score;
// Act
var match = matcher.IsMatch(s).Score;
// Assert
// Assert
Check.That(match).IsEqualTo(0);
}
@@ -130,215 +130,399 @@ public class JsonMatcherTests
object? o = null;
var matcher = new JsonMatcher("");
// Act
double match = matcher.IsMatch(o).Score;
// Act
var match = matcher.IsMatch(o).Score;
// Assert
// Assert
Check.That(match).IsEqualTo(0);
}
[Fact]
public void JsonMatcher_IsMatch_JArray()
{
// Assign
// Assign
var matcher = new JsonMatcher(new[] { "x", "y" });
// Act
// Act
var jArray = new JArray
{
"x",
"y"
};
double match = matcher.IsMatch(jArray).Score;
var match = matcher.IsMatch(jArray).Score;
// Assert
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonMatcher_IsMatch_JObject()
public void JsonMatcher_IsMatch_JObject_ShouldMatch()
{
// Assign
// Assign
var matcher = new JsonMatcher(new { Id = 1, Name = "Test" });
// Act
// Act
var jObject = new JObject
{
{ "Id", new JValue(1) },
{ "Name", new JValue("Test") }
};
double match = matcher.IsMatch(jObject).Score;
var match = matcher.IsMatch(jObject).Score;
// Assert
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonMatcher_IsMatch_JObject_ShouldNotMatch()
{
// Assign
var matcher = new JsonMatcher(new { Id = 1, Name = "Test" });
// Act
var jObject = new JObject
{
{ "Id", new JValue(1) },
{ "Name", new JValue("Test") },
{ "Other", new JValue("abc") }
};
var score = matcher.IsMatch(jObject).Score;
// Assert
Assert.Equal(MatchScores.Mismatch, score);
}
[Fact]
public void JsonMatcher_IsMatch_WithIgnoreCaseTrue_JObject()
{
// Assign
// Assign
var matcher = new JsonMatcher(new { id = 1, Name = "test" }, true);
// Act
// Act
var jObject = new JObject
{
{ "Id", new JValue(1) },
{ "NaMe", new JValue("Test") }
};
double match = matcher.IsMatch(jObject).Score;
var match = matcher.IsMatch(jObject).Score;
// Assert
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonMatcher_IsMatch_JObjectParsed()
{
// Assign
// Assign
var matcher = new JsonMatcher(new { Id = 1, Name = "Test" });
// Act
// Act
var jObject = JObject.Parse("{ \"Id\" : 1, \"Name\" : \"Test\" }");
double match = matcher.IsMatch(jObject).Score;
var match = matcher.IsMatch(jObject).Score;
// Assert
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonMatcher_IsMatch_WithIgnoreCaseTrue_JObjectParsed()
{
// Assign
// Assign
var matcher = new JsonMatcher(new { Id = 1, Name = "TESt" }, true);
// Act
// Act
var jObject = JObject.Parse("{ \"Id\" : 1, \"Name\" : \"Test\" }");
double match = matcher.IsMatch(jObject).Score;
var match = matcher.IsMatch(jObject).Score;
// Assert
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonMatcher_IsMatch_JArrayAsString()
{
// Assign
// Assign
var matcher = new JsonMatcher("[ \"x\", \"y\" ]");
// Act
// Act
var jArray = new JArray
{
"x",
"y"
};
double match = matcher.IsMatch(jArray).Score;
var match = matcher.IsMatch(jArray).Score;
// Assert
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonMatcher_IsMatch_JObjectAsString()
{
// Assign
// Assign
var matcher = new JsonMatcher("{ \"Id\" : 1, \"Name\" : \"Test\" }");
// Act
// Act
var jObject = new JObject
{
{ "Id", new JValue(1) },
{ "Name", new JValue("Test") }
};
double match = matcher.IsMatch(jObject).Score;
var match = matcher.IsMatch(jObject).Score;
// Assert
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonMatcher_IsMatch_WithIgnoreCaseTrue_JObjectAsString()
{
// Assign
// Assign
var matcher = new JsonMatcher("{ \"Id\" : 1, \"Name\" : \"test\" }", true);
// Act
// Act
var jObject = new JObject
{
{ "Id", new JValue(1) },
{ "Name", new JValue("Test") }
};
double match = matcher.IsMatch(jObject).Score;
var match = matcher.IsMatch(jObject).Score;
// Assert
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonMatcher_IsMatch_JObjectAsString_RejectOnMatch()
{
// Assign
// Assign
var matcher = new JsonMatcher(MatchBehaviour.RejectOnMatch, "{ \"Id\" : 1, \"Name\" : \"Test\" }");
// Act
// Act
var jObject = new JObject
{
{ "Id", new JValue(1) },
{ "Name", new JValue("Test") }
};
double match = matcher.IsMatch(jObject).Score;
var match = matcher.IsMatch(jObject).Score;
// Assert
// Assert
Assert.Equal(0.0, match);
}
[Fact]
public void JsonMatcher_IsMatch_JObjectWithDateTimeOffsetAsString()
{
// Assign
// Assign
var matcher = new JsonMatcher("{ \"preferredAt\" : \"2019-11-21T10:32:53.2210009+00:00\" }");
// Act
// Act
var jObject = new JObject
{
{ "preferredAt", new JValue("2019-11-21T10:32:53.2210009+00:00") }
};
double match = matcher.IsMatch(jObject).Score;
var match = matcher.IsMatch(jObject).Score;
// Assert
// Assert
Assert.Equal(1.0, match);
}
[Fact]
public void JsonMatcher_IsMatch_NormalEnum()
{
// Assign
var matcher = new JsonMatcher(new Test1 { NormalEnum = NormalEnum.Abc});
// Assign
var matcher = new JsonMatcher(new Test1 { NormalEnum = NormalEnum.Abc });
// Act
// Act
var jObject = new JObject
{
{ "NormalEnum", new JValue(0) }
};
double match = matcher.IsMatch(jObject).Score;
var match = matcher.IsMatch(jObject).Score;
// Assert
// Assert
match.Should().Be(1.0);
}
[Fact]
public void JsonMatcher_IsMatch_EnumWithJsonConverter()
{
// Assign
// Assign
var matcher = new JsonMatcher(new Test2 { EnumWithJsonConverter = EnumWithJsonConverter.Type1 });
// Act
// Act
var jObject = new JObject
{
{ "EnumWithJsonConverter", new JValue("Type1") }
};
double match = matcher.IsMatch(jObject).Score;
var match = matcher.IsMatch(jObject).Score;
// Assert
// Assert
match.Should().Be(1.0);
}
[Fact]
public void JsonMatcher_IsMatch_WithRegexTrue_ShouldMatch()
{
// Assign
var matcher = new JsonMatcher(new { Id = "^\\d+$", Name = "Test" }, regex: true);
// Act
var jObject = new JObject
{
{ "Id", new JValue(42) },
{ "Name", new JValue("Test") }
};
var score = matcher.IsMatch(jObject).Score;
// Assert
Assert.Equal(1.0, score);
}
[Fact]
public void JsonMatcher_IsMatch_WithRegexTrue_Complex_ShouldMatch()
{
// Assign
var matcher = new JsonMatcher(new
{
Complex = new
{
Id = "^\\d+$",
Name = ".*"
}
}, regex: true);
// Act
var jObject = new JObject
{
{
"Complex", new JObject
{
{ "Id", new JValue(42) },
{ "Name", new JValue("Test") }
}
}
};
var score = matcher.IsMatch(jObject).Score;
// Assert
Assert.Equal(1.0, score);
}
[Fact]
public void JsonMatcher_IsMatch_WithRegexTrue_Complex_ShouldNotMatch()
{
// Assign
var matcher = new JsonMatcher(new
{
Complex = new
{
Id = "^\\d+$",
Name = ".*"
}
}, regex: true);
// Act
var jObject = new JObject
{
{
"Complex", new JObject
{
{ "Id", new JValue(42) },
{ "Name", new JValue("Test") },
{ "Other", new JValue("Other") }
}
}
};
var score = matcher.IsMatch(jObject).Score;
// Assert
Assert.Equal(MatchScores.Mismatch, score);
}
[Fact]
public void JsonMatcher_IsMatch_WithRegexTrue_Array_ShouldMatch()
{
// Assign
var matcher = new JsonMatcher(new
{
Array = new[]
{
"^\\d+$",
".*"
}
}, regex: true);
// Act
var jObject = new JObject
{
{ "Array", new JArray("42", "test") }
};
var score = matcher.IsMatch(jObject).Score;
// Assert
Assert.Equal(1.0, score);
}
[Fact]
public void JsonMatcher_IsMatch_WithRegexTrue_Array_ShouldNotMatch()
{
// Assign
var matcher = new JsonMatcher(new
{
Array = new[]
{
"^\\d+$",
".*"
}
}, regex: true);
// Act
var jObject = new JObject
{
{ "Array", new JArray("42", "test", "other") }
};
var score = matcher.IsMatch(jObject).Score;
// Assert
Assert.Equal(MatchScores.Mismatch, score);
}
[Fact]
public void JsonMatcher_IsMatch_GuidAndString()
{
// Assign
var id = Guid.NewGuid();
var idAsString = id.ToString();
var matcher = new JsonMatcher(new { Id = id });
// Act
var jObject = new JObject
{
{ "Id", new JValue(idAsString) }
};
var score = matcher.IsMatch(jObject).Score;
// Assert
Assert.Equal(1.0, score);
}
[Fact]
public void JsonMatcher_IsMatch_StringAndGuid()
{
// Assign
var id = Guid.NewGuid();
var idAsString = id.ToString();
var matcher = new JsonMatcher(new { Id = idAsString });
// Act
var jObject = new JObject
{
{ "Id", new JValue(id) }
};
var score = matcher.IsMatch(jObject).Score;
// Assert
Assert.Equal(1.0, score);
}
}

View File

@@ -25,7 +25,8 @@
Pattern: {
name: stef
},
IgnoreCase: false
IgnoreCase: false,
Regex: false
},
ProtoBufMessageType: greet.HelloRequest
}

View File

@@ -42,7 +42,8 @@ message HelloReply {
Pattern: {
name: stef
},
IgnoreCase: false
IgnoreCase: false,
Regex: false
},
ProtoBufMessageType: greet.HelloRequest
}