From 494642e84409a8bc5d4b2ec58b4d8480b259f08a Mon Sep 17 00:00:00 2001 From: skwasjer <11424653+skwasjer@users.noreply.github.com> Date: Sat, 18 Nov 2023 11:24:14 +0100 Subject: [PATCH 1/5] fix(CS8604): in older runtimes, IEqualityComparer<> did not use nullable annotation. Fix with string polyfill for Arg.Any. --- test/MockHttp.Json.Tests/ArgAny.cs | 13 +++++++++++++ .../JsonContentMatcherTests.cs | 15 ++++++++------- 2 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 test/MockHttp.Json.Tests/ArgAny.cs 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(); } } From baac5b90d43d9ad616329aa715fab461746dd569 Mon Sep 17 00:00:00 2001 From: skwasjer <11424653+skwasjer@users.noreply.github.com> Date: Sat, 18 Nov 2023 11:26:16 +0100 Subject: [PATCH 2/5] fix(CA2007): use `ConfigureAwait` on awaitable async stream --- .../Server/HttpResponseMessageExtensions.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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); } } From 622a6bfbe3ec582e4ac4b7976c6b0833ffad365b Mon Sep 17 00:00:00 2001 From: skwasjer <11424653+skwasjer@users.noreply.github.com> Date: Sat, 18 Nov 2023 11:43:28 +0100 Subject: [PATCH 3/5] refactor(CA1031): use `TryAddWithoutValidation` to avoid exceptions and to skip adding header if not supported. --- src/MockHttp/Responses/HttpHeaderBehavior.cs | 26 ++++--------------- .../Language/Flow/Response/HeaderSpec.cs | 12 +++------ 2 files changed, 9 insertions(+), 29 deletions(-) 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.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; } } From 1770c6291013cd7b1d137933217b52b74eb88ffb Mon Sep 17 00:00:00 2001 From: skwasjer <11424653+skwasjer@users.noreply.github.com> Date: Sat, 18 Nov 2023 11:58:00 +0100 Subject: [PATCH 4/5] fix(SYSLIB0051): mark serializable/binary formatter support as obsolete --- src/MockHttp/HttpMockException.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/MockHttp/HttpMockException.cs b/src/MockHttp/HttpMockException.cs index 261a0a6d..9f0af5c2 100644 --- a/src/MockHttp/HttpMockException.cs +++ b/src/MockHttp/HttpMockException.cs @@ -35,6 +35,9 @@ internal HttpMockException(string message, Exception innerException) : base(mess /// [ExcludeFromCodeCoverage] +#if NET8_0_OR_GREATER + [Obsolete(DiagnosticId = "SYSLIB0051")] +#endif protected HttpMockException(SerializationInfo info, StreamingContext context) : base(info, context) { From 5702763c41438d275ef2d22ee0adba1cecf9e2ad Mon Sep 17 00:00:00 2001 From: skwasjer <11424653+skwasjer@users.noreply.github.com> Date: Sat, 18 Nov 2023 12:03:13 +0100 Subject: [PATCH 5/5] refactor(CA2208): rather, catch the invalid enum value in ctor. --- src/MockHttp/Http/HttpHeaderEqualityComparer.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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