From cf3f387c807c53e83911c528ad081b94e5ed7fa1 Mon Sep 17 00:00:00 2001 From: Martijn Bodeman <11424653+skwasjer@users.noreply.github.com> Date: Wed, 31 Jan 2024 13:06:12 +0100 Subject: [PATCH] fix: when setting any content header after setting the HttpContent with JsonBody (or any other custom IResponseBehavior) it would reset the HttpContent to an empty content. (#99) --- .../Response/EnsureHttpContentBehavior.cs | 19 ++++++++ .../Language/Flow/Response/ResponseBuilder.cs | 15 +++---- test/MockHttp.Json.Tests/Issues/Issue98.cs | 45 +++++++++++++++++++ 3 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 src/MockHttp/Language/Flow/Response/EnsureHttpContentBehavior.cs create mode 100644 test/MockHttp.Json.Tests/Issues/Issue98.cs diff --git a/src/MockHttp/Language/Flow/Response/EnsureHttpContentBehavior.cs b/src/MockHttp/Language/Flow/Response/EnsureHttpContentBehavior.cs new file mode 100644 index 00000000..9cf0ac18 --- /dev/null +++ b/src/MockHttp/Language/Flow/Response/EnsureHttpContentBehavior.cs @@ -0,0 +1,19 @@ +using MockHttp.Responses; + +namespace MockHttp.Language.Flow.Response; + +internal sealed class EnsureHttpContentBehavior + : IResponseBehavior +{ + public Task HandleAsync + ( + MockHttpRequestContext requestContext, + HttpResponseMessage responseMessage, + ResponseHandlerDelegate next, + CancellationToken cancellationToken) + { + // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract + responseMessage.Content ??= new EmptyContent(); + return next(requestContext, responseMessage, cancellationToken); + } +} diff --git a/src/MockHttp/Language/Flow/Response/ResponseBuilder.cs b/src/MockHttp/Language/Flow/Response/ResponseBuilder.cs index e72d68c0..b028d00d 100644 --- a/src/MockHttp/Language/Flow/Response/ResponseBuilder.cs +++ b/src/MockHttp/Language/Flow/Response/ResponseBuilder.cs @@ -12,10 +12,12 @@ internal sealed class ResponseBuilder IWithContentResult, IWithHeadersResult { - private static readonly Func> EmptyHttpContentFactory = _ => Task.FromResult(new EmptyContent()); - /// - public IList Behaviors { get; } = new List(); + public IList Behaviors { get; } = new List + { + // We're adding this default behavior as the first step, to ensure HttpContent is not null initially. + new EnsureHttpContentBehavior() + }; /// public IWithStatusCodeResult StatusCode(HttpStatusCode statusCode, string? reasonPhrase = null) @@ -56,12 +58,6 @@ public IWithHeadersResult Headers(IEnumerable().Any()) - { - Body(EmptyHttpContentFactory); - } - Behaviors.Add(new HttpHeaderBehavior(headerList)); return this; } @@ -157,4 +153,3 @@ int CompareOtherWayAround(int result) } } } - diff --git a/test/MockHttp.Json.Tests/Issues/Issue98.cs b/test/MockHttp.Json.Tests/Issues/Issue98.cs new file mode 100644 index 00000000..36d5ab1f --- /dev/null +++ b/test/MockHttp.Json.Tests/Issues/Issue98.cs @@ -0,0 +1,45 @@ +using System.Net; +using System.Text; +using MockHttp.FluentAssertions; +using Newtonsoft.Json; + +namespace MockHttp.Json.Issues; + +public class Issue98 +{ + [Fact] + public async Task When_setting_content_header_after_setting_jsonBody_it_should_return_expected_response() + { + using var mockHttp = new MockHttpHandler(); + mockHttp + .When(m => m + .Method(HttpMethod.Post) + .RequestUri("api/login") + .ContentType("application/json; charset=utf-8") + .JsonBody(new { username = @"corp\user", password = "Super.Mari0.Bro$$" }) + ) + .Respond(r => r + .StatusCode(HttpStatusCode.OK) + .JsonBody(new { username = "user@corp" }, Encoding.UTF8) + .Header("Set-Cookie", + "session=abcdefghi==; Expires=Tue, 01 Feb 2024 01:01:01 GMT; Secure; HttpOnly; Path=/") + ) + .Verifiable(); + + var client = new HttpClient(mockHttp) + { + BaseAddress = new Uri("http://localhost") + }; + // Act + HttpResponseMessage? response = await client.PostAsJsonAsync("api/login", new { username = @"corp\user", password = @"Super.Mari0.Bro$$" }); + + // Assert + response.Should().HaveStatusCode(HttpStatusCode.OK); + response.Headers.Should() + .ContainKey("Set-Cookie", + "session=abcdefghi==; Expires=Tue, 01 Feb 2024 01:01:01 GMT; Secure; HttpOnly; Path=/"); + response.Should().HaveContentType("application/json; charset=utf-8"); + await response.Should().HaveContentAsync(JsonConvert.SerializeObject(new { username = "user@corp" }), Encoding.UTF8); + mockHttp.Verify(); + } +}