diff --git a/src/MockHttp.Server/Server/HttpResponseMessageExtensions.cs b/src/MockHttp.Server/Server/HttpResponseMessageExtensions.cs index 6da655b9..c91f9fa0 100644 --- a/src/MockHttp.Server/Server/HttpResponseMessageExtensions.cs +++ b/src/MockHttp.Server/Server/HttpResponseMessageExtensions.cs @@ -1,4 +1,5 @@ using System.Net.Http.Headers; +using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Primitives; @@ -22,11 +23,12 @@ internal static async Task MapToFeatureAsync if (response.Content is not null) { CopyHeaders(response.Content.Headers, responseFeature.Headers); + Stream contentStream = await response.Content.ReadAsStreamAsync( #if NET6_0_OR_GREATER - await using Stream contentStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); -#else - await using Stream contentStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + cancellationToken #endif + ).ConfigureAwait(false); + await using ConfiguredAsyncDisposable _ = contentStream.ConfigureAwait(false); await contentStream.CopyToAsync(responseBodyFeature.Writer.AsStream(), 4096, cancellationToken).ConfigureAwait(false); } } diff --git a/src/MockHttp/Http/HttpHeaderEqualityComparer.cs b/src/MockHttp/Http/HttpHeaderEqualityComparer.cs index e1ad0a08..9ce89c0f 100644 --- a/src/MockHttp/Http/HttpHeaderEqualityComparer.cs +++ b/src/MockHttp/Http/HttpHeaderEqualityComparer.cs @@ -1,4 +1,5 @@ -using MockHttp.Matchers.Patterns; +using System.ComponentModel; +using MockHttp.Matchers.Patterns; namespace MockHttp.Http; @@ -25,6 +26,11 @@ internal sealed class HttpHeaderEqualityComparer : IEqualityComparer> x, KeyValuePair [ExcludeFromCodeCoverage] +#if NET8_0_OR_GREATER + [Obsolete(DiagnosticId = "SYSLIB0051")] +#endif protected HttpMockException(SerializationInfo info, StreamingContext context) : base(info, context) { diff --git a/src/MockHttp/Responses/HttpHeaderBehavior.cs b/src/MockHttp/Responses/HttpHeaderBehavior.cs index daa96fc0..92094dc0 100644 --- a/src/MockHttp/Responses/HttpHeaderBehavior.cs +++ b/src/MockHttp/Responses/HttpHeaderBehavior.cs @@ -1,5 +1,4 @@ -using System.Net.Http.Headers; -using MockHttp.Http; +using MockHttp.Http; namespace MockHttp.Responses; @@ -57,10 +56,12 @@ private static void Add(KeyValuePair> header, HttpR // Special case handling of headers which only allow single values. if (HeadersWithSingleValueOnly.Contains(header.Key)) { + // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract if (responseMessage.Content?.Headers.TryGetValues(header.Key, out _) == true) { responseMessage.Content.Headers.Remove(header.Key); } + if (responseMessage.Headers.TryGetValues(header.Key, out _)) { responseMessage.Headers.Remove(header.Key); @@ -68,27 +69,10 @@ private static void Add(KeyValuePair> header, HttpR } // Try to add as message header first, if that fails, add as content header. - if (!TryAdd(responseMessage.Headers, header.Key, header.Value)) + // Let it throw if not supported. + if (!responseMessage.Headers.TryAddWithoutValidation(header.Key, header.Value)) { responseMessage.Content?.Headers.Add(header.Key, header.Value); } } - - private static bool TryAdd(HttpHeaders? headers, string name, IEnumerable values) - { - if (headers is null) - { - return false; - } - - try - { - headers.Add(name, values); - return true; - } - catch (Exception) - { - return false; - } - } } diff --git a/test/MockHttp.Json.Tests/ArgAny.cs b/test/MockHttp.Json.Tests/ArgAny.cs new file mode 100644 index 00000000..9d693007 --- /dev/null +++ b/test/MockHttp.Json.Tests/ArgAny.cs @@ -0,0 +1,13 @@ +namespace MockHttp.Json; + +/// +/// To deal with runtime API differences around mocking with nullable. +/// +internal static class ArgAny +{ +#if NETCOREAPP3_1_OR_GREATER + public static ref string? String() => ref Arg.Any(); +#else + public static ref string String() => ref Arg.Any(); +#endif +} diff --git a/test/MockHttp.Json.Tests/JsonContentMatcherTests.cs b/test/MockHttp.Json.Tests/JsonContentMatcherTests.cs index dc1ba740..25fb1c7f 100644 --- a/test/MockHttp.Json.Tests/JsonContentMatcherTests.cs +++ b/test/MockHttp.Json.Tests/JsonContentMatcherTests.cs @@ -16,7 +16,7 @@ public JsonContentMatcherTests() _equalityComparerMock = Substitute.For>(); _equalityComparerMock - .Equals(Arg.Any(), Arg.Any()) + .Equals(ArgAny.String(), ArgAny.String()) .Returns(true); _requestMessage = new HttpRequestMessage(); @@ -43,7 +43,7 @@ public async Task Given_that_adapter_is_provided_to_ctor_when_matching_it_should // Assert _adapterMock.Received(1).Serialize(jsonContentAsObject); globalAdapterMock.DidNotReceiveWithAnyArgs().Serialize(Arg.Any()); - _ = _equalityComparerMock.Received(1).Equals(Arg.Any(), serializedJson); + _ = _equalityComparerMock.Received(1).Equals(ArgAny.String(), serializedJson); } [Fact] @@ -65,7 +65,7 @@ public async Task Given_that_adapter_is_not_provided_to_ctor_when_matching_it_sh // Assert globalAdapterMock.Received(1).Serialize(jsonContentAsObject); - _ = _equalityComparerMock.Received(1).Equals(Arg.Any(), serializedJson); + _ = _equalityComparerMock.Received(1).Equals(ArgAny.String(), serializedJson); } [Fact] @@ -80,7 +80,7 @@ public async Task Given_that_adapter_is_not_provided_to_ctor_and_no_global_adapt await sut.IsMatchAsync(_requestContext); // Assert - _ = _equalityComparerMock.Received(1).Equals(Arg.Any(), serializedJson); + _ = _equalityComparerMock.Received(1).Equals(ArgAny.String(), serializedJson); } [Fact] @@ -104,14 +104,14 @@ public async Task When_matching_it_should_return_the_results_of_the_comparer(boo var sut = new JsonContentMatcher("something to compare with", _adapterMock, _equalityComparerMock); _equalityComparerMock - .Equals(Arg.Any(), Arg.Any()) + .Equals(ArgAny.String(), ArgAny.String()) .Returns(equals); // Act bool actual = await sut.IsMatchAsync(_requestContext); // Assert - _ =_equalityComparerMock.Received(1).Equals(Arg.Any(), Arg.Any()); + _ =_equalityComparerMock.Received(1).Equals(ArgAny.String(), ArgAny.String()); actual.Should().Be(equals); } @@ -131,7 +131,7 @@ HttpContent content await sut.IsMatchAsync(_requestContext); // Assert - _ = _equalityComparerMock.Received(1).Equals(string.Empty, Arg.Any()); + _ = _equalityComparerMock.Received(1).Equals(string.Empty, ArgAny.String()); } } @@ -169,6 +169,7 @@ public static IEnumerable JsonMatchTestCases() public void Dispose() { + // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract _requestMessage?.Dispose(); } } diff --git a/test/MockHttp.Tests/Language/Flow/Response/HeaderSpec.cs b/test/MockHttp.Tests/Language/Flow/Response/HeaderSpec.cs index 3ffe9beb..cd10bfdd 100644 --- a/test/MockHttp.Tests/Language/Flow/Response/HeaderSpec.cs +++ b/test/MockHttp.Tests/Language/Flow/Response/HeaderSpec.cs @@ -6,14 +6,8 @@ namespace MockHttp.Language.Flow.Response; public class HeaderSpec : ResponseSpec { - private readonly DateTimeOffset _utcNow; - private readonly DateTime _now; - - public HeaderSpec() - { - _utcNow = DateTimeOffset.UtcNow; - _now = DateTime.Now; - } + private readonly DateTimeOffset _utcNow = DateTimeOffset.UtcNow; + private readonly DateTime _now = DateTime.Now; protected override void Given(IResponseBuilder with) { @@ -42,6 +36,8 @@ protected override Task Should(HttpResponseMessage response) .And.HaveHeader("X-Date", new[] { _now.AddYears(-1).ToString("R"), _now.ToString("R") }) .And.HaveHeader("X-Empty", string.Empty) .And.HaveHeader("X-Null", string.Empty); + response.Headers.Count().Should().Be(5); + response.Content.Headers.Count().Should().Be(2); return Task.CompletedTask; } }