diff --git a/CHANGELOG.md b/CHANGELOG.md index de80a95..1a0279c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Add .NET 8.0 support - `AdvancedSettings` uses `MatchRules.Default` instead of a new instance of `MatchRules` if not provided during construction +- Fix `NullReferenceException` when trying to match by body with a null body (`refit` compatibility) ## v0.9.0 (2023-05-17) diff --git a/EasyVCR.Tests/ClientTest.cs b/EasyVCR.Tests/ClientTest.cs index 38a9a4c..8907375 100644 --- a/EasyVCR.Tests/ClientTest.cs +++ b/EasyVCR.Tests/ClientTest.cs @@ -513,6 +513,26 @@ public async Task TestMatchNonJsonBody() Assert.IsNotNull(response); } + [TestMethod] + public async Task TestMatchEmptyStringBodyToNonEmptyStringBody() + { + var cassette = TestUtils.GetCassette("test_match_empty_body"); + cassette.Erase(); // Erase cassette before recording + + const string url = "https://httpbin.org/post"; + + var client = HttpClients.NewHttpClient(cassette, Mode.Record); + var someContent = new ByteArrayContent(Encoding.UTF8.GetBytes("non_empty_string_body")); + _ = await client.PostAsync(url, someContent); + + // try to replay the request with match by body enforcement + client = HttpClients.NewHttpClient(cassette, Mode.Replay, new AdvancedSettings + { + MatchRules = new MatchRules().ByBody() + }); + var emptyContent = new ByteArrayContent(Encoding.UTF8.GetBytes(string.Empty)); + await Assert.ThrowsExceptionAsync(async () => await client.PostAsync(url, emptyContent), $"No interaction found for request POST {url}"); + } [TestMethod] public async Task TestInteractionElements() diff --git a/EasyVCR/InternalUtilities/Extensions.cs b/EasyVCR/InternalUtilities/Extensions.cs new file mode 100644 index 0000000..bdfb185 --- /dev/null +++ b/EasyVCR/InternalUtilities/Extensions.cs @@ -0,0 +1,7 @@ +namespace EasyVCR.InternalUtilities +{ + public static class Extensions + { + public static bool IsEmptyStringOrNull(this string? str) => str == null || string.IsNullOrWhiteSpace(str); + } +} \ No newline at end of file diff --git a/EasyVCR/MatchRules.cs b/EasyVCR/MatchRules.cs index 2984e4e..2162cd3 100644 --- a/EasyVCR/MatchRules.cs +++ b/EasyVCR/MatchRules.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Web; +using EasyVCR.InternalUtilities; using EasyVCR.RequestElements; using JsonSerialization = EasyVCR.InternalUtilities.JSON.Serialization; using XmlSerialization = EasyVCR.InternalUtilities.XML.Serialization; @@ -132,7 +133,7 @@ public MatchRules ByBody(List? ignoredElements = null) return true; if (received.Body == null || recorded.Body == null) - // one has a null body, so they don't match + // one has a null body, the other does not, so they don't match return false; var receivedBody = received.Body; @@ -155,11 +156,24 @@ public MatchRules ByBody(List? ignoredElements = null) // not JSON, using the string as it is } + if (receivedBody.IsEmptyStringOrNull()) + // short-cut if the received body is empty + receivedBody = null; + + if (recordedBody.IsEmptyStringOrNull()) + // short-cut if the recorded body is empty + recordedBody = null; + if (receivedBody == null && recordedBody == null) // both have empty string bodies, so they match return true; - return receivedBody!.Equals(recordedBody, StringComparison.OrdinalIgnoreCase); + if (receivedBody == null || recordedBody == null) + // one has a null body, the other does not, so they don't match + return false; + + // if the bodies are not null, then we can compare them + return receivedBody.Equals(recordedBody, StringComparison.OrdinalIgnoreCase); }); return this; }