From 6e624b0a594bf27b96698d551da431285d5eda5e Mon Sep 17 00:00:00 2001 From: Adrian Iftode Date: Tue, 6 Oct 2026 19:06:57 +0100 Subject: [PATCH] Add configurable MaximumReadableBytes via HttpResponseFormatterOptions --- readme.md | 28 ++++++++++ .../AwesomeAssertions.Web.Types.csproj | 4 ++ .../HttpResponseMessageFormatter.cs | 7 ++- .../FluentAssertions.Web.Types.csproj | 4 ++ .../FluentAssertionsWebConfig.cs | 31 +++++++++-- .../HttpResponseFormatterExtensions.cs | 34 +++++++----- .../HttpResponseFormatterOptions.cs | 32 +++++++++++ .../Internal/ContentProcessors/Appender.cs | 4 +- .../ContentProcessors/BinaryProcessor.cs | 3 +- .../ContentProcessors/FallbackProcessor.cs | 3 +- .../InternalServerErrorProcessor.cs | 2 + .../ContentProcessors/JsonProcessor.cs | 3 +- .../ContentProcessors/MultipartProcessor.cs | 5 +- .../ContentProcessors/ProcessorBase.cs | 9 +++- .../ContentProcessors/ProcessorsRunner.cs | 10 ++-- src/HttpMessageFormatter/Readme.md | 16 +++++- ...eb.AwesomeAssertionsWebConfig.Tests.csproj | 1 + .../ResponseFormatterOptionsTests.cs | 53 ++++++++++++++++++ ...Web.FluentAssertionsWebConfig.Tests.csproj | 1 + .../ResponseFormatterOptionsTests.cs | 53 ++++++++++++++++++ .../FallbackProcessorTests.cs | 43 +++++++++++++++ .../HttpResponseFormatterExtensionsTests.cs | 54 +++++++++++++++++++ 22 files changed, 369 insertions(+), 31 deletions(-) create mode 100644 src/HttpMessageFormatter/HttpResponseFormatterOptions.cs create mode 100644 test/AwesomeAssertions.Web.AwesomeAssertionsWebConfig.Tests/ResponseFormatterOptionsTests.cs create mode 100644 test/FluentAssertions.Web.FluentAssertionsWebConfig.Tests/ResponseFormatterOptionsTests.cs diff --git a/readme.md b/readme.md index d617149..4149b53 100644 --- a/readme.md +++ b/readme.md @@ -269,6 +269,34 @@ The related `Newtonsoft.Json.JsonSerializerSettings` used to configure the Newto NewtonsoftJsonSerializerConfig.Options.Converters.Add(new YesNoBooleanJsonConverter()); ``` +### Response Formatting + +The assertion failure messages include a readable rendering of the HTTP response. By default, only the first `10 * 128 * 1024` characters of the response content are printed, the rest being replaced by a warning message. To change this limit globally, set the `ResponseFormatterOptions`: + +```csharp +FluentAssertionsWebConfig.ResponseFormatterOptions = new HttpResponseFormatterOptions +{ + MaximumReadableBytes = 4 * 1024 +}; +``` +or + +```csharp +AwesomeAssertionsWebConfig.ResponseFormatterOptions = new HttpResponseFormatterOptions +{ + MaximumReadableBytes = 4 * 1024 +}; +``` + +The change must be done before the test is run, like for the serializer configuration above. The global options only apply to the messages produced by **FluentAssertions.Web** and **AwesomeAssertions.Web**. The **HttpMessageFormatter** library itself keeps no global state and accepts the same `HttpResponseFormatterOptions` parameter object per call: + +```csharp +var formatted = response.Format(new HttpResponseFormatterOptions +{ + MaximumReadableBytes = 4 * 1024 +}); +``` + ## Full API | *HttpResponseMessageAssertions* | Contains a number of methods to assert that an HttpResponseMessage is in the expected state related to the HTTP content. | diff --git a/src/AwesomeAssertions.Web.Types/AwesomeAssertions.Web.Types.csproj b/src/AwesomeAssertions.Web.Types/AwesomeAssertions.Web.Types.csproj index e72fecf..4b734fe 100644 --- a/src/AwesomeAssertions.Web.Types/AwesomeAssertions.Web.Types.csproj +++ b/src/AwesomeAssertions.Web.Types/AwesomeAssertions.Web.Types.csproj @@ -8,6 +8,10 @@ + + + + $(DefineConstants);AAV diff --git a/src/FluentAssertions.HttpMessageFormatter/HttpResponseMessageFormatter.cs b/src/FluentAssertions.HttpMessageFormatter/HttpResponseMessageFormatter.cs index 579a450..2d28498 100644 --- a/src/FluentAssertions.HttpMessageFormatter/HttpResponseMessageFormatter.cs +++ b/src/FluentAssertions.HttpMessageFormatter/HttpResponseMessageFormatter.cs @@ -26,7 +26,12 @@ public void Format(object value, FormatChild formatChild) { var response = (HttpResponseMessage)value; - var formatted = response.Format(); +#if AAV + var options = AwesomeAssertionsWebConfig.ResponseFormatterOptions; +#else + var options = FluentAssertionsWebConfig.ResponseFormatterOptions; +#endif + var formatted = response.Format(options); formattedGraph.AddFragment(formatted); } diff --git a/src/FluentAssertions.Web.Types/FluentAssertions.Web.Types.csproj b/src/FluentAssertions.Web.Types/FluentAssertions.Web.Types.csproj index 9eb39d8..1b1070c 100644 --- a/src/FluentAssertions.Web.Types/FluentAssertions.Web.Types.csproj +++ b/src/FluentAssertions.Web.Types/FluentAssertions.Web.Types.csproj @@ -7,4 +7,8 @@ + + + + diff --git a/src/FluentAssertions.Web.Types/FluentAssertionsWebConfig.cs b/src/FluentAssertions.Web.Types/FluentAssertionsWebConfig.cs index afffb35..faa88dd 100644 --- a/src/FluentAssertions.Web.Types/FluentAssertionsWebConfig.cs +++ b/src/FluentAssertions.Web.Types/FluentAssertionsWebConfig.cs @@ -1,4 +1,6 @@ -// ReSharper disable once CheckNamespace +using HttpMessageFormatter; + +// ReSharper disable once CheckNamespace #if AAV namespace AwesomeAssertions; #else @@ -18,11 +20,20 @@ public static class FluentAssertionsWebConfig #endif { private static ISerializer? _serializer; + private static HttpResponseFormatterOptions? _responseFormatterOptions; #if AAV - static AwesomeAssertionsWebConfig() => Serializer = new SystemTextJsonSerializer(); + static AwesomeAssertionsWebConfig() + { + Serializer = new SystemTextJsonSerializer(); + ResponseFormatterOptions = new HttpResponseFormatterOptions(); + } #else - static FluentAssertionsWebConfig() => Serializer = new SystemTextJsonSerializer(); + static FluentAssertionsWebConfig() + { + Serializer = new SystemTextJsonSerializer(); + ResponseFormatterOptions = new HttpResponseFormatterOptions(); + } #endif /// @@ -34,4 +45,18 @@ public static ISerializer Serializer set => _serializer = value ?? throw new ArgumentNullException(nameof(value), "Serializer cannot be null."); } + + /// + /// The options used when formatting an HTTP response message into a readable string, + /// for example in the assertion failure messages. + /// They control, for instance, how many characters of the content are printed before the content + /// is considered too large. These options do not change the assertions themselves, only the way + /// the HTTP messages are formatted. + /// + public static HttpResponseFormatterOptions ResponseFormatterOptions + { + get => _responseFormatterOptions ?? throw new InvalidOperationException("ResponseFormatterOptions cannot be null"); + + set => _responseFormatterOptions = value ?? throw new ArgumentNullException(nameof(value), "ResponseFormatterOptions cannot be null."); + } } diff --git a/src/HttpMessageFormatter/HttpResponseFormatterExtensions.cs b/src/HttpMessageFormatter/HttpResponseFormatterExtensions.cs index dda8549..20f55c6 100644 --- a/src/HttpMessageFormatter/HttpResponseFormatterExtensions.cs +++ b/src/HttpMessageFormatter/HttpResponseFormatterExtensions.cs @@ -15,23 +15,33 @@ public static class HttpResponseFormatterExtensions /// The HTTP response message to format. /// A formatted string representation of the HTTP response. public static string Format(this HttpResponseMessage response) + => Format(response, null); + + /// + /// Formats an HTTP response message into a readable string representation using the given . + /// + /// The HTTP response message to format. + /// The options controlling the formatting, like the maximum number of content characters to print. + /// When , the default options are used. + /// A formatted string representation of the HTTP response. + public static string Format(this HttpResponseMessage response, HttpResponseFormatterOptions? options) { var messageBuilder = new StringBuilder(); messageBuilder.AppendLine(); messageBuilder.AppendLine(); messageBuilder.AppendLine("The HTTP response was:"); - Func contentResolver = async () => await AppendHttpResponseMessage(messageBuilder, response); + Func contentResolver = async () => await AppendHttpResponseMessage(messageBuilder, response, options); contentResolver.ExecuteInDefaultSynchronizationContext(); return messageBuilder.ToString(); } - private static async Task AppendHttpResponseMessage(StringBuilder messageBuilder, HttpResponseMessage response) + private static async Task AppendHttpResponseMessage(StringBuilder messageBuilder, HttpResponseMessage response, HttpResponseFormatterOptions? options) { try { - await AppendResponse(messageBuilder, response); + await AppendResponse(messageBuilder, response, options); } catch (Exception e) { @@ -40,7 +50,7 @@ private static async Task AppendHttpResponseMessage(StringBuilder messageBuilder } try { - await AppendRequest(messageBuilder, response); + await AppendRequest(messageBuilder, response, options); } catch (Exception e) { @@ -49,15 +59,15 @@ private static async Task AppendHttpResponseMessage(StringBuilder messageBuilder } } - private static async Task AppendResponse(StringBuilder messageBuilder, HttpResponseMessage response) + private static async Task AppendResponse(StringBuilder messageBuilder, HttpResponseMessage response, HttpResponseFormatterOptions? options) { AppendProtocolAndStatusCode(messageBuilder, response); Appender.AppendHeaders(messageBuilder, response.GetHeaders()); AppendContentLength(messageBuilder, response.Content); - await AppendResponseContent(messageBuilder, response); + await AppendResponseContent(messageBuilder, response, options); } - private static async Task AppendRequest(StringBuilder messageBuilder, HttpResponseMessage response) + private static async Task AppendRequest(StringBuilder messageBuilder, HttpResponseMessage response, HttpResponseFormatterOptions? options) { messageBuilder.AppendLine(); messageBuilder.AppendLine(); @@ -75,10 +85,10 @@ private static async Task AppendRequest(StringBuilder messageBuilder, HttpRespon Appender.AppendHeaders(messageBuilder, request.GetHeaders()); AppendContentLength(messageBuilder, request.Content); - await AppendRequestContent(messageBuilder, request.Content); + await AppendRequestContent(messageBuilder, request.Content, options); } - private static async Task AppendResponseContent(StringBuilder messageBuilder, HttpResponseMessage response) + private static async Task AppendResponseContent(StringBuilder messageBuilder, HttpResponseMessage response, HttpResponseFormatterOptions? options) { var content = response.Content; if (content == null) @@ -88,7 +98,7 @@ private static async Task AppendResponseContent(StringBuilder messageBuilder, Ht var processors = new List(); processors.Add(new InternalServerErrorProcessor(response, content)); - processors.AddRange(ProcessorsRunner.CommonProcessors(content)); + processors.AddRange(ProcessorsRunner.CommonProcessors(content, options)); var contentBuilder = await ProcessorsRunner.RunProcessors(processors); messageBuilder.AppendLine(); @@ -109,9 +119,9 @@ internal static IEnumerable>> GetHeader return request.Headers.Union(requestContentHeaders); } - private static async Task AppendRequestContent(StringBuilder messageBuilder, HttpContent content) + private static async Task AppendRequestContent(StringBuilder messageBuilder, HttpContent content, HttpResponseFormatterOptions? options) { - await Appender.AppendContent(messageBuilder, content, false); + await Appender.AppendContent(messageBuilder, content, false, options); } private static void AppendProtocolAndStatusCode(StringBuilder messageBuilder, HttpResponseMessage response) diff --git a/src/HttpMessageFormatter/HttpResponseFormatterOptions.cs b/src/HttpMessageFormatter/HttpResponseFormatterOptions.cs new file mode 100644 index 0000000..85c095c --- /dev/null +++ b/src/HttpMessageFormatter/HttpResponseFormatterOptions.cs @@ -0,0 +1,32 @@ +using HttpMessageFormatter.Internal; + +namespace HttpMessageFormatter; + +/// +/// Parameter object with the options used when formatting an . +/// +public class HttpResponseFormatterOptions +{ + private int _maximumReadableBytes = ContentFormatterOptions.MaximumReadableBytes; + + /// + /// The maximum number of characters read from the content and printed into the formatted output. + /// When the content is longer, only a part of it is printed together with a warning message. + /// Defaults to 10 * 128 * 1024 (1,310,720) characters. + /// + /// The value is less than one. + public int MaximumReadableBytes + { + get => _maximumReadableBytes; + + set + { + if (value < 1) + { + throw new ArgumentOutOfRangeException(nameof(value), value, $"{nameof(MaximumReadableBytes)} must be greater than zero."); + } + + _maximumReadableBytes = value; + } + } +} diff --git a/src/HttpMessageFormatter/Internal/ContentProcessors/Appender.cs b/src/HttpMessageFormatter/Internal/ContentProcessors/Appender.cs index fa15ad0..a60a1b2 100644 --- a/src/HttpMessageFormatter/Internal/ContentProcessors/Appender.cs +++ b/src/HttpMessageFormatter/Internal/ContentProcessors/Appender.cs @@ -16,9 +16,9 @@ public static void AppendHeaders(StringBuilder messageBuilder, IEnumerable 0) { diff --git a/src/HttpMessageFormatter/Internal/ContentProcessors/BinaryProcessor.cs b/src/HttpMessageFormatter/Internal/ContentProcessors/BinaryProcessor.cs index 76ffb2f..74eed3a 100644 --- a/src/HttpMessageFormatter/Internal/ContentProcessors/BinaryProcessor.cs +++ b/src/HttpMessageFormatter/Internal/ContentProcessors/BinaryProcessor.cs @@ -4,7 +4,8 @@ internal class BinaryProcessor : ProcessorBase { private readonly HttpContent? _httpContent; - public BinaryProcessor(HttpContent? httpContent) + public BinaryProcessor(HttpContent? httpContent, HttpResponseFormatterOptions? options = null) + : base(options) { _httpContent = httpContent; } diff --git a/src/HttpMessageFormatter/Internal/ContentProcessors/FallbackProcessor.cs b/src/HttpMessageFormatter/Internal/ContentProcessors/FallbackProcessor.cs index bfb307e..c3be511 100644 --- a/src/HttpMessageFormatter/Internal/ContentProcessors/FallbackProcessor.cs +++ b/src/HttpMessageFormatter/Internal/ContentProcessors/FallbackProcessor.cs @@ -4,7 +4,8 @@ internal class FallbackProcessor : ProcessorBase { private readonly HttpContent? _httpContent; - public FallbackProcessor(HttpContent? httpContent) + public FallbackProcessor(HttpContent? httpContent, HttpResponseFormatterOptions? options = null) + : base(options) { _httpContent = httpContent; } diff --git a/src/HttpMessageFormatter/Internal/ContentProcessors/InternalServerErrorProcessor.cs b/src/HttpMessageFormatter/Internal/ContentProcessors/InternalServerErrorProcessor.cs index 477464c..00f8a39 100644 --- a/src/HttpMessageFormatter/Internal/ContentProcessors/InternalServerErrorProcessor.cs +++ b/src/HttpMessageFormatter/Internal/ContentProcessors/InternalServerErrorProcessor.cs @@ -7,7 +7,9 @@ internal class InternalServerErrorProcessor : ProcessorBase private readonly HttpResponseMessage? _httpResponseMessage; private readonly HttpContent? _httpContent; + // the exception details are extracted as they are and are not limited by the readable bytes limit public InternalServerErrorProcessor(HttpResponseMessage? httpResponseMessage, HttpContent? httpContent) + : base(null) { _httpResponseMessage = httpResponseMessage; _httpContent = httpContent; diff --git a/src/HttpMessageFormatter/Internal/ContentProcessors/JsonProcessor.cs b/src/HttpMessageFormatter/Internal/ContentProcessors/JsonProcessor.cs index 47dd0f0..a8fd62e 100644 --- a/src/HttpMessageFormatter/Internal/ContentProcessors/JsonProcessor.cs +++ b/src/HttpMessageFormatter/Internal/ContentProcessors/JsonProcessor.cs @@ -9,7 +9,8 @@ internal class JsonProcessor : ProcessorBase private static readonly JavaScriptEncoder JavaScriptEncoder = JavaScriptEncoder.Create(UnicodeRanges.All); private readonly HttpContent? _httpContent; - public JsonProcessor(HttpContent? httpContent) + public JsonProcessor(HttpContent? httpContent, HttpResponseFormatterOptions? options = null) + : base(options) { _httpContent = httpContent; } diff --git a/src/HttpMessageFormatter/Internal/ContentProcessors/MultipartProcessor.cs b/src/HttpMessageFormatter/Internal/ContentProcessors/MultipartProcessor.cs index 5c823e9..c9eaaee 100644 --- a/src/HttpMessageFormatter/Internal/ContentProcessors/MultipartProcessor.cs +++ b/src/HttpMessageFormatter/Internal/ContentProcessors/MultipartProcessor.cs @@ -4,7 +4,8 @@ internal class MultipartProcessor : ProcessorBase { private readonly HttpContent? _httpContent; - public MultipartProcessor(HttpContent? httpContent) + public MultipartProcessor(HttpContent? httpContent, HttpResponseFormatterOptions? options = null) + : base(options) { _httpContent = httpContent; } @@ -52,7 +53,7 @@ protected override async Task Handle(StringBuilder contentBuilder) Appender.AppendHeaders(contentBuilder, content.Headers); - await Appender.AppendContent(contentBuilder, content, true); + await Appender.AppendContent(contentBuilder, content, true, Options); } } finally diff --git a/src/HttpMessageFormatter/Internal/ContentProcessors/ProcessorBase.cs b/src/HttpMessageFormatter/Internal/ContentProcessors/ProcessorBase.cs index 4e3df4f..8fb3f13 100644 --- a/src/HttpMessageFormatter/Internal/ContentProcessors/ProcessorBase.cs +++ b/src/HttpMessageFormatter/Internal/ContentProcessors/ProcessorBase.cs @@ -2,6 +2,10 @@ namespace HttpMessageFormatter.Internal.ContentProcessors; internal abstract class ProcessorBase : IContentProcessor { + protected ProcessorBase(HttpResponseFormatterOptions? options) => Options = options; + + protected HttpResponseFormatterOptions? Options { get; } + public async Task GetContentInfo(StringBuilder contentBuilder) { if (!CanHandle()) @@ -22,14 +26,15 @@ protected void AppendContentWithinLimits(StringBuilder contentBuilder, string? c return; } + var maximumReadableBytes = Options?.MaximumReadableBytes ?? ContentFormatterOptions.MaximumReadableBytes; var toAppend = content; var contentLength = content.Length; - if (contentLength >= ContentFormatterOptions.MaximumReadableBytes) + if (contentLength >= maximumReadableBytes) { contentBuilder.AppendLine(); contentBuilder.AppendLine(ContentFormatterOptions.WarningMessageWhenContentIsTooLarge); - toAppend = content!.Substring(0, ContentFormatterOptions.MaximumReadableBytes); + toAppend = content!.Substring(0, maximumReadableBytes); } contentBuilder.Append(toAppend); diff --git a/src/HttpMessageFormatter/Internal/ContentProcessors/ProcessorsRunner.cs b/src/HttpMessageFormatter/Internal/ContentProcessors/ProcessorsRunner.cs index b4693ca..714c226 100644 --- a/src/HttpMessageFormatter/Internal/ContentProcessors/ProcessorsRunner.cs +++ b/src/HttpMessageFormatter/Internal/ContentProcessors/ProcessorsRunner.cs @@ -13,11 +13,11 @@ public static async Task RunProcessors(IEnumerable CommonProcessors(HttpContent content) => new IContentProcessor[] + public static IReadOnlyCollection CommonProcessors(HttpContent content, HttpResponseFormatterOptions? options = null) => new IContentProcessor[] { - new JsonProcessor(content), - new BinaryProcessor(content), - new MultipartProcessor(content), - new FallbackProcessor(content) + new JsonProcessor(content, options), + new BinaryProcessor(content, options), + new MultipartProcessor(content, options), + new FallbackProcessor(content, options) }; } diff --git a/src/HttpMessageFormatter/Readme.md b/src/HttpMessageFormatter/Readme.md index 9b2e89b..4171187 100644 --- a/src/HttpMessageFormatter/Readme.md +++ b/src/HttpMessageFormatter/Readme.md @@ -24,4 +24,18 @@ public class HttpResponseFormatterExtensionsTests } } -``` \ No newline at end of file +``` + +### Limiting The Printed Content + +By default, only the first 10 * 128 * 1024 characters of the content are printed. Pass an +`HttpResponseFormatterOptions` parameter object to change this limit: + +```csharp +using HttpMessageFormatter; + +var formatted = subject.Format(new HttpResponseFormatterOptions +{ + MaximumReadableBytes = 4 * 1024 +}); +``` diff --git a/test/AwesomeAssertions.Web.AwesomeAssertionsWebConfig.Tests/AwesomeAssertions.Web.AwesomeAssertionsWebConfig.Tests.csproj b/test/AwesomeAssertions.Web.AwesomeAssertionsWebConfig.Tests/AwesomeAssertions.Web.AwesomeAssertionsWebConfig.Tests.csproj index b33bd52..42937c2 100644 --- a/test/AwesomeAssertions.Web.AwesomeAssertionsWebConfig.Tests/AwesomeAssertions.Web.AwesomeAssertionsWebConfig.Tests.csproj +++ b/test/AwesomeAssertions.Web.AwesomeAssertionsWebConfig.Tests/AwesomeAssertions.Web.AwesomeAssertionsWebConfig.Tests.csproj @@ -8,6 +8,7 @@ + diff --git a/test/AwesomeAssertions.Web.AwesomeAssertionsWebConfig.Tests/ResponseFormatterOptionsTests.cs b/test/AwesomeAssertions.Web.AwesomeAssertionsWebConfig.Tests/ResponseFormatterOptionsTests.cs new file mode 100644 index 0000000..3860b61 --- /dev/null +++ b/test/AwesomeAssertions.Web.AwesomeAssertionsWebConfig.Tests/ResponseFormatterOptionsTests.cs @@ -0,0 +1,53 @@ +using System.Net.Http; +using AwesomeAssertions.Formatting; +using HttpMessageFormatter; + +namespace AwesomeAssertions.Web.AwesomeAssertionsWebConfig.Tests; + +[Collection("Response Formatter Options Tests")] +public sealed class ResponseFormatterOptionsTests : IDisposable +{ + private readonly HttpResponseFormatterOptions _initialResponseFormatterOptions; + + public ResponseFormatterOptionsTests() + { + _initialResponseFormatterOptions = AwesomeAssertions.AwesomeAssertionsWebConfig.ResponseFormatterOptions; + } + + [Fact] + public void ResponseFormatterOptions_IsAvailableByDefault() + { + AwesomeAssertions.AwesomeAssertionsWebConfig.ResponseFormatterOptions + .Should().BeOfType() + .Which.MaximumReadableBytes.Should().Be(10 * 128 * 1024); + } + + [Fact] + public void ResponseFormatterOptions_ShouldLimitTheFormattedResponseContent() + { + // Arrange + AwesomeAssertions.AwesomeAssertionsWebConfig.ResponseFormatterOptions = new HttpResponseFormatterOptions + { + MaximumReadableBytes = 20 + }; + using var subject = new HttpResponseMessage + { + Content = new StringContent(new string('-', 50) + new string('+', 50)) + }; + var formattedGraph = new FormattedObjectGraph(maxLines: 100); + + // Act + new HttpResponseMessageFormatter().Format(subject, formattedGraph, null!, null!); + + // Assert + var formatted = formattedGraph.ToString(); + formatted.Should().Match("*Content is too large to display*") + .And.Contain(new string('-', 20)) + .And.NotContain("+"); + } + + public void Dispose() + { + AwesomeAssertions.AwesomeAssertionsWebConfig.ResponseFormatterOptions = _initialResponseFormatterOptions; + } +} diff --git a/test/FluentAssertions.Web.FluentAssertionsWebConfig.Tests/FluentAssertions.Web.FluentAssertionsWebConfig.Tests.csproj b/test/FluentAssertions.Web.FluentAssertionsWebConfig.Tests/FluentAssertions.Web.FluentAssertionsWebConfig.Tests.csproj index 0a20dfd..aabe3ff 100644 --- a/test/FluentAssertions.Web.FluentAssertionsWebConfig.Tests/FluentAssertions.Web.FluentAssertionsWebConfig.Tests.csproj +++ b/test/FluentAssertions.Web.FluentAssertionsWebConfig.Tests/FluentAssertions.Web.FluentAssertionsWebConfig.Tests.csproj @@ -8,6 +8,7 @@ + diff --git a/test/FluentAssertions.Web.FluentAssertionsWebConfig.Tests/ResponseFormatterOptionsTests.cs b/test/FluentAssertions.Web.FluentAssertionsWebConfig.Tests/ResponseFormatterOptionsTests.cs new file mode 100644 index 0000000..c8dfcf8 --- /dev/null +++ b/test/FluentAssertions.Web.FluentAssertionsWebConfig.Tests/ResponseFormatterOptionsTests.cs @@ -0,0 +1,53 @@ +using System.Net.Http; +using FluentAssertions.Formatting; +using HttpMessageFormatter; + +namespace FluentAssertions.Web.FluentAssertionsWebConfig.Tests; + +[Collection("Response Formatter Options Tests")] +public sealed class ResponseFormatterOptionsTests : IDisposable +{ + private readonly HttpResponseFormatterOptions _initialResponseFormatterOptions; + + public ResponseFormatterOptionsTests() + { + _initialResponseFormatterOptions = FluentAssertions.FluentAssertionsWebConfig.ResponseFormatterOptions; + } + + [Fact] + public void ResponseFormatterOptions_IsAvailableByDefault() + { + FluentAssertions.FluentAssertionsWebConfig.ResponseFormatterOptions + .Should().BeOfType() + .Which.MaximumReadableBytes.Should().Be(10 * 128 * 1024); + } + + [Fact] + public void ResponseFormatterOptions_ShouldLimitTheFormattedResponseContent() + { + // Arrange + FluentAssertions.FluentAssertionsWebConfig.ResponseFormatterOptions = new HttpResponseFormatterOptions + { + MaximumReadableBytes = 20 + }; + using var subject = new HttpResponseMessage + { + Content = new StringContent(new string('-', 50) + new string('+', 50)) + }; + var formattedGraph = new FormattedObjectGraph(maxLines: 100); + + // Act + new HttpResponseMessageFormatter().Format(subject, formattedGraph, null!, null!); + + // Assert + var formatted = formattedGraph.ToString(); + formatted.Should().Match("*Content is too large to display*") + .And.Contain(new string('-', 20)) + .And.NotContain("+"); + } + + public void Dispose() + { + FluentAssertions.FluentAssertionsWebConfig.ResponseFormatterOptions = _initialResponseFormatterOptions; + } +} diff --git a/test/HttpMessageFormatter.Tests/ContentProcessors/FallbackProcessorTests.cs b/test/HttpMessageFormatter.Tests/ContentProcessors/FallbackProcessorTests.cs index 25fc6ad..f215a5a 100644 --- a/test/HttpMessageFormatter.Tests/ContentProcessors/FallbackProcessorTests.cs +++ b/test/HttpMessageFormatter.Tests/ContentProcessors/FallbackProcessorTests.cs @@ -59,6 +59,49 @@ public async Task GivenContentWithLengthTooLarge_WhenGetContentInfo_ThenItContai contentBuilder.ToString().Should().Match("*too large*"); } + [Fact] + public async Task GivenContentLargerThanTheGivenMaximumReadableBytes_WhenGetContentInfo_ThenItTruncatesToTheGivenMaximumReadableBytes() + { + // Arrange + using var response = new HttpResponseMessage + { + Content = new StringContent(new string('-', 100)) + }; + var sut = new FallbackProcessor(response.Content, new HttpResponseFormatterOptions + { + MaximumReadableBytes = 10 + }); + var contentBuilder = new StringBuilder(); + + // Act + await sut.GetContentInfo(contentBuilder); + + // Assert + contentBuilder.ToString().Should().Match("*too large*") + .And.EndWith(new string('-', 10)); + } + + [Fact] + public async Task GivenContentSmallerThanTheGivenMaximumReadableBytes_WhenGetContentInfo_ThenItPrintsTheWholeContent() + { + // Arrange + using var response = new HttpResponseMessage + { + Content = new StringContent("the content") + }; + var sut = new FallbackProcessor(response.Content, new HttpResponseFormatterOptions + { + MaximumReadableBytes = 1000 + }); + var contentBuilder = new StringBuilder(); + + // Act + await sut.GetContentInfo(contentBuilder); + + // Assert + contentBuilder.ToString().Should().Be("the content"); + } + [Fact] public async Task GivenHttpResponseWithDisposedContent_WhenGetContentInfo_ThenIsEmpty() { diff --git a/test/HttpMessageFormatter.Tests/HttpResponseFormatterExtensionsTests.cs b/test/HttpMessageFormatter.Tests/HttpResponseFormatterExtensionsTests.cs index 2c3665b..6f0cec3 100644 --- a/test/HttpMessageFormatter.Tests/HttpResponseFormatterExtensionsTests.cs +++ b/test/HttpMessageFormatter.Tests/HttpResponseFormatterExtensionsTests.cs @@ -1,3 +1,5 @@ +using System; + namespace HttpMessageFormatter.Tests; public class HttpResponseFormatterExtensionsTests @@ -15,4 +17,56 @@ public void GivenUnspecifiedResponse_ShouldFormatBasicResponse() Assert.Contains("The HTTP response was:", formatted); Assert.Contains("HTTP/1.1 200 OK", formatted); } + + [Fact] + public void GivenOptionsWithSmallerMaximumReadableBytes_WhenFormat_ThenOnlyAPartOfTheContentIsPrinted() + { + // Arrange + using var subject = new HttpResponseMessage + { + Content = new StringContent(new string('-', 50) + new string('+', 50)) + }; + var options = new HttpResponseFormatterOptions + { + MaximumReadableBytes = 20 + }; + + // Act + var formatted = subject.Format(options); + + // Assert + Assert.Contains("Content is too large to display", formatted); + Assert.Contains(new string('-', 20), formatted); + Assert.DoesNotContain("+", formatted); + } + + [Fact] + public void GivenDefaultOptions_WhenFormat_ThenTheWholeContentIsPrinted() + { + // Arrange + var content = string.Concat(Enumerable.Repeat("-abc", 1000)); + using var subject = new HttpResponseMessage + { + Content = new StringContent(content) + }; + var options = new HttpResponseFormatterOptions(); + + // Act + var formatted = subject.Format(options); + + // Assert + Assert.DoesNotContain("too large", formatted); + Assert.Contains(content, formatted); + } + + [Fact] + public void GivenMaximumReadableBytesNotGreaterThanZero_WhenSet_ThenThrowsArgumentOutOfRangeException() + { + // Arrange + var options = new HttpResponseFormatterOptions(); + + // Act & Assert + Assert.Throws(() => options.MaximumReadableBytes = 0); + Assert.Throws(() => options.MaximumReadableBytes = -1); + } }