From 8431d4ae14a106a4c6a20fb36055d2fe23f8498a Mon Sep 17 00:00:00 2001 From: jhonny Date: Fri, 2 Feb 2024 15:31:45 +0000 Subject: [PATCH 1/5] fix: avoid null exception when received body is empty but recorded body isn't --- EasyVCR.Tests/ClientTest.cs | 23 +++++++++++++++++++++++ EasyVCR/MatchRules.cs | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/EasyVCR.Tests/ClientTest.cs b/EasyVCR.Tests/ClientTest.cs index 38a9a4c..7c29a53 100644 --- a/EasyVCR.Tests/ClientTest.cs +++ b/EasyVCR.Tests/ClientTest.cs @@ -512,6 +512,29 @@ public async Task TestMatchNonJsonBody() var response = await client.PostAsync(url, content); 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"; + + // record baseline request first + var client = HttpClients.NewHttpClient(cassette, Mode.Record); + var someContent = new ByteArrayContent(Encoding.UTF8.GetBytes("whatevs")); + _ = 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)); + Assert.ThrowsExceptionAsync(async () => await client.PostAsync(url, emptyContent), "No interaction found for request POST https://httpbin.org/post"); + } + [TestMethod] diff --git a/EasyVCR/MatchRules.cs b/EasyVCR/MatchRules.cs index 2984e4e..1e6288a 100644 --- a/EasyVCR/MatchRules.cs +++ b/EasyVCR/MatchRules.cs @@ -159,7 +159,7 @@ public MatchRules ByBody(List? ignoredElements = null) // both have empty string bodies, so they match return true; - return receivedBody!.Equals(recordedBody, StringComparison.OrdinalIgnoreCase); + return (receivedBody ?? "").Equals(recordedBody, StringComparison.OrdinalIgnoreCase); }); return this; } From e53702b0c3047721d42a5dda5f5b3d41c46747a8 Mon Sep 17 00:00:00 2001 From: jhonny Date: Fri, 2 Feb 2024 16:46:31 +0000 Subject: [PATCH 2/5] lint --- EasyVCR.Tests/ClientTest.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/EasyVCR.Tests/ClientTest.cs b/EasyVCR.Tests/ClientTest.cs index 7c29a53..1b3eda9 100644 --- a/EasyVCR.Tests/ClientTest.cs +++ b/EasyVCR.Tests/ClientTest.cs @@ -512,7 +512,7 @@ public async Task TestMatchNonJsonBody() var response = await client.PostAsync(url, content); Assert.IsNotNull(response); } - + [TestMethod] public async Task TestMatchEmptyStringBodyToNonEmptyStringBody() { @@ -520,8 +520,7 @@ public async Task TestMatchEmptyStringBodyToNonEmptyStringBody() cassette.Erase(); // Erase cassette before recording const string url = "https://httpbin.org/post"; - - // record baseline request first + var client = HttpClients.NewHttpClient(cassette, Mode.Record); var someContent = new ByteArrayContent(Encoding.UTF8.GetBytes("whatevs")); _ = await client.PostAsync(url, someContent); @@ -534,7 +533,7 @@ public async Task TestMatchEmptyStringBodyToNonEmptyStringBody() var emptyContent = new ByteArrayContent(Encoding.UTF8.GetBytes(string.Empty)); Assert.ThrowsExceptionAsync(async () => await client.PostAsync(url, emptyContent), "No interaction found for request POST https://httpbin.org/post"); } - + [TestMethod] From 2f6bc78c0c972126d54c7a2eb6d10b71c9a2dd36 Mon Sep 17 00:00:00 2001 From: nwithan8 Date: Fri, 2 Feb 2024 12:48:25 -0700 Subject: [PATCH 3/5] - Use shortcut for checking null bodies in matching --- EasyVCR/InternalUtilities/Extensions.cs | 7 +++++++ EasyVCR/MatchRules.cs | 18 ++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 EasyVCR/InternalUtilities/Extensions.cs 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 1e6288a..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; } From a860eafceca4cadd78900d546aa6b5e81869a604 Mon Sep 17 00:00:00 2001 From: nwithan8 Date: Fri, 2 Feb 2024 12:57:22 -0700 Subject: [PATCH 4/5] - Minor test cleanup --- EasyVCR.Tests/ClientTest.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/EasyVCR.Tests/ClientTest.cs b/EasyVCR.Tests/ClientTest.cs index 1b3eda9..8907375 100644 --- a/EasyVCR.Tests/ClientTest.cs +++ b/EasyVCR.Tests/ClientTest.cs @@ -522,7 +522,7 @@ public async Task TestMatchEmptyStringBodyToNonEmptyStringBody() const string url = "https://httpbin.org/post"; var client = HttpClients.NewHttpClient(cassette, Mode.Record); - var someContent = new ByteArrayContent(Encoding.UTF8.GetBytes("whatevs")); + 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 @@ -531,11 +531,9 @@ public async Task TestMatchEmptyStringBodyToNonEmptyStringBody() MatchRules = new MatchRules().ByBody() }); var emptyContent = new ByteArrayContent(Encoding.UTF8.GetBytes(string.Empty)); - Assert.ThrowsExceptionAsync(async () => await client.PostAsync(url, emptyContent), "No interaction found for request POST https://httpbin.org/post"); + await Assert.ThrowsExceptionAsync(async () => await client.PostAsync(url, emptyContent), $"No interaction found for request POST {url}"); } - - [TestMethod] public async Task TestInteractionElements() { From 821c898c6f500c1701a553e983a6a49b03ca1ecd Mon Sep 17 00:00:00 2001 From: nwithan8 Date: Fri, 2 Feb 2024 13:00:22 -0700 Subject: [PATCH 5/5] - Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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)