From 8fe520f9490a8a775e30d9977cfd9678854ba4e3 Mon Sep 17 00:00:00 2001 From: Kambiz Khojasteh Date: Sun, 21 Dec 2025 20:02:03 +0800 Subject: [PATCH 1/5] Refactor error handling; add transient handler & base class - Introduce RetryableHttpErrorHandler abstract base for transient error handling - Add TransientHttpErrorHandler for multiple status codes - Refactor HttpError429Handler and HttpError503Handler to use new base class - Update project versions to 2.5.0 and copyright to 2025 - Relax Newtonsoft.Json and System.Text.Json package versions - Improve HttpRequestMessageCloneManager disposal logic - Minor variable renaming and add namespace documentation stub --- .../Kampute.HttpClient.DataContract.csproj | 4 +- .../Kampute.HttpClient.Json.csproj | 2 +- .../Kampute.HttpClient.NewtonsoftJson.csproj | 6 +- .../Kampute.HttpClient.Xml.csproj | 4 +- .../ErrorHandlers/Abstracts/NamespaceDoc.cs | 13 ++ .../Abstracts/RetryableHttpErrorHandler.cs | 125 ++++++++++++++++++ .../ErrorHandlers/HttpError429Handler.cs | 82 +++--------- .../ErrorHandlers/HttpError503Handler.cs | 95 +++---------- .../TransientHttpErrorHandler.cs | 77 +++++++++++ .../HttpRequestMessageCloneManager.cs | 13 +- src/Kampute.HttpClient/HttpRestClient.cs | 4 +- .../Kampute.HttpClient.csproj | 4 +- 12 files changed, 264 insertions(+), 165 deletions(-) create mode 100644 src/Kampute.HttpClient/ErrorHandlers/Abstracts/NamespaceDoc.cs create mode 100644 src/Kampute.HttpClient/ErrorHandlers/Abstracts/RetryableHttpErrorHandler.cs create mode 100644 src/Kampute.HttpClient/ErrorHandlers/TransientHttpErrorHandler.cs diff --git a/src/Kampute.HttpClient.DataContract/Kampute.HttpClient.DataContract.csproj b/src/Kampute.HttpClient.DataContract/Kampute.HttpClient.DataContract.csproj index d2d3320..6619227 100644 --- a/src/Kampute.HttpClient.DataContract/Kampute.HttpClient.DataContract.csproj +++ b/src/Kampute.HttpClient.DataContract/Kampute.HttpClient.DataContract.csproj @@ -5,9 +5,9 @@ Kampute.HttpClient.DataContract This package is an extension package for Kampute.HttpClient, enhancing it to manage application/xml content types, using DataContractSerializer for serialization and deserialization of XML responses and payloads. Kambiz Khojasteh - 2.4.0 + 2.5.0 Kampute - Copyright (c) 2024 Kampute + Copyright (c) 2025 Kampute latest enable true diff --git a/src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj b/src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj index 6fdaa68..573eb84 100644 --- a/src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj +++ b/src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj @@ -36,7 +36,7 @@ - + diff --git a/src/Kampute.HttpClient.NewtonsoftJson/Kampute.HttpClient.NewtonsoftJson.csproj b/src/Kampute.HttpClient.NewtonsoftJson/Kampute.HttpClient.NewtonsoftJson.csproj index 5d3f663..a83bf10 100644 --- a/src/Kampute.HttpClient.NewtonsoftJson/Kampute.HttpClient.NewtonsoftJson.csproj +++ b/src/Kampute.HttpClient.NewtonsoftJson/Kampute.HttpClient.NewtonsoftJson.csproj @@ -5,9 +5,9 @@ Kampute.HttpClient.NewtonsoftJson This package is an extension package for Kampute.HttpClient, enhancing it to manage application/json content types, using Newtonsoft.Json library for serialization and deserialization of JSON responses and payloads. Kambiz Khojasteh - 2.4.0 + 2.5.0 Kampute - Copyright (c) 2024 Kampute + Copyright (c) 2025 Kampute latest enable true @@ -36,7 +36,7 @@ - + diff --git a/src/Kampute.HttpClient.Xml/Kampute.HttpClient.Xml.csproj b/src/Kampute.HttpClient.Xml/Kampute.HttpClient.Xml.csproj index 3bacaad..b0f9b0b 100644 --- a/src/Kampute.HttpClient.Xml/Kampute.HttpClient.Xml.csproj +++ b/src/Kampute.HttpClient.Xml/Kampute.HttpClient.Xml.csproj @@ -5,9 +5,9 @@ Kampute.HttpClient.Xml This package is an extension package for Kampute.HttpClient, enhancing it to manage application/xml content types, using XmlSerializer for serialization and deserialization of XML responses and payloads. Kambiz Khojasteh - 2.4.0 + 2.5.0 Kampute - Copyright (c) 2024 Kampute + Copyright (c) 2025 Kampute latest enable true diff --git a/src/Kampute.HttpClient/ErrorHandlers/Abstracts/NamespaceDoc.cs b/src/Kampute.HttpClient/ErrorHandlers/Abstracts/NamespaceDoc.cs new file mode 100644 index 0000000..5f2e554 --- /dev/null +++ b/src/Kampute.HttpClient/ErrorHandlers/Abstracts/NamespaceDoc.cs @@ -0,0 +1,13 @@ +// Copyright (C) 2024 Kampute +// +// This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. +// See the LICENSE file in the project root for the full license text. + +namespace Kampute.HttpClient.ErrorHandlers.Abstracts +{ + /// + /// This namespace contains abstract classes for handling transient HTTP error responses by implementing + /// backoff and retry strategies. + /// + internal static class NamespaceDoc { } +} diff --git a/src/Kampute.HttpClient/ErrorHandlers/Abstracts/RetryableHttpErrorHandler.cs b/src/Kampute.HttpClient/ErrorHandlers/Abstracts/RetryableHttpErrorHandler.cs new file mode 100644 index 0000000..087eda3 --- /dev/null +++ b/src/Kampute.HttpClient/ErrorHandlers/Abstracts/RetryableHttpErrorHandler.cs @@ -0,0 +1,125 @@ +// Copyright (C) 2024 Kampute +// +// This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. +// See the LICENSE file in the project root for the full license text. + +namespace Kampute.HttpClient.ErrorHandlers.Abstracts +{ + using Kampute.HttpClient.Interfaces; + using System; + using System.Net; + using System.Threading; + using System.Threading.Tasks; + + /// + /// Provides the base functionality for handling HTTP responses with transient error status codes by attempting to back off and + /// retry the request according to a specified or default backoff strategy. + /// + /// + /// This handler class is designed to be extended for specific transient error status codes. It offers a mechanism to respond to + /// transient HTTP errors by retrying the request after a delay. The delay duration and retry logic can be customized through the + /// delegate. + /// + /// + /// + public abstract class RetryableHttpErrorHandler : IHttpErrorHandler + { + /// + /// A delegate that allows customization of the backoff strategy when responses with transient error status codes are received. + /// + /// + /// A function that takes an and an optional representing + /// the suggested retry time, and returns an to be used for the retry operation. + /// + /// + /// + /// If this delegate is set and returns an , the returned strategy is used for the retry operation. + /// If it is not set, or returns , a default behavior is applied. + /// + /// + /// The delegate receives the following parameters: + /// + /// + /// context + /// + /// Provides context about the HTTP response that indicates a transient error. It is encapsulated within an + /// instance, allowing for an informed decision on the retry strategy. + /// + /// + /// + /// retryTime + /// + /// Advises on the next retry attempt timing as a value if the response suggests one. If the response + /// does not include a suggested retry time, the value will be . + /// + /// + /// + /// + /// + public Func? OnBackoffStrategy { get; set; } + + /// + /// Determines whether this handler can process the specified HTTP status code. + /// + /// The HTTP status code to evaluate. + /// if the handler can process the status code; otherwise, . + public abstract bool CanHandle(HttpStatusCode statusCode); + + /// + /// Extracts the suggested retry time from the HTTP response's header, if present. + /// + /// The context containing information about the HTTP response. + /// The suggested to retry the request, or if the header is not present. + /// Thrown if is . + protected virtual DateTimeOffset? GetSuggestedRetryTime(HttpResponseErrorContext ctx) + { + if (ctx is null) + throw new ArgumentNullException(nameof(ctx)); + + ctx.Response.Headers.TryExtractRetryAfterTime(out var retryTime); + return retryTime; + } + + /// + /// Provides the default backoff strategy when no custom strategy is specified. + /// + /// The context containing information about the HTTP response. + /// The suggested retry time, if any. + /// An representing the default backoff strategy. + /// Thrown if is . + protected virtual IHttpBackoffProvider GetDefaultStrategy(HttpResponseErrorContext ctx, DateTimeOffset? retryTime) + { + if (ctx is null) + throw new ArgumentNullException(nameof(ctx)); + + return retryTime.HasValue ? BackoffStrategies.Once(retryTime.Value) : ctx.Client.BackoffStrategy; + } + + /// + /// Creates a scheduler for retrying the failed request based on the error context. + /// + /// The context containing information about the HTTP response that indicates a failure. + /// An that schedules the retry attempts. + /// Thrown if is . + /// + /// The method uses when available. If the delegate is not provided or returns + /// , and the response includes a suggested retry time, a single retry at that time is used. + /// Otherwise the client's default backoff strategy is used. + /// + protected virtual IRetryScheduler? CreateScheduler(HttpResponseErrorContext ctx) + { + if (ctx is null) + throw new ArgumentNullException(nameof(ctx)); + + var retryTime = GetSuggestedRetryTime(ctx); + var strategy = OnBackoffStrategy?.Invoke(ctx, retryTime) ?? GetDefaultStrategy(ctx, retryTime); + return strategy.CreateScheduler(ctx); + } + + /// + Task IHttpErrorHandler.DecideOnRetryAsync(HttpResponseErrorContext ctx, CancellationToken cancellationToken) + { + return ctx.ScheduleRetryAsync(CreateScheduler, cancellationToken); + } + } +} diff --git a/src/Kampute.HttpClient/ErrorHandlers/HttpError429Handler.cs b/src/Kampute.HttpClient/ErrorHandlers/HttpError429Handler.cs index b0ae86c..231c830 100644 --- a/src/Kampute.HttpClient/ErrorHandlers/HttpError429Handler.cs +++ b/src/Kampute.HttpClient/ErrorHandlers/HttpError429Handler.cs @@ -5,11 +5,10 @@ namespace Kampute.HttpClient.ErrorHandlers { + using Kampute.HttpClient.ErrorHandlers.Abstracts; using Kampute.HttpClient.Interfaces; using System; using System.Net; - using System.Threading; - using System.Threading.Tasks; /// /// Handles '429 Too Many Requests' HTTP responses by attempting to back off and retry the request according to a specified or @@ -17,92 +16,41 @@ namespace Kampute.HttpClient.ErrorHandlers /// /// /// This handler provides a mechanism to respond to HTTP 429 errors by retrying the request after a delay. The delay duration and - /// retry logic can be customized through the delegate. If the delegate is not provided, or does not - /// specify a strategy, the handler will look for a rate limit reset header in the response. If the header is present, its value is - /// used to determine the backoff duration. If the header is not present, no retries will be attempted. + /// retry logic can be customized through the delegate. If the delegate + /// is not provided, or does not specify a strategy, the handler will look for a rate limit reset header in the response. If the + /// header is present, its value is used to determine the backoff duration. If the header is not present, no retries will be attempted. /// /// - public class HttpError429Handler : IHttpErrorHandler + public class HttpError429Handler : RetryableHttpErrorHandler { - /// - /// A delegate that allows customization of the backoff strategy when a 429 Too Many Requests' response is received. - /// - /// - /// A function that takes an and an optional representing - /// the rate limit reset time, and returns an to define the backoff strategy. - /// - /// - /// - /// If this delegate is set and returns an , the returned strategy is used for the retry operation. - /// If it is not set, or returns , the handler will defer to the Retry-After header in the response. - /// - /// - /// The delegate receives the following parameters: - /// - /// - /// context - /// - /// Provides context about the HTTP response indicating a '429 Too Many Requests' error. It is encapsulated within - /// an instance, allowing for an informed decision on the retry strategy. - /// - /// - /// - /// resetTime - /// - /// Indicates the time when the rate limit will be lifted as a value. If the server specifies - /// a reset time via response headers, this parameter provides that time, allowing the client to know when to resume requests. - /// If the server does not specify a reset time, the value will be . - /// - /// - /// - /// - /// - public Func? OnBackoffStrategy { get; set; } - - /// - /// Determines whether this handler can process the specified HTTP status code. - /// - /// The HTTP status code to evaluate. - /// if the handler can process the status code; otherwise, . + /// /// /// This implementation specifically handles the HTTP '429 Too Many Requests' status code. /// - public bool CanHandle(HttpStatusCode statusCode) => + public sealed override bool CanHandle(HttpStatusCode statusCode) => #if NETSTANDARD2_1_OR_GREATER statusCode == HttpStatusCode.TooManyRequests; #else statusCode == (HttpStatusCode)429; #endif - /// - /// Creates a scheduler for retrying the failed request based on the error context. - /// - /// The context containing information about the HTTP response that indicates a failure. - /// An that schedules the retry attempts. - /// Thrown if is . - /// - /// This method first attempts to use the delegate to obtain a retry strategy. If the delegate is not - /// provided or returns , and a rate limit reset header is present, the value of this header is used to create a retry delay. - /// If neither condition is met, no retries will be attempted. - /// - protected virtual IRetryScheduler? CreateScheduler(HttpResponseErrorContext ctx) + /// + protected override DateTimeOffset? GetSuggestedRetryTime(HttpResponseErrorContext ctx) { if (ctx is null) throw new ArgumentNullException(nameof(ctx)); ctx.Response.Headers.TryExtractRateLimitResetTime(out var resetTime); - - var strategy = OnBackoffStrategy?.Invoke(ctx, resetTime); - if (strategy is null && resetTime is DateTimeOffset retryAfter) - strategy = BackoffStrategies.Once(retryAfter); - - return strategy?.CreateScheduler(ctx); + return resetTime; } /// - Task IHttpErrorHandler.DecideOnRetryAsync(HttpResponseErrorContext ctx, CancellationToken cancellationToken) + protected override IHttpBackoffProvider GetDefaultStrategy(HttpResponseErrorContext ctx, DateTimeOffset? retryTime) { - return ctx.ScheduleRetryAsync(CreateScheduler, cancellationToken); + if (ctx is null) + throw new ArgumentNullException(nameof(ctx)); + + return retryTime.HasValue ? BackoffStrategies.Once(retryTime.Value) : BackoffStrategies.None; } } } diff --git a/src/Kampute.HttpClient/ErrorHandlers/HttpError503Handler.cs b/src/Kampute.HttpClient/ErrorHandlers/HttpError503Handler.cs index 4c1ae5e..e200e6d 100644 --- a/src/Kampute.HttpClient/ErrorHandlers/HttpError503Handler.cs +++ b/src/Kampute.HttpClient/ErrorHandlers/HttpError503Handler.cs @@ -5,100 +5,35 @@ namespace Kampute.HttpClient.ErrorHandlers { - using Kampute.HttpClient.Interfaces; - using System; + using Kampute.HttpClient.ErrorHandlers.Abstracts; using System.Net; - using System.Threading; - using System.Threading.Tasks; /// /// Handles '503 Service Unavailable' HTTP responses by attempting to back off and retry the request according to a specified or /// default backoff strategy. /// /// + /// /// This handler provides a mechanism to respond to HTTP 503 errors by retrying the request after a delay. The delay duration and - /// retry logic can be customized through the delegate. If the delegate is not provided, or does not - /// specify a strategy, the handler will look for a Retry-After header in the response. If the Retry-After header is - /// present, its value is used to determine the backoff duration. If the header is not present, the default backoff strategy of the - /// is used. + /// retry logic can be customized through the delegate. If the delegate + /// is not provided, or does not specify a strategy, the handler will look for a Retry-After header in the response. If the + /// Retry-After header is present, its value is used to determine the backoff duration. If the header is not present, the + /// default backoff strategy of the is used. + /// + /// + /// Consider using if you want to handle multiple transient HTTP errors (including 503) with + /// a single handler. + /// /// /// /// - public class HttpError503Handler : IHttpErrorHandler + /// + public class HttpError503Handler : RetryableHttpErrorHandler { - /// - /// A delegate that allows customization of the backoff strategy when a '503 Service Unavailable' response is received. - /// - /// - /// A function that takes an and an optional representing - /// the suggested retry time from the Retry-After header, and returns an to be used - /// for the retry operation. - /// - /// - /// - /// If this delegate is set and returns an , the returned strategy is used for the retry operation. - /// If it is not set, or returns , the handler will defer to the Retry-After header in the response or the - /// client's default backoff strategy. - /// - /// - /// The delegate receives the following parameters: - /// - /// - /// context - /// - /// Provides context about the HTTP response indicating a '503 Service Unavailable' error. It is encapsulated within - /// an instance, allowing for an informed decision on the retry strategy. - /// - /// - /// - /// retryAfter - /// - /// Advises on the next retry attempt timing as a value. If the response includes a Retry-After - /// header, this parameter reflects its value, suggesting an optimal time to retry. If the header is missing, the value is , - /// indicating no specific suggestion from the server. - /// - /// - /// - /// - /// - public Func? OnBackoffStrategy { get; set; } - - /// - /// Determines whether this handler can process the specified HTTP status code. - /// - /// The HTTP status code to evaluate. - /// if the handler can process the status code; otherwise, . + /// /// /// This implementation specifically handles the HTTP '503 Service Unavailable' status code. /// - public virtual bool CanHandle(HttpStatusCode statusCode) => statusCode == HttpStatusCode.ServiceUnavailable; - - /// - /// Creates a scheduler for retrying the failed request based on the error context. - /// - /// The context containing information about the HTTP response that indicates a failure. - /// An that schedules the retry attempts. - /// Thrown if is . - /// - /// This method first attempts to use the delegate to obtain a retry strategy. If the delegate is not - /// provided or returns , and a Retry-After header is present, the value of this header is used to create a retry - /// delay. If neither condition is met, the client's default backoff strategy is utilized. - /// - protected virtual IRetryScheduler? CreateScheduler(HttpResponseErrorContext ctx) - { - if (ctx is null) - throw new ArgumentNullException(nameof(ctx)); - - ctx.Response.Headers.TryExtractRetryAfterTime(out var retryAfter); - - var strategy = OnBackoffStrategy?.Invoke(ctx, retryAfter) ?? (retryAfter.HasValue ? BackoffStrategies.Once(retryAfter.Value) : ctx.Client.BackoffStrategy); - return strategy.CreateScheduler(ctx); - } - - /// - Task IHttpErrorHandler.DecideOnRetryAsync(HttpResponseErrorContext ctx, CancellationToken cancellationToken) - { - return ctx.ScheduleRetryAsync(CreateScheduler, cancellationToken); - } + public sealed override bool CanHandle(HttpStatusCode statusCode) => statusCode == HttpStatusCode.ServiceUnavailable; } } diff --git a/src/Kampute.HttpClient/ErrorHandlers/TransientHttpErrorHandler.cs b/src/Kampute.HttpClient/ErrorHandlers/TransientHttpErrorHandler.cs new file mode 100644 index 0000000..6632176 --- /dev/null +++ b/src/Kampute.HttpClient/ErrorHandlers/TransientHttpErrorHandler.cs @@ -0,0 +1,77 @@ +// Copyright (C) 2024 Kampute +// +// This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. +// See the LICENSE file in the project root for the full license text. + +namespace Kampute.HttpClient.ErrorHandlers +{ + using Kampute.HttpClient.ErrorHandlers.Abstracts; + using System; + using System.Collections.Generic; + using System.Net; + + /// + /// Handles HTTP responses with a transient error status code by attempting to back off and retry the request according to a specified + /// or default backoff strategy. + /// + /// + /// + public class TransientHttpErrorHandler : RetryableHttpErrorHandler + { + private readonly HashSet _handledStatusCodes; + + /// + /// Initializes a new instance of the class with default transient error status codes. + /// + /// + /// The default transient error status codes handled by this instance are: + /// + /// 408Request Timeout + /// 502Bad Gateway + /// 503Service Unavailable + /// 504Gateway Timeout + /// 507Insufficient Storage + /// 509Bandwidth Limit Exceeded + /// + /// + public TransientHttpErrorHandler() + { + _handledStatusCodes = + [ + HttpStatusCode.RequestTimeout, // 408 - Request Timeout + HttpStatusCode.BadGateway, // 502 - Bad Gateway + HttpStatusCode.ServiceUnavailable, // 503 - Service Unavailable + HttpStatusCode.GatewayTimeout, // 504 - Gateway Timeout + (HttpStatusCode) 507, // 507 - Insufficient Storage + (HttpStatusCode) 509, // 509 - Bandwidth Limit Exceeded + ]; + } + + /// + /// Initializes a new instance of the class with specified transient error status codes. + /// + /// The collection of HTTP status codes that this handler will process as transient errors. + /// Thrown if is . + /// Thrown if is empty. + public TransientHttpErrorHandler(IEnumerable statusCodes) + { + if (statusCodes is null) + throw new ArgumentNullException(nameof(statusCodes)); + + _handledStatusCodes = [.. statusCodes]; + if (_handledStatusCodes.Count == 0) + throw new ArgumentException("At least one status code must be provided.", nameof(statusCodes)); + } + + /// + /// Gets the collection of HTTP status codes that are considered transient errors and handled by this instance. + /// + /// + /// A read-only collection of values that are treated as transient errors. + /// + public IReadOnlyCollection HandledStatusCodes => _handledStatusCodes; + + /// + public sealed override bool CanHandle(HttpStatusCode statusCode) => _handledStatusCodes.Contains(statusCode); + } +} diff --git a/src/Kampute.HttpClient/HttpRequestMessageCloneManager.cs b/src/Kampute.HttpClient/HttpRequestMessageCloneManager.cs index 64863a9..4ae7e6a 100644 --- a/src/Kampute.HttpClient/HttpRequestMessageCloneManager.cs +++ b/src/Kampute.HttpClient/HttpRequestMessageCloneManager.cs @@ -70,12 +70,13 @@ public readonly void Dispose() [MethodImpl(MethodImplOptions.AggressiveInlining)] private readonly void DisposeNonOriginalRequest() { - if (!ReferenceEquals(_currentRequest, _originalRequest)) - { - if (_currentRequest.IsCloned()) - _currentRequest.Content = null; // Content is reused, not cloned. - _currentRequest.Dispose(); - } + if (ReferenceEquals(_currentRequest, _originalRequest)) + return; + + if (_currentRequest.IsCloned()) + _currentRequest.Content = null; // Content is reused, not cloned. + + _currentRequest.Dispose(); } } } diff --git a/src/Kampute.HttpClient/HttpRestClient.cs b/src/Kampute.HttpClient/HttpRestClient.cs index 9c1fa6c..db6d1fa 100644 --- a/src/Kampute.HttpClient/HttpRestClient.cs +++ b/src/Kampute.HttpClient/HttpRestClient.cs @@ -484,11 +484,11 @@ CancellationToken cancellationToken if (response is null) throw new ArgumentNullException(nameof(response)); - var context = new HttpResponseErrorContext(this, request, response, error); + var ctx = new HttpResponseErrorContext(this, request, response, error); foreach (var errorHandler in ErrorHandlers.GetHandlersFor(response.StatusCode)) { - var decision = await errorHandler.DecideOnRetryAsync(context, cancellationToken).ConfigureAwait(false); + var decision = await errorHandler.DecideOnRetryAsync(ctx, cancellationToken).ConfigureAwait(false); if (decision.RequestToRetry is not null) { decision.RequestToRetry.Properties[HttpRequestMessagePropertyKeys.ErrorHandler] = errorHandler; diff --git a/src/Kampute.HttpClient/Kampute.HttpClient.csproj b/src/Kampute.HttpClient/Kampute.HttpClient.csproj index 6f0139b..38fb281 100644 --- a/src/Kampute.HttpClient/Kampute.HttpClient.csproj +++ b/src/Kampute.HttpClient/Kampute.HttpClient.csproj @@ -5,9 +5,9 @@ Kampute.HttpClient Kampute.HttpClient is a versatile and lightweight .NET library that simplifies RESTful API communication. Its core HttpRestClient class provides a streamlined approach to HTTP interactions, offering advanced features such as flexible serialization/deserialization, robust error handling, configurable backoff strategies, and detailed request-response processing. Striking a balance between simplicity and extensibility, Kampute.HttpClient empowers developers with a powerful yet easy-to-use client for seamless API integration across a wide range of .NET applications. Kambiz Khojasteh - 2.4.0 + 2.5.0 Kampute - Copyright (c) 2024 Kampute + Copyright (c) 2025 Kampute latest enable true From a89c70bff4162f9efac185117074bbcd0b0683d9 Mon Sep 17 00:00:00 2001 From: Kambiz Khojasteh Date: Sun, 21 Dec 2025 20:02:59 +0800 Subject: [PATCH 2/5] Update tests for .NET 10, NUnit 4.4, and new APIs - Target .NET 10.0 in all test projects; update NUnit3TestAdapter to 6.0.1 - Replace deprecated Assert.Multiple with Assert.EnterMultipleScope - Use C# 12 collection expressions in assertions - Add TransientHttpErrorHandlerTests for coverage of transient error handling and backoff logic - Introduce TestStream helper for seekable/non-seekable stream tests - Refactor tests to use TestStream for content reusability checks - Add tests for compressed content retry scenarios (gzip/deflate) - Minor assertion improvements for clarity and consistency - Modernize and improve maintainability of the test suite --- .../HttpRestClientXmlExtensionsTests.cs | 12 +- ...ampute.HttpClient.DataContract.Test.csproj | 4 +- .../XmlContentTests.cs | 8 +- .../HttpRestClientJsonExtensionsTests.cs | 12 +- .../JsonContentTests.cs | 4 +- .../Kampute.HttpClient.Json.Test.csproj | 4 +- .../HttpRestClientJsonExtensionsTests.cs | 12 +- .../JsonContentTests.cs | 4 +- ...pute.HttpClient.NewtonsoftJson.Test.csproj | 4 +- .../DeflateCompressedContentTests.cs | 4 +- .../Compression/GzipCompressedContentTests.cs | 4 +- .../ErrorHandlers/HttpError401HandlerTests.cs | 8 +- .../ErrorHandlers/HttpError429HandlerTests.cs | 4 +- .../ErrorHandlers/HttpError503HandlerTests.cs | 8 +- .../TransientHttpErrorHandlerTests.cs | 187 ++++++++++++++++++ .../HttpContentDeserializerCollectionTests.cs | 4 +- .../HttpContentExtensionsTests.cs | 34 +++- .../HttpErrorHandlerCollectionTests.cs | 4 +- .../HttpRequestMessageExtensionsTests.cs | 24 +-- .../HttpRequestScopeTests.cs | 6 +- .../HttpResponseHeadersExtensionsTests.cs | 16 +- .../HttpRestClientExtensionsTests.cs | 76 +++---- .../HttpRestClientFormExtensionsTests.cs | 12 +- .../HttpRestClientTests.cs | 91 +++++++-- .../Kampute.HttpClient.Test.csproj | 4 +- .../RetryManagement/RetrySchedulerTests.cs | 12 +- .../Strategies/ExponentialStrategyTests.cs | 8 +- .../Strategies/FibonacciStrategyTests.cs | 16 +- .../Strategies/LinearStrategyTests.cs | 16 +- .../Modifiers/JitterStrategyModifierTests.cs | 8 +- .../LimitedAttemptsStrategyModifierTests.cs | 8 +- .../LimitedDurationStrategyModifierTests.cs | 8 +- .../Strategies/NoneStrategyTests.cs | 4 +- .../Strategies/UniformStrategyTests.cs | 8 +- .../TestHelpers/TestStream.cs | 18 ++ .../Utilities/AsyncUpdateThrottleTests.cs | 8 +- .../Utilities/FlyweightCacheTests.cs | 4 +- .../Utilities/ScopedCollectionTests.cs | 8 +- .../Utilities/SharedDisposableTests.cs | 22 +-- .../HttpRestClientXmlExtensionsTests.cs | 12 +- .../Kampute.HttpClient.Xml.Test.csproj | 4 +- .../XmlContentTests.cs | 8 +- 42 files changed, 497 insertions(+), 225 deletions(-) create mode 100644 tests/Kampute.HttpClient.Test/ErrorHandlers/TransientHttpErrorHandlerTests.cs create mode 100644 tests/Kampute.HttpClient.Test/TestHelpers/TestStream.cs diff --git a/tests/Kampute.HttpClient.DataContract.Test/HttpRestClientXmlExtensionsTests.cs b/tests/Kampute.HttpClient.DataContract.Test/HttpRestClientXmlExtensionsTests.cs index 4ac158f..e99faae 100644 --- a/tests/Kampute.HttpClient.DataContract.Test/HttpRestClientXmlExtensionsTests.cs +++ b/tests/Kampute.HttpClient.DataContract.Test/HttpRestClientXmlExtensionsTests.cs @@ -62,12 +62,12 @@ public async Task PostAsXmlAsync_InvokesHttpClientCorrectly() MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Post)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/echo"))); Assert.That(request.Content?.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.Xml)); - }); + } return new HttpResponseMessage { @@ -89,12 +89,12 @@ public async Task PutAsXmlAsync_InvokesHttpClientCorrectly() MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Put)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/echo"))); Assert.That(request.Content?.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.Xml)); - }); + } return new HttpResponseMessage { @@ -116,12 +116,12 @@ public async Task PatchAsXmlAsync_InvokesHttpClientCorrectly() MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Patch)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/echo"))); Assert.That(request.Content?.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.Xml)); - }); + } return new HttpResponseMessage { diff --git a/tests/Kampute.HttpClient.DataContract.Test/Kampute.HttpClient.DataContract.Test.csproj b/tests/Kampute.HttpClient.DataContract.Test/Kampute.HttpClient.DataContract.Test.csproj index e6eaa33..a686f69 100644 --- a/tests/Kampute.HttpClient.DataContract.Test/Kampute.HttpClient.DataContract.Test.csproj +++ b/tests/Kampute.HttpClient.DataContract.Test/Kampute.HttpClient.DataContract.Test.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 false true latest @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Kampute.HttpClient.DataContract.Test/XmlContentTests.cs b/tests/Kampute.HttpClient.DataContract.Test/XmlContentTests.cs index bfa06e7..5cf4bb6 100644 --- a/tests/Kampute.HttpClient.DataContract.Test/XmlContentTests.cs +++ b/tests/Kampute.HttpClient.DataContract.Test/XmlContentTests.cs @@ -17,12 +17,12 @@ public async Task WithDefaultEncoding_SetsContentCorrectly() var xmlString = await xmlContent.ReadAsStringAsync(); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(xmlString, Is.EqualTo(expectedString)); Assert.That(xmlContent.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.Xml)); Assert.That(xmlContent.Headers.ContentType?.CharSet, Is.EqualTo(Encoding.UTF8.WebName)); - }); + } } [Test] @@ -36,12 +36,12 @@ public async Task WithCustomEncoding_SetsContentCorrectly() var xmlString = await xmlContent.ReadAsStringAsync(); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(xmlString, Is.EqualTo(expectedString)); Assert.That(xmlContent.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.Xml)); Assert.That(xmlContent.Headers.ContentType?.CharSet, Is.EqualTo(encoding.WebName)); - }); + } } } } diff --git a/tests/Kampute.HttpClient.Json.Test/HttpRestClientJsonExtensionsTests.cs b/tests/Kampute.HttpClient.Json.Test/HttpRestClientJsonExtensionsTests.cs index 7256a06..71fa5e6 100644 --- a/tests/Kampute.HttpClient.Json.Test/HttpRestClientJsonExtensionsTests.cs +++ b/tests/Kampute.HttpClient.Json.Test/HttpRestClientJsonExtensionsTests.cs @@ -63,12 +63,12 @@ public async Task PostAsJsonAsync_InvokesHttpClientCorrectly() MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Post)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/echo"))); Assert.That(request.Content?.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.Json)); - }); + } return new HttpResponseMessage { @@ -90,12 +90,12 @@ public async Task PutAsJsonAsync_InvokesHttpClientCorrectly() MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Put)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/echo"))); Assert.That(request.Content?.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.Json)); - }); + } return new HttpResponseMessage { @@ -117,12 +117,12 @@ public async Task PatchAsJsonAsync_InvokesHttpClientCorrectly() MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Patch)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/echo"))); Assert.That(request.Content?.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.Json)); - }); + } return new HttpResponseMessage { diff --git a/tests/Kampute.HttpClient.Json.Test/JsonContentTests.cs b/tests/Kampute.HttpClient.Json.Test/JsonContentTests.cs index 58b10aa..6c508d0 100644 --- a/tests/Kampute.HttpClient.Json.Test/JsonContentTests.cs +++ b/tests/Kampute.HttpClient.Json.Test/JsonContentTests.cs @@ -17,12 +17,12 @@ public async Task SetsContentCorrectly() var jsonString = await jsonContent.ReadAsStringAsync(); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(jsonString, Is.EqualTo(expectedString)); Assert.That(jsonContent.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.Json)); Assert.That(jsonContent.Headers.ContentType?.CharSet, Is.EqualTo(Encoding.UTF8.WebName)); - }); + } } } } diff --git a/tests/Kampute.HttpClient.Json.Test/Kampute.HttpClient.Json.Test.csproj b/tests/Kampute.HttpClient.Json.Test/Kampute.HttpClient.Json.Test.csproj index eea2a71..f223b5b 100644 --- a/tests/Kampute.HttpClient.Json.Test/Kampute.HttpClient.Json.Test.csproj +++ b/tests/Kampute.HttpClient.Json.Test/Kampute.HttpClient.Json.Test.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 false true latest @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Kampute.HttpClient.NewtonsoftJson.Test/HttpRestClientJsonExtensionsTests.cs b/tests/Kampute.HttpClient.NewtonsoftJson.Test/HttpRestClientJsonExtensionsTests.cs index d364649..2ca5cce 100644 --- a/tests/Kampute.HttpClient.NewtonsoftJson.Test/HttpRestClientJsonExtensionsTests.cs +++ b/tests/Kampute.HttpClient.NewtonsoftJson.Test/HttpRestClientJsonExtensionsTests.cs @@ -63,12 +63,12 @@ public async Task PostAsJsonAsync_InvokesHttpClientCorrectly() MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Post)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/echo"))); Assert.That(request.Content?.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.Json)); - }); + } return new HttpResponseMessage { @@ -90,12 +90,12 @@ public async Task PutAsJsonAsync_InvokesHttpClientCorrectly() MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Put)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/echo"))); Assert.That(request.Content?.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.Json)); - }); + } return new HttpResponseMessage { @@ -117,12 +117,12 @@ public async Task PatchAsJsonAsync_InvokesHttpClientCorrectly() MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Patch)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/echo"))); Assert.That(request.Content?.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.Json)); - }); + } return new HttpResponseMessage { diff --git a/tests/Kampute.HttpClient.NewtonsoftJson.Test/JsonContentTests.cs b/tests/Kampute.HttpClient.NewtonsoftJson.Test/JsonContentTests.cs index 4b6f95f..ecbdccb 100644 --- a/tests/Kampute.HttpClient.NewtonsoftJson.Test/JsonContentTests.cs +++ b/tests/Kampute.HttpClient.NewtonsoftJson.Test/JsonContentTests.cs @@ -17,12 +17,12 @@ public async Task SetsContentCorrectly() var jsonString = await jsonContent.ReadAsStringAsync(); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(jsonString, Is.EqualTo(expectedString)); Assert.That(jsonContent.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.Json)); Assert.That(jsonContent.Headers.ContentType?.CharSet, Is.EqualTo(Encoding.UTF8.WebName)); - }); + } } } } diff --git a/tests/Kampute.HttpClient.NewtonsoftJson.Test/Kampute.HttpClient.NewtonsoftJson.Test.csproj b/tests/Kampute.HttpClient.NewtonsoftJson.Test/Kampute.HttpClient.NewtonsoftJson.Test.csproj index 97932ca..de8de47 100644 --- a/tests/Kampute.HttpClient.NewtonsoftJson.Test/Kampute.HttpClient.NewtonsoftJson.Test.csproj +++ b/tests/Kampute.HttpClient.NewtonsoftJson.Test/Kampute.HttpClient.NewtonsoftJson.Test.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 false true latest @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Kampute.HttpClient.Test/Content/Compression/DeflateCompressedContentTests.cs b/tests/Kampute.HttpClient.Test/Content/Compression/DeflateCompressedContentTests.cs index dd6547e..fb61952 100644 --- a/tests/Kampute.HttpClient.Test/Content/Compression/DeflateCompressedContentTests.cs +++ b/tests/Kampute.HttpClient.Test/Content/Compression/DeflateCompressedContentTests.cs @@ -19,11 +19,11 @@ public async Task DeflateCompressedContent_CompressesDataCorrectly() using var originalContent = new StringContent(text, Encoding.UTF32, MediaTypeNames.Text.Plain); using var compressedContent = new DeflateCompressedContent(originalContent, CompressionLevel.Optimal); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(compressedContent.Headers.ContentType, Is.EqualTo(originalContent.Headers.ContentType)); Assert.That(compressedContent.Headers.ContentEncoding, Contains.Item("deflate")); - }); + } var compressedStream = await compressedContent.ReadAsStreamAsync(); diff --git a/tests/Kampute.HttpClient.Test/Content/Compression/GzipCompressedContentTests.cs b/tests/Kampute.HttpClient.Test/Content/Compression/GzipCompressedContentTests.cs index 2ee03a8..0e3a22f 100644 --- a/tests/Kampute.HttpClient.Test/Content/Compression/GzipCompressedContentTests.cs +++ b/tests/Kampute.HttpClient.Test/Content/Compression/GzipCompressedContentTests.cs @@ -19,11 +19,11 @@ public async Task GzipCompressedContent_CompressesDataCorrectly() using var originalContent = new StringContent(text, Encoding.UTF32, MediaTypeNames.Text.Plain); using var compressedContent = new GzipCompressedContent(originalContent, CompressionLevel.Optimal); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(compressedContent.Headers.ContentType, Is.EqualTo(originalContent.Headers.ContentType)); Assert.That(compressedContent.Headers.ContentEncoding, Contains.Item("gzip")); - }); + } var compressedStream = await compressedContent.ReadAsStreamAsync(); diff --git a/tests/Kampute.HttpClient.Test/ErrorHandlers/HttpError401HandlerTests.cs b/tests/Kampute.HttpClient.Test/ErrorHandlers/HttpError401HandlerTests.cs index aca6ec7..4a05aca 100644 --- a/tests/Kampute.HttpClient.Test/ErrorHandlers/HttpError401HandlerTests.cs +++ b/tests/Kampute.HttpClient.Test/ErrorHandlers/HttpError401HandlerTests.cs @@ -67,17 +67,17 @@ public async Task On401Response_BySuccessfulAuthentication_AuthorizesRequests() var tasks = Enumerable.Range(1, numberOfRequests).Select(i => _client.SendAsync(HttpMethod.Get, $"/protected/resource{i}")); await Task.WhenAll(tasks); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(numberOfInvokes, Is.EqualTo(1)); Assert.That(numberOfResponses, Is.EqualTo(numberOfRequests + 1)); Assert.That(_client.DefaultRequestHeaders.Authorization, Is.Not.Null); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(_client.DefaultRequestHeaders.Authorization?.Scheme, Is.EqualTo(scheme)); Assert.That(_client.DefaultRequestHeaders.Authorization?.Parameter, Is.EqualTo(apiKey)); - }); - }); + } + } } [Test] diff --git a/tests/Kampute.HttpClient.Test/ErrorHandlers/HttpError429HandlerTests.cs b/tests/Kampute.HttpClient.Test/ErrorHandlers/HttpError429HandlerTests.cs index 6ea1e89..abbe3b0 100644 --- a/tests/Kampute.HttpClient.Test/ErrorHandlers/HttpError429HandlerTests.cs +++ b/tests/Kampute.HttpClient.Test/ErrorHandlers/HttpError429HandlerTests.cs @@ -54,11 +54,11 @@ public async Task On429Response_WithRateLimitResetHeader_RetriesRequestAfterSpec await Assert.ThatAsync(() => _client.SendAsync(HttpMethod.Get, "/rate-limited/resource"), Throws.TypeOf()); timer.Stop(); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(attempts, Is.EqualTo(2)); Assert.That(timer.Elapsed, Is.EqualTo(resetDelay).Within(TimeSpan.FromSeconds(1.0))); - }); + } } [Test] diff --git a/tests/Kampute.HttpClient.Test/ErrorHandlers/HttpError503HandlerTests.cs b/tests/Kampute.HttpClient.Test/ErrorHandlers/HttpError503HandlerTests.cs index a9dd9fb..7878c30 100644 --- a/tests/Kampute.HttpClient.Test/ErrorHandlers/HttpError503HandlerTests.cs +++ b/tests/Kampute.HttpClient.Test/ErrorHandlers/HttpError503HandlerTests.cs @@ -55,11 +55,11 @@ public async Task On503Response_WithRetryAfterHeader_AsDate_RetriesRequestAfterS await Assert.ThatAsync(() => _client.SendAsync(HttpMethod.Get, "/unavailable/resource"), Throws.TypeOf()); timer.Stop(); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(attempts, Is.EqualTo(2)); Assert.That(timer.Elapsed, Is.EqualTo(retryDelay).Within(0.1 * retryDelay)); - }); + } } [Test] @@ -84,11 +84,11 @@ public async Task On503Response_WithRetryAfterHeader_AsDelta_RetriesRequestAfter await Assert.ThatAsync(() => _client.SendAsync(HttpMethod.Get, "/unavailable/resource"), Throws.TypeOf()); timer.Stop(); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(attempts, Is.EqualTo(2)); Assert.That(timer.Elapsed, Is.EqualTo(retryDelay).Within(0.1 * retryDelay)); - }); + } } [Test] diff --git a/tests/Kampute.HttpClient.Test/ErrorHandlers/TransientHttpErrorHandlerTests.cs b/tests/Kampute.HttpClient.Test/ErrorHandlers/TransientHttpErrorHandlerTests.cs new file mode 100644 index 0000000..afded15 --- /dev/null +++ b/tests/Kampute.HttpClient.Test/ErrorHandlers/TransientHttpErrorHandlerTests.cs @@ -0,0 +1,187 @@ +// Copyright (C) 2024 Kampute +// +// This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. +// See the LICENSE file in the project root for the full license text. + +namespace Kampute.HttpClient.Test.ErrorHandlers +{ + using Kampute.HttpClient.ErrorHandlers; + using Kampute.HttpClient.Test.TestHelpers; + using Moq; + using NUnit.Framework; + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Threading.Tasks; + + [TestFixture] + public class TransientHttpErrorHandlerTests + { + private readonly Mock _mockMessageHandler = new(); + private HttpRestClient _client; + + [SetUp] + public void Setup() + { + var httpClient = new HttpClient(_mockMessageHandler.Object, disposeHandler: false); + _client = new HttpRestClient(httpClient) + { + BaseAddress = new Uri("http://api.test.com"), + }; + } + + [TearDown] + public void Cleanup() + { + _client.Dispose(); + } + + [Test] + public void Constructor_WithNullStatusCodes_ThrowsArgumentNullException() + { + Assert.Throws(() => new TransientHttpErrorHandler((IEnumerable)null!)); + } + + [Test] + public void Constructor_WithEmptyStatusCodes_ThrowsArgumentException() + { + Assert.Throws(() => new TransientHttpErrorHandler([])); + } + + [Test] + public void Constructor_WithCustomStatusCodes_SetsHandledStatusCodes() + { + var customCodes = new[] + { + HttpStatusCode.InternalServerError, + HttpStatusCode.BadRequest + }; + var handler = new TransientHttpErrorHandler(customCodes); + + Assert.That(handler.HandledStatusCodes, Is.EquivalentTo(customCodes)); + } + + [Test] + [TestCase(HttpStatusCode.RequestTimeout, ExpectedResult = true)] + [TestCase(HttpStatusCode.BadGateway, ExpectedResult = true)] + [TestCase(HttpStatusCode.ServiceUnavailable, ExpectedResult = true)] + [TestCase(HttpStatusCode.GatewayTimeout, ExpectedResult = true)] + [TestCase(507 /* Insufficient Storage */, ExpectedResult = true)] + [TestCase(509 /* Bandwidth Limit Exceeded */, ExpectedResult = true)] + [TestCase(HttpStatusCode.OK, ExpectedResult = false)] + [TestCase(HttpStatusCode.NotFound, ExpectedResult = false)] + [TestCase(HttpStatusCode.InternalServerError, ExpectedResult = false)] + [TestCase(HttpStatusCode.Unauthorized, ExpectedResult = false)] + [TestCase(HttpStatusCode.Forbidden, ExpectedResult = false)] + [TestCase(HttpStatusCode.BadRequest, ExpectedResult = false)] + public bool CanHandle_ForDefaultConfiguration_ReturnsExpectedResults(HttpStatusCode statusCode) + { + var handler = new TransientHttpErrorHandler(); + + return handler.CanHandle(statusCode); + } + + [Test] + public async Task OnTransientHttpError_WithRetryAfterHeader_AsDate_RetriesRequestAfterSpecifiedTime() + { + var transientHandler = new TransientHttpErrorHandler(); + _client.ErrorHandlers.Add(transientHandler); + + var retryDelay = TimeSpan.FromMilliseconds(1000); + + var attempts = 0; + _mockMessageHandler.MockHttpResponse(request => + { + attempts++; + + var response = new HttpResponseMessage(HttpStatusCode.RequestTimeout); + response.Headers.RetryAfter = new RetryConditionHeaderValue(DateTimeOffset.UtcNow.Add(retryDelay)); + return response; + }); + + var timer = Stopwatch.StartNew(); + await Assert.ThatAsync(() => _client.SendAsync(HttpMethod.Get, "/unavailable/resource"), Throws.TypeOf()); + timer.Stop(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(attempts, Is.EqualTo(2)); + Assert.That(timer.Elapsed, Is.EqualTo(retryDelay).Within(0.1 * retryDelay)); + } + } + + [Test] + public async Task OnTransientHttpError_WithRetryAfterHeader_AsDelta_RetriesRequestAfterSpecifiedDelay() + { + var transientHandler = new TransientHttpErrorHandler(); + _client.ErrorHandlers.Add(transientHandler); + + var retryDelay = TimeSpan.FromMilliseconds(1000); + + var attempts = 0; + _mockMessageHandler.MockHttpResponse(request => + { + attempts++; + + var response = new HttpResponseMessage(HttpStatusCode.RequestTimeout); + response.Headers.RetryAfter = new RetryConditionHeaderValue(retryDelay); + return response; + }); + + var timer = Stopwatch.StartNew(); + await Assert.ThatAsync(() => _client.SendAsync(HttpMethod.Get, "/unavailable/resource"), Throws.TypeOf()); + timer.Stop(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(attempts, Is.EqualTo(2)); + Assert.That(timer.Elapsed, Is.EqualTo(retryDelay).Within(0.1 * retryDelay)); + } + } + + [Test] + public async Task OnTransientHttpError_WithoutRetryAfterHeader_RetriesAccordingToDefaultStrategy() + { + var transientHandler = new TransientHttpErrorHandler(); + _client.ErrorHandlers.Add(transientHandler); + _client.BackoffStrategy = BackoffStrategies.Uniform(2, TimeSpan.Zero); + + var attempts = 0; + _mockMessageHandler.MockHttpResponse(request => + { + attempts++; + + return new HttpResponseMessage(HttpStatusCode.RequestTimeout); + }); + + await Assert.ThatAsync(() => _client.SendAsync(HttpMethod.Get, "/unavailable/resource"), Throws.TypeOf()); + + Assert.That(attempts, Is.EqualTo(3)); + } + + [Test] + public async Task OnTransientHttpError_WithCustomBackoffStrategy_RetriesAccordingToCustomStrategy() + { + var transientHandler = new TransientHttpErrorHandler + { + OnBackoffStrategy = (ctx, retryAfter) => BackoffStrategies.Uniform(2, TimeSpan.Zero) + }; + _client.ErrorHandlers.Add(transientHandler); + + var attempts = 0; + _mockMessageHandler.MockHttpResponse(request => + { + attempts++; + + return new HttpResponseMessage(HttpStatusCode.RequestTimeout); + }); + + await Assert.ThatAsync(() => _client.SendAsync(HttpMethod.Get, "/unavailable/resource"), Throws.TypeOf()); + + Assert.That(attempts, Is.EqualTo(3)); + } + } +} diff --git a/tests/Kampute.HttpClient.Test/HttpContentDeserializerCollectionTests.cs b/tests/Kampute.HttpClient.Test/HttpContentDeserializerCollectionTests.cs index c2e98cd..b6fcad9 100644 --- a/tests/Kampute.HttpClient.Test/HttpContentDeserializerCollectionTests.cs +++ b/tests/Kampute.HttpClient.Test/HttpContentDeserializerCollectionTests.cs @@ -99,11 +99,11 @@ public void Remove_WithExistingDeserializer_RemovesDeserializerAndReturnsTrue() var result = collection.Remove(deserializer); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.True); Assert.That(collection, Is.Empty); - }); + } } [Test] diff --git a/tests/Kampute.HttpClient.Test/HttpContentExtensionsTests.cs b/tests/Kampute.HttpClient.Test/HttpContentExtensionsTests.cs index 9966ff4..e4dea46 100644 --- a/tests/Kampute.HttpClient.Test/HttpContentExtensionsTests.cs +++ b/tests/Kampute.HttpClient.Test/HttpContentExtensionsTests.cs @@ -1,9 +1,9 @@ namespace Kampute.HttpClient.Test { using Kampute.HttpClient.Content.Compression; + using Kampute.HttpClient.Test.TestHelpers; using NUnit.Framework; using System; - using System.IO; using System.Net.Http; using System.Net.Http.Headers; using System.Text; @@ -11,12 +11,6 @@ [TestFixture] public class HttpContentExtensionsTests { - private class NonSeekableMemoryStream : MemoryStream - { - public override bool CanSeek => false; - public override long Seek(long offset, SeekOrigin loc) => throw new NotSupportedException(); - } - [Test] public void FindCharacterEncoding_WithCharSet_ReturnsEncoding() { @@ -49,7 +43,7 @@ public void FindCharacterEncoding_WithUnsupportedCharSet_ThrowsArgumentException [Test] public void IsReusable_WhenContentIsReusable_ReturnsTrue() { - using var content = new StreamContent(new MemoryStream()); + using var content = new StreamContent(new TestStream(seekable: true)); var result = content.IsReusable(); @@ -59,7 +53,29 @@ public void IsReusable_WhenContentIsReusable_ReturnsTrue() [Test] public void IsReusable_WhenContentIsNotReusable_ReturnsFalse() { - using var content = new StreamContent(new NonSeekableMemoryStream()); + using var content = new StreamContent(new TestStream(seekable: false)); + + var result = content.IsReusable(); + + Assert.That(result, Is.False); + } + + [Test] + public void IsReusable_WhenCompressedContentIsReusable_ReturnsTrue() + { + var originalContent = new StreamContent(new TestStream(seekable: true)); + using var content = new DeflateCompressedContent(originalContent); + + var result = content.IsReusable(); + + Assert.That(result, Is.True); + } + + [Test] + public void IsReusable_WhenCompressedContentIsNotReusable_ReturnsFalse() + { + var originalContent = new StreamContent(new TestStream(seekable: false)); + using var content = new DeflateCompressedContent(originalContent); var result = content.IsReusable(); diff --git a/tests/Kampute.HttpClient.Test/HttpErrorHandlerCollectionTests.cs b/tests/Kampute.HttpClient.Test/HttpErrorHandlerCollectionTests.cs index ee71137..e2997f9 100644 --- a/tests/Kampute.HttpClient.Test/HttpErrorHandlerCollectionTests.cs +++ b/tests/Kampute.HttpClient.Test/HttpErrorHandlerCollectionTests.cs @@ -53,11 +53,11 @@ public void Remove_WithExistingErrorHandler_RemovesErrorHandlerAndReturnsTrue() var result = collection.Remove(errorHandler); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.True); Assert.That(collection, Is.Empty); - }); + } } [Test] diff --git a/tests/Kampute.HttpClient.Test/HttpRequestMessageExtensionsTests.cs b/tests/Kampute.HttpClient.Test/HttpRequestMessageExtensionsTests.cs index 64dbc8d..b5121dc 100644 --- a/tests/Kampute.HttpClient.Test/HttpRequestMessageExtensionsTests.cs +++ b/tests/Kampute.HttpClient.Test/HttpRequestMessageExtensionsTests.cs @@ -1,19 +1,13 @@ namespace Kampute.HttpClient.Test { + using Kampute.HttpClient.Test.TestHelpers; using NUnit.Framework; using System; - using System.IO; using System.Net.Http; [TestFixture] public class HttpRequestMessageExtensionsTests { - private class NonSeekableMemoryStream : MemoryStream - { - public override bool CanSeek => false; - public override long Seek(long offset, SeekOrigin loc) => throw new NotSupportedException(); - } - [Test] public void Clone_WithReusableContent_CreatesCopy() { @@ -28,14 +22,14 @@ public void Clone_WithReusableContent_CreatesCopy() Assert.That(clonedRequest, Is.Not.Null); Assert.That(clonedRequest, Is.Not.SameAs(originalRequest)); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(clonedRequest.RequestUri, Is.EqualTo(originalRequest.RequestUri)); Assert.That(clonedRequest.Version, Is.EqualTo(originalRequest.Version)); Assert.That(clonedRequest.Headers.Contains("Test-Header"), Is.True); Assert.That(clonedRequest.Content, Is.SameAs(originalRequest.Content)); Assert.That(clonedRequest.GetCloneGeneration(), Is.EqualTo(1)); - }); + } } [Test] @@ -43,7 +37,7 @@ public void Clone_WithNonReusableContent_ThrowsInvalidOperationException() { using var request = new HttpRequestMessage { - Content = new StreamContent(new NonSeekableMemoryStream()) + Content = new StreamContent(new TestStream(seekable: false)) }; Assert.That(request.Clone, Throws.InstanceOf()); @@ -62,7 +56,7 @@ public void IsClonable_WithReusableContent_ReturnsTrue() { using var request = new HttpRequestMessage { - Content = new StreamContent(new MemoryStream()) + Content = new StreamContent(new TestStream(seekable: true)) }; Assert.That(request.CanClone(), Is.True); @@ -73,7 +67,7 @@ public void IsClonable_WithNonReusableContent_ReturnsFalse() { using var request = new HttpRequestMessage { - Content = new StreamContent(new NonSeekableMemoryStream()) + Content = new StreamContent(new TestStream(seekable: false)) }; Assert.That(request.CanClone(), Is.False); @@ -103,12 +97,12 @@ public void GetCloneGeneration_WhenClonedMultipleTimes_ReturnsCorrectCount() using var firstGenerationClone = originalRequest.Clone(); using var secondGenerationClone = firstGenerationClone.Clone(); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { - Assert.That(originalRequest.GetCloneGeneration(), Is.EqualTo(0)); + Assert.That(originalRequest.GetCloneGeneration(), Is.Zero); Assert.That(firstGenerationClone.GetCloneGeneration(), Is.EqualTo(1)); Assert.That(secondGenerationClone.GetCloneGeneration(), Is.EqualTo(2)); - }); + } } } } diff --git a/tests/Kampute.HttpClient.Test/HttpRequestScopeTests.cs b/tests/Kampute.HttpClient.Test/HttpRequestScopeTests.cs index e4bc189..5ad43a8 100644 --- a/tests/Kampute.HttpClient.Test/HttpRequestScopeTests.cs +++ b/tests/Kampute.HttpClient.Test/HttpRequestScopeTests.cs @@ -91,12 +91,12 @@ public async Task PerformAsync_AppliesScopedHeadersAndProperties() _mockMessageHandler.MockHttpResponse(request => { var propExists = request.Options.TryGetValue(new HttpRequestOptionsKey(propertyName), out var propValue); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(propExists, Is.True); Assert.That(propValue, Is.EqualTo(propertyValue)); - Assert.That(request.Headers.GetValues(headerName), Is.EqualTo(new[] { headerValue })); - }); + Assert.That(request.Headers.GetValues(headerName), Is.EqualTo([headerValue])); + } return new HttpResponseMessage(HttpStatusCode.OK); }); diff --git a/tests/Kampute.HttpClient.Test/HttpResponseHeadersExtensionsTests.cs b/tests/Kampute.HttpClient.Test/HttpResponseHeadersExtensionsTests.cs index 805a166..8b2e5ab 100644 --- a/tests/Kampute.HttpClient.Test/HttpResponseHeadersExtensionsTests.cs +++ b/tests/Kampute.HttpClient.Test/HttpResponseHeadersExtensionsTests.cs @@ -25,11 +25,11 @@ public void TryExtractRetryAfterTime_WithValidDate_ReturnsTrueAndTime() var result = _headers.TryExtractRetryAfterTime(out var retryAfterTime); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.True); Assert.That(retryAfterTime, Is.EqualTo(expectedDate).Within(TimeSpan.FromSeconds(1))); - }); + } } [Test] @@ -40,11 +40,11 @@ public void TryExtractRetryAfterTime_WithValidDelta_ReturnsTrueAndTime() var result = _headers.TryExtractRetryAfterTime(out var retryAfterTime); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.True); Assert.That(retryAfterTime, Is.EqualTo(DateTimeOffset.UtcNow.Add(delta)).Within(TimeSpan.FromSeconds(1))); - }); + } } [Test] @@ -52,11 +52,11 @@ public void TryExtractRetryAfterTime_WithoutRetryAfterHeader_ReturnsFalse() { var result = _headers.TryExtractRetryAfterTime(out var retryAfterTime); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.False); Assert.That(retryAfterTime, Is.Null); - }); + } } [Test] @@ -67,11 +67,11 @@ public void TryExtractRateLimitResetTime_WithValidUnixTimestampHeader_ReturnsTru var result = _headers.TryExtractRateLimitResetTime(out var resetTime); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.True); Assert.That(resetTime, Is.EqualTo(expectedTime).Within(TimeSpan.FromSeconds(1))); - }); + } } } } diff --git a/tests/Kampute.HttpClient.Test/HttpRestClientExtensionsTests.cs b/tests/Kampute.HttpClient.Test/HttpRestClientExtensionsTests.cs index 6407320..41fbf52 100644 --- a/tests/Kampute.HttpClient.Test/HttpRestClientExtensionsTests.cs +++ b/tests/Kampute.HttpClient.Test/HttpRestClientExtensionsTests.cs @@ -47,11 +47,11 @@ public async Task HeadAsync_InvokesHttpClientCorrectly() { _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Head)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); - }); + } return new HttpResponseMessage(HttpStatusCode.OK); }); @@ -66,11 +66,11 @@ public async Task OptionsAsync_InvokesHttpClientCorrectly() { _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Options)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); - }); + } return new HttpResponseMessage(HttpStatusCode.OK); }); @@ -87,11 +87,11 @@ public async Task GetAsync_ReturnsResponseAsObject() _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Get)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); - }); + } return new HttpResponseMessage { @@ -112,11 +112,11 @@ public async Task GetAsByteArrayAsync_ReturnsResponseAsBytes() _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Get)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); - }); + } return new HttpResponseMessage { @@ -137,11 +137,11 @@ public async Task GetAsStringAsync_ReturnsResponseAsString() _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Get)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); - }); + } return new HttpResponseMessage { @@ -162,11 +162,11 @@ public async Task GetAsStreamAsync_ReturnsResponseAsStream() _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Get)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); - }); + } return new HttpResponseMessage { @@ -187,11 +187,11 @@ public async Task GetToStreamAsync_WritesResponseIntoStream() _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Get)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); - }); + } return new HttpResponseMessage { @@ -214,12 +214,12 @@ public async Task PostAsync_InvokesHttpClientCorrectly() var sent = false; _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Post)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); Assert.That(request.Content?.ReadAsStringAsync().Result, Is.EqualTo(payload)); - }); + } sent = true; return new HttpResponseMessage(HttpStatusCode.OK); @@ -238,12 +238,12 @@ public async Task PostAsync_Generic_ReturnsResponseAsObject() _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Post)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); Assert.That(request.Content?.ReadAsStringAsync().Result, Is.EqualTo(payload)); - }); + } return new HttpResponseMessage { @@ -265,12 +265,12 @@ public async Task PutAsync_InvokesHttpClientCorrectly() var sent = false; _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Put)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); Assert.That(request.Content?.ReadAsStringAsync().Result, Is.EqualTo(payload)); - }); + } sent = true; return new HttpResponseMessage(HttpStatusCode.OK); @@ -278,7 +278,7 @@ public async Task PutAsync_InvokesHttpClientCorrectly() await _client.PutAsync("/resource", new TestContent(payload)); - Assert.That(sent, Is.EqualTo(true)); + Assert.That(sent, Is.True); } [Test] @@ -289,12 +289,12 @@ public async Task PutAsync_Generic_ReturnsResponseAsObject() _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Put)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); Assert.That(request.Content?.ReadAsStringAsync().Result, Is.EqualTo(payload)); - }); + } return new HttpResponseMessage { @@ -316,12 +316,12 @@ public async Task PatchAsync_InvokesHttpClientCorrectly() var sent = false; _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Patch)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); Assert.That(request.Content?.ReadAsStringAsync().Result, Is.EqualTo(payload)); - }); + } sent = true; return new HttpResponseMessage(HttpStatusCode.OK); @@ -329,7 +329,7 @@ public async Task PatchAsync_InvokesHttpClientCorrectly() await _client.PatchAsync("/resource", new TestContent(payload)); - Assert.That(sent, Is.EqualTo(true)); + Assert.That(sent, Is.True); } [Test] @@ -340,12 +340,12 @@ public async Task PatchAsync_Generic_ReturnsResponseAsObject() _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Patch)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); Assert.That(request.Content?.ReadAsStringAsync().Result, Is.EqualTo(payload)); - }); + } return new HttpResponseMessage { @@ -366,11 +366,11 @@ public async Task DeleteAsync_InvokesHttpClientCorrectly() _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Delete)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); - }); + } return new HttpResponseMessage { @@ -391,11 +391,11 @@ public async Task DeleteAsync_Generic_ReturnsResponseAsObject() _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Delete)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); - }); + } return new HttpResponseMessage { @@ -418,11 +418,11 @@ public async Task DownloadAsync_WritesResponseIntoStream() _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(method)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); - }); + } var content = new ByteArrayContent(expectedStream.ToArray()); content.Headers.ContentType = new MediaTypeHeaderValue(MediaTypeNames.Application.Octet); @@ -437,21 +437,21 @@ public async Task DownloadAsync_WritesResponseIntoStream() using var resultStream = await _client.DownloadAsync(method, "/resource", new TestContent(payload), contentHeaders => { Assert.That(contentHeaders, Is.Not.Null); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(contentHeaders.ContentType, Is.EqualTo(new MediaTypeHeaderValue(MediaTypeNames.Application.Octet))); Assert.That(contentHeaders.ContentLength, Is.EqualTo(expectedStream.Length)); - }); + } return new MemoryStream((int)contentHeaders.ContentLength.GetValueOrDefault()); }); Assert.That(resultStream, Is.Not.Null); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { resultStream.Seek(0, SeekOrigin.Begin); Assert.That(resultStream, Is.TypeOf()); Assert.That(resultStream, Is.EqualTo(expectedStream)); - }); + } } } } diff --git a/tests/Kampute.HttpClient.Test/HttpRestClientFormExtensionsTests.cs b/tests/Kampute.HttpClient.Test/HttpRestClientFormExtensionsTests.cs index 7a5751d..54bccfd 100644 --- a/tests/Kampute.HttpClient.Test/HttpRestClientFormExtensionsTests.cs +++ b/tests/Kampute.HttpClient.Test/HttpRestClientFormExtensionsTests.cs @@ -44,13 +44,13 @@ public async Task PostAsFormAsync_InvokesHttpClientCorrectly() { _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Post)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); Assert.That(request.Content?.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.FormUrlEncoded)); Assert.That(request.Content?.ReadAsStringAsync().Result, Is.EqualTo("name=value")); - }); + } return new HttpResponseMessage(HttpStatusCode.OK); }); @@ -63,13 +63,13 @@ public async Task PutAsFormAsync_InvokesHttpClientCorrectly() { _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Put)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); Assert.That(request.Content?.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.FormUrlEncoded)); Assert.That(request.Content?.ReadAsStringAsync().Result, Is.EqualTo("name=value")); - }); + } return new HttpResponseMessage(HttpStatusCode.OK); }); @@ -82,13 +82,13 @@ public async Task PatchAsFormAsync_InvokesHttpClientCorrectly() { _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Patch)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); Assert.That(request.Content?.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.FormUrlEncoded)); Assert.That(request.Content?.ReadAsStringAsync().Result, Is.EqualTo("name=value")); - }); + } return new HttpResponseMessage(HttpStatusCode.OK); }); diff --git a/tests/Kampute.HttpClient.Test/HttpRestClientTests.cs b/tests/Kampute.HttpClient.Test/HttpRestClientTests.cs index 3f10640..db763e2 100644 --- a/tests/Kampute.HttpClient.Test/HttpRestClientTests.cs +++ b/tests/Kampute.HttpClient.Test/HttpRestClientTests.cs @@ -8,6 +8,9 @@ using NUnit.Framework; using System; using System.Collections.Generic; + using System.IO; + using System.IO.Compression; + using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -47,7 +50,7 @@ public void Cleanup() } [Test] - public void DefaultConstractor_UsesSharedHttpClient() + public void DefaultConstructor_UsesSharedHttpClient() { var client = new HttpRestClient(); Assert.That(SharedHttpClient.ReferenceCount, Is.EqualTo(1)); @@ -81,11 +84,11 @@ public async Task SendAsync_NonGeneric_ReturnsResponse() { _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(TestHttpMethod)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); - }); + } var response = new HttpResponseMessage(HttpStatusCode.OK); response.Headers.Add("X-Test", "Testing"); @@ -105,11 +108,11 @@ public async Task SendAsync_Generic_ReturnsResponseAsObject() _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(TestHttpMethod)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); - }); + } return new HttpResponseMessage { @@ -141,13 +144,13 @@ public void OnUnsuccessfulStatusCode_WithoutResponseErrorType_ThrowsStandardRest var exception = Assert.ThrowsAsync(async () => await _client.SendAsync(TestHttpMethod, "/resource")); Assert.That(exception, Is.Not.Null); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(exception.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); Assert.That(exception.ResponseMessage?.RequestMessage?.Method, Is.EqualTo(TestHttpMethod)); Assert.That(exception.ResponseMessage?.RequestMessage?.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); Assert.That(exception.Message, Is.Not.EqualTo(errorDetails.Message)); - }); + } } [Test] @@ -160,14 +163,14 @@ public void OnUnsuccessfulStatusCode_WithResponseErrorType_ThrowsCustomizedRestE var exception = Assert.ThrowsAsync(async () => await _client.SendAsync(TestHttpMethod, "/resource")); Assert.That(exception, Is.Not.Null); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(exception.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); Assert.That(exception.ResponseMessage?.RequestMessage?.Method, Is.EqualTo(TestHttpMethod)); Assert.That(exception.ResponseMessage?.RequestMessage?.RequestUri, Is.EqualTo(AbsoluteUrl("/resource"))); Assert.That(exception.ResponseObject, Is.EqualTo(errorDetails).UsingPropertiesComparer()); Assert.That(exception.Message, Is.EqualTo(errorDetails.Message)); - }); + } } [Test] @@ -211,11 +214,11 @@ public async Task OnUnsuccessfulStatusCode_WithErrorHandler_RetriesRequest() var result = await _client.SendAsync(HttpMethod.Get, "/resource", new StringContent("test")); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(attempts, Is.EqualTo(2)); Assert.That(result, Is.EqualTo(expectedResult)); - }); + } } [Test] @@ -252,6 +255,60 @@ public async Task OnConnectionFailure_UsesBackoffStrategy() Assert.That(attempts, Is.EqualTo(maxRetries + 1)); } + [TestCase("gzip")] + [TestCase("deflate")] + public async Task OnConnectionFailure_WithCompressedContent_UsesBackoffStrategy(string encoding) + { + var maxRetries = 2; + + var mockBackoffStrategy = new Mock(); + var mockRetryScheduler = new Mock(); + + var retries = 0; + mockRetryScheduler.Setup(scheduler => scheduler.WaitAsync(It.IsAny())) + .ReturnsAsync(() => retries < maxRetries).Callback(() => ++retries); + mockBackoffStrategy.Setup(strategy => strategy.CreateScheduler(It.IsAny())) + .Returns(mockRetryScheduler.Object); + + _client.BackoffStrategy = mockBackoffStrategy.Object; + + var attempts = 0; + _mockMessageHandler.MockHttpResponse(request => + { + if (++attempts <= maxRetries) + throw new HttpRequestException("Connection failure", new SocketException((int)SocketError.HostUnreachable)); + + Assert.That(request.Content, Is.Not.Null); + + var compressedStream = request.Content.ReadAsStreamAsync().Result; + using Stream decompressedStream = request.Content.Headers.ContentEncoding.FirstOrDefault() switch + { + "gzip" => new GZipStream(compressedStream, CompressionMode.Decompress), + "deflate" => new DeflateStream(compressedStream, CompressionMode.Decompress), + _ => throw new InvalidOperationException("Unsupported encoding") + }; + using var reader = new StreamReader(decompressedStream, Encoding.UTF8); + var actual = reader.ReadToEnd(); + + Assert.That(actual, Is.EqualTo("test")); + + return new HttpResponseMessage(HttpStatusCode.OK); + }); + + using var payload = new StringContent("test", Encoding.UTF8); + using HttpContent compressedPayload = encoding switch + { + "gzip" => payload.AsGzip(), + "deflate" => payload.AsDeflate(), + _ => throw new InvalidOperationException("Unsupported encoding") + }; + + await _client.SendAsync(TestHttpMethod, "/test", compressedPayload); + + mockRetryScheduler.Verify(scheduler => scheduler.WaitAsync(It.IsAny()), Times.Exactly(maxRetries)); + Assert.That(attempts, Is.EqualTo(maxRetries + 1)); + } + [Test] public async Task BeginPropertyScope_ModifiesRequestPropertiesCorrectly() { @@ -261,11 +318,11 @@ public async Task BeginPropertyScope_ModifiesRequestPropertiesCorrectly() _mockMessageHandler.MockHttpResponse(request => { var propExists = request.Options.TryGetValue(new HttpRequestOptionsKey(scopedProperty.Key), out var propValue); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(propExists, Is.True); Assert.That(propValue, Is.EqualTo(scopedProperty.Value)); - }); + } sent = true; return new HttpResponseMessage(HttpStatusCode.OK); @@ -293,12 +350,12 @@ public async Task BeginHeaderScope_ModifiesRequestHeadersCorrectly() var sent = false; _mockMessageHandler.MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { - Assert.That(request.Headers.GetValues(scopedHeaderToAdd.Key), Is.EqualTo(new[] { scopedHeaderToAdd.Value })); - Assert.That(request.Headers.GetValues(scopedHeaderToChange.Key), Is.EqualTo(new[] { scopedHeaderToChange.Value })); + Assert.That(request.Headers.GetValues(scopedHeaderToAdd.Key), Is.EqualTo([scopedHeaderToAdd.Value])); + Assert.That(request.Headers.GetValues(scopedHeaderToChange.Key), Is.EqualTo([scopedHeaderToChange.Value])); Assert.That(request.Headers.Contains(scopedHeaderToDelete.Key), Is.False); - }); + } sent = true; return new HttpResponseMessage(HttpStatusCode.OK); diff --git a/tests/Kampute.HttpClient.Test/Kampute.HttpClient.Test.csproj b/tests/Kampute.HttpClient.Test/Kampute.HttpClient.Test.csproj index f0f11e9..8877245 100644 --- a/tests/Kampute.HttpClient.Test/Kampute.HttpClient.Test.csproj +++ b/tests/Kampute.HttpClient.Test/Kampute.HttpClient.Test.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 false true latest @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Kampute.HttpClient.Test/RetryManagement/RetrySchedulerTests.cs b/tests/Kampute.HttpClient.Test/RetryManagement/RetrySchedulerTests.cs index 691208f..fe6ebd0 100644 --- a/tests/Kampute.HttpClient.Test/RetryManagement/RetrySchedulerTests.cs +++ b/tests/Kampute.HttpClient.Test/RetryManagement/RetrySchedulerTests.cs @@ -56,11 +56,11 @@ public async Task WaitAsync_WhenStrategyIndicatesRetryIsAdvisable_ReturnsTrue() var result = await scheduler.WaitAsync(CancellationToken.None); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.True); Assert.That(scheduler.Attempts, Is.EqualTo(1u)); - }); + } } [Test] @@ -72,11 +72,11 @@ public async Task WaitAsync_WhenStrategyIndicatesRetryIsNotAdvisable_ReturnsFals var result = await scheduler.WaitAsync(CancellationToken.None); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.False); Assert.That(scheduler.Attempts, Is.Zero); - }); + } } [Test] @@ -102,11 +102,11 @@ public async Task Reset_ResetsInternalState() await scheduler.WaitAsync(CancellationToken.None); scheduler.Reset(); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(scheduler.Elapsed, Is.LessThanOrEqualTo(TimeSpan.FromMilliseconds(10))); Assert.That(scheduler.Attempts, Is.Zero); - }); + } } } } diff --git a/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/ExponentialStrategyTests.cs b/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/ExponentialStrategyTests.cs index 16f2f43..f45f231 100644 --- a/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/ExponentialStrategyTests.cs +++ b/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/ExponentialStrategyTests.cs @@ -34,11 +34,11 @@ public void TryGetRetryDelay_ReturnsExpectedDelay(uint attempts, int initialDela var result = strategy.TryGetRetryDelay(TimeSpan.Zero, attempts, out var actualDelay); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.True); Assert.That(actualDelay, Is.EqualTo(expectedDelay)); - }); + } } [TestCase(0u, 0, 2.0, 0)] @@ -57,11 +57,11 @@ public void TryGetRetryDelay_IgnoresElapsed(uint attempts, int initialDelayMs, d var result = strategy.TryGetRetryDelay(TimeSpan.FromHours(1), attempts, out var actualDelay); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.True); Assert.That(actualDelay, Is.EqualTo(expectedDelay)); - }); + } } } } diff --git a/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/FibonacciStrategyTests.cs b/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/FibonacciStrategyTests.cs index a1da2f0..4d98e2d 100644 --- a/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/FibonacciStrategyTests.cs +++ b/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/FibonacciStrategyTests.cs @@ -17,11 +17,11 @@ public void Constructor_WithInitialDelayAndDelayStep_SetsPropertiesCorrectly(int var strategy = new FibonacciStrategy(initialDelay, delayStep); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(strategy.InitialDelay, Is.EqualTo(initialDelay)); Assert.That(strategy.DelayStep, Is.EqualTo(delayStep)); - }); + } } [TestCase(0)] @@ -33,11 +33,11 @@ public void Constructor_WithInitialDelayOnly_SetsInitialDelayAndDelayStep(int in var strategy = new FibonacciStrategy(initialDelay); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(strategy.InitialDelay, Is.EqualTo(initialDelay)); Assert.That(strategy.DelayStep, Is.EqualTo(initialDelay)); - }); + } } [TestCase(0u, 0, 0, 0)] @@ -57,11 +57,11 @@ public void TryGetRetryDelay_ReturnsExpectedDelay(uint attempts, int initialDela var result = strategy.TryGetRetryDelay(TimeSpan.Zero, attempts, out var actualDelay); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.True); Assert.That(actualDelay, Is.EqualTo(expectedDelay)); - }); + } } [TestCase(0u, 0, 0, 0)] @@ -81,11 +81,11 @@ public void TryGetRetryDelay_IgnoresElapsed(uint attempts, int initialDelayMs, i var result = strategy.TryGetRetryDelay(TimeSpan.FromHours(1), attempts, out var actualDelay); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.True); Assert.That(actualDelay, Is.EqualTo(expectedDelay)); - }); + } } } } diff --git a/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/LinearStrategyTests.cs b/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/LinearStrategyTests.cs index 49c3e4b..ed4f8e7 100644 --- a/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/LinearStrategyTests.cs +++ b/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/LinearStrategyTests.cs @@ -17,11 +17,11 @@ public void Constructor_WithInitialDelayAndDelayStep_SetsPropertiesCorrectly(int var strategy = new LinearStrategy(initialDelay, delayStep); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(strategy.InitialDelay, Is.EqualTo(initialDelay)); Assert.That(strategy.DelayStep, Is.EqualTo(delayStep)); - }); + } } [TestCase(0)] @@ -33,11 +33,11 @@ public void Constructor_WithInitialDelayOnly_SetsInitialDelayAndDelayStep(int in var strategy = new LinearStrategy(initialDelay); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(strategy.InitialDelay, Is.EqualTo(initialDelay)); Assert.That(strategy.DelayStep, Is.EqualTo(initialDelay)); - }); + } } [TestCase(0u, 0, 0, 0)] @@ -57,11 +57,11 @@ public void TryGetRetryDelay_ReturnsExpectedDelay(uint attempts, int initialDela var result = strategy.TryGetRetryDelay(TimeSpan.Zero, attempts, out var actualDelay); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.True); Assert.That(actualDelay, Is.EqualTo(expectedDelay)); - }); + } } [TestCase(0u, 0, 0, 0)] @@ -81,11 +81,11 @@ public void TryGetRetryDelay_IgnoresElapsed(uint attempts, int initialDelayMs, i var result = strategy.TryGetRetryDelay(TimeSpan.FromHours(1), attempts, out var actualDelay); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.True); Assert.That(actualDelay, Is.EqualTo(expectedDelay)); - }); + } } } } diff --git a/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/Modifiers/JitterStrategyModifierTests.cs b/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/Modifiers/JitterStrategyModifierTests.cs index 5295bb8..381465d 100644 --- a/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/Modifiers/JitterStrategyModifierTests.cs +++ b/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/Modifiers/JitterStrategyModifierTests.cs @@ -32,11 +32,11 @@ public void TryGetRetryDelay_WhenSourceReturnsTrue_ReturnsTrueWithJitteredDelay( var result = strategy.TryGetRetryDelay(TimeSpan.Zero, 0, out var actualDelay); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.True); Assert.That(actualDelay, Is.EqualTo(baseDelay).Within(jitterFactor * baseDelay)); - }); + } } [Test] @@ -48,11 +48,11 @@ public void TryGetRetryDelay_WhenSourceReturnsFalse_ReturnsFalse() var result = strategy.TryGetRetryDelay(TimeSpan.Zero, 0, out var actualDelay); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.False); Assert.That(actualDelay, Is.Default); - }); + } } } } diff --git a/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/Modifiers/LimitedAttemptsStrategyModifierTests.cs b/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/Modifiers/LimitedAttemptsStrategyModifierTests.cs index ac2b8f2..10e90aa 100644 --- a/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/Modifiers/LimitedAttemptsStrategyModifierTests.cs +++ b/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/Modifiers/LimitedAttemptsStrategyModifierTests.cs @@ -27,11 +27,11 @@ public void TryGetRetryDelay_WhenSourceReturnsTrue_ReturnsExpectedResult(uint ma var result = strategy.TryGetRetryDelay(TimeSpan.Zero, attempts, out var actualDelay); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.EqualTo(expectedResult)); Assert.That(actualDelay, expectedResult ? Is.Not.Default : Is.Default); - }); + } } [Test] @@ -44,11 +44,11 @@ public void TryGetRetryDelay_WhenSourceReturnsFalse_ReturnsFalse() var result = strategy.TryGetRetryDelay(TimeSpan.Zero, 0, out var actualDelay); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.False); Assert.That(actualDelay, Is.Default); - }); + } } } } diff --git a/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/Modifiers/LimitedDurationStrategyModifierTests.cs b/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/Modifiers/LimitedDurationStrategyModifierTests.cs index 52ef683..47dba62 100644 --- a/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/Modifiers/LimitedDurationStrategyModifierTests.cs +++ b/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/Modifiers/LimitedDurationStrategyModifierTests.cs @@ -32,11 +32,11 @@ public void TryGetRetryDelay_WhenSourceReturnsTrue_ReturnsExpectedResult(int tim var result = strategy.TryGetRetryDelay(elapsed, 0, out var actualDelay); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.EqualTo(expectedResult)); Assert.That(actualDelay, Is.EqualTo(expectedDelay)); - }); + } } [Test] @@ -48,11 +48,11 @@ public void TryGetRetryDelay_WhenSourceReturnsFalse_ReturnsFalse() var result = strategy.TryGetRetryDelay(TimeSpan.Zero, 0, out var actualDelay); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.False); Assert.That(actualDelay, Is.Default); - }); + } } } } diff --git a/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/NoneStrategyTests.cs b/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/NoneStrategyTests.cs index 74b5dbc..37e48e8 100644 --- a/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/NoneStrategyTests.cs +++ b/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/NoneStrategyTests.cs @@ -33,11 +33,11 @@ public void TryGetRetryDelay_ReturnsFalseAndDefaultDelay(uint attempts, int elap var result = NoneStrategy.Instance.TryGetRetryDelay(elapsed, attempts, out var delay); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.False); Assert.That(delay, Is.Default); - }); + } } } } diff --git a/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/UniformStrategyTests.cs b/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/UniformStrategyTests.cs index d0d0289..c1c8a84 100644 --- a/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/UniformStrategyTests.cs +++ b/tests/Kampute.HttpClient.Test/RetryManagement/Strategies/UniformStrategyTests.cs @@ -27,11 +27,11 @@ public void TryGetRetryDelay_ReturnsExpectedDelay(uint attempts, int delayMillis var result = strategy.TryGetRetryDelay(TimeSpan.Zero, attempts, out var actualDelay); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.True); Assert.That(actualDelay, Is.EqualTo(expectedDelay)); - }); + } } [TestCase(0u)] @@ -44,11 +44,11 @@ public void TryGetRetryDelay_IgnoresElapsedAndAttempts(uint attempts) var result = strategy.TryGetRetryDelay(TimeSpan.FromHours(1), attempts, out var actualDelay); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(result, Is.True); Assert.That(actualDelay, Is.EqualTo(expectedDelay)); - }); + } } } } diff --git a/tests/Kampute.HttpClient.Test/TestHelpers/TestStream.cs b/tests/Kampute.HttpClient.Test/TestHelpers/TestStream.cs new file mode 100644 index 0000000..cedf9df --- /dev/null +++ b/tests/Kampute.HttpClient.Test/TestHelpers/TestStream.cs @@ -0,0 +1,18 @@ +namespace Kampute.HttpClient.Test.TestHelpers +{ + using System; + using System.IO; + + internal class TestStream : MemoryStream + { + private readonly bool seekable; + + public TestStream(bool seekable) : base() => this.seekable = seekable; + + public override bool CanSeek => seekable; + + public override long Seek(long offset, SeekOrigin loc) => seekable + ? base.Seek(offset, loc) + : throw new NotSupportedException(); + } +} diff --git a/tests/Kampute.HttpClient.Test/Utilities/AsyncUpdateThrottleTests.cs b/tests/Kampute.HttpClient.Test/Utilities/AsyncUpdateThrottleTests.cs index a15f3a0..6b3dbe4 100644 --- a/tests/Kampute.HttpClient.Test/Utilities/AsyncUpdateThrottleTests.cs +++ b/tests/Kampute.HttpClient.Test/Utilities/AsyncUpdateThrottleTests.cs @@ -22,11 +22,11 @@ public async Task TryUpdateAsync_UpdatesValue() var updateResult = await synchronizer.TryUpdateAsync(() => Task.FromResult(42)); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(updateResult, Is.True); Assert.That(synchronizer.Value, Is.EqualTo(42)); - }); + } } [Test] @@ -48,12 +48,12 @@ public async Task TryUpdateAsync_DoesNotUpdateIfAnotherUpdateHasCompleted() }) ); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(results[0], Is.True); Assert.That(results[1], Is.False); Assert.That(synchronizer.Value, Is.EqualTo(2)); - }); + } } } } diff --git a/tests/Kampute.HttpClient.Test/Utilities/FlyweightCacheTests.cs b/tests/Kampute.HttpClient.Test/Utilities/FlyweightCacheTests.cs index 4417fe2..c406d3f 100644 --- a/tests/Kampute.HttpClient.Test/Utilities/FlyweightCacheTests.cs +++ b/tests/Kampute.HttpClient.Test/Utilities/FlyweightCacheTests.cs @@ -26,11 +26,11 @@ public void Get_WhenKeyExists_ReturnsExistingValue() var result1 = cache.Get(1); var result2 = cache.Get(1); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(cache.Count, Is.EqualTo(1)); Assert.That(result2, Is.SameAs(result1)); - }); + } } [Test] diff --git a/tests/Kampute.HttpClient.Test/Utilities/ScopedCollectionTests.cs b/tests/Kampute.HttpClient.Test/Utilities/ScopedCollectionTests.cs index 5ee23ae..7f35f97 100644 --- a/tests/Kampute.HttpClient.Test/Utilities/ScopedCollectionTests.cs +++ b/tests/Kampute.HttpClient.Test/Utilities/ScopedCollectionTests.cs @@ -17,11 +17,11 @@ public void BeginScope_ReturnsScopeWithProperties() using var scope = context.BeginScope(items); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(scope, Is.Not.Null); Assert.That(context, Is.EqualTo(items)); - }); + } } [Test] @@ -113,11 +113,11 @@ public async Task BeginScope_AsyncOperations_IsolatesScopes() var results = await Task.WhenAll(task1, task2); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(results[0], Is.EquivalentTo(expectedProperties1)); Assert.That(results[1], Is.EquivalentTo(expectedProperties2)); - }); + } } } } diff --git a/tests/Kampute.HttpClient.Test/Utilities/SharedDisposableTests.cs b/tests/Kampute.HttpClient.Test/Utilities/SharedDisposableTests.cs index 6b50008..d74a60f 100644 --- a/tests/Kampute.HttpClient.Test/Utilities/SharedDisposableTests.cs +++ b/tests/Kampute.HttpClient.Test/Utilities/SharedDisposableTests.cs @@ -22,11 +22,11 @@ public void SharedDisposable_DefaultConstructor_CreatesResource_OnFirstReference using var reference1 = sharedDisposable.AcquireReference(); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(sharedDisposable.ReferenceCount, Is.EqualTo(1)); Assert.That(reference1.Instance, Is.Not.Null); - }); + } } [Test] @@ -42,11 +42,11 @@ public void SharedDisposable_FactoryConstructor_CreatesResource_OnFirstReference using var reference1 = sharedDisposable.AcquireReference(); using var reference2 = sharedDisposable.AcquireReference(); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(sharedDisposable.ReferenceCount, Is.EqualTo(2)); Assert.That(factoryInvoked, Is.EqualTo(1)); - }); + } } [Test] @@ -61,18 +61,18 @@ public void SharedDisposable_DisposesResource_OnLastRelease() Assert.That(sharedDisposable.ReferenceCount, Is.EqualTo(2)); reference1.Dispose(); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(sharedDisposable.ReferenceCount, Is.EqualTo(1)); Assert.That(instance.IsDisposed, Is.False); - }); + } reference2.Dispose(); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { - Assert.That(sharedDisposable.ReferenceCount, Is.EqualTo(0)); + Assert.That(sharedDisposable.ReferenceCount, Is.Zero); Assert.That(instance.IsDisposed, Is.True); - }); + } } [Test] @@ -99,11 +99,11 @@ public void SharedDisposable_MaintainsCorrectReferenceCount_OnConcurrentAcquireA foreach (var thread in threads) thread.Join(); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(sharedDisposable.ReferenceCount, Is.Zero); Assert.That(createdCount, Is.EqualTo(numberOfThreads)); - }); + } } } } diff --git a/tests/Kampute.HttpClient.Xml.Test/HttpRestClientXmlExtensionsTests.cs b/tests/Kampute.HttpClient.Xml.Test/HttpRestClientXmlExtensionsTests.cs index 01b07af..c892894 100644 --- a/tests/Kampute.HttpClient.Xml.Test/HttpRestClientXmlExtensionsTests.cs +++ b/tests/Kampute.HttpClient.Xml.Test/HttpRestClientXmlExtensionsTests.cs @@ -62,12 +62,12 @@ public async Task PostAsXmlAsync_InvokesHttpClientCorrectly() MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Post)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/echo"))); Assert.That(request.Content?.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.Xml)); - }); + } return new HttpResponseMessage { @@ -89,12 +89,12 @@ public async Task PutAsXmlAsync_InvokesHttpClientCorrectly() MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Put)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/echo"))); Assert.That(request.Content?.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.Xml)); - }); + } return new HttpResponseMessage { @@ -116,12 +116,12 @@ public async Task PatchAsXmlAsync_InvokesHttpClientCorrectly() MockHttpResponse(request => { - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(request.Method, Is.EqualTo(HttpMethod.Patch)); Assert.That(request.RequestUri, Is.EqualTo(AbsoluteUrl("/echo"))); Assert.That(request.Content?.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.Xml)); - }); + } return new HttpResponseMessage { diff --git a/tests/Kampute.HttpClient.Xml.Test/Kampute.HttpClient.Xml.Test.csproj b/tests/Kampute.HttpClient.Xml.Test/Kampute.HttpClient.Xml.Test.csproj index 233ce5d..b1aa371 100644 --- a/tests/Kampute.HttpClient.Xml.Test/Kampute.HttpClient.Xml.Test.csproj +++ b/tests/Kampute.HttpClient.Xml.Test/Kampute.HttpClient.Xml.Test.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 false true latest @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Kampute.HttpClient.Xml.Test/XmlContentTests.cs b/tests/Kampute.HttpClient.Xml.Test/XmlContentTests.cs index 286c620..e26c4c9 100644 --- a/tests/Kampute.HttpClient.Xml.Test/XmlContentTests.cs +++ b/tests/Kampute.HttpClient.Xml.Test/XmlContentTests.cs @@ -17,12 +17,12 @@ public async Task WithDefaultEncoding_SetsContentCorrectly() var xmlString = await xmlContent.ReadAsStringAsync(); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(xmlString, Is.EqualTo(expectedString)); Assert.That(xmlContent.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.Xml)); Assert.That(xmlContent.Headers.ContentType?.CharSet, Is.EqualTo(Encoding.UTF8.WebName)); - }); + } } [Test] @@ -36,12 +36,12 @@ public async Task WithCustomEncoding_SetsContentCorrectly() var xmlString = await xmlContent.ReadAsStringAsync(); - Assert.Multiple(() => + using (Assert.EnterMultipleScope()) { Assert.That(xmlString, Is.EqualTo(expectedString)); Assert.That(xmlContent.Headers.ContentType?.MediaType, Is.EqualTo(MediaTypeNames.Application.Xml)); Assert.That(xmlContent.Headers.ContentType?.CharSet, Is.EqualTo(encoding.WebName)); - }); + } } } } From d2b19c3acac15c88273029040536157c0e7cbfaa Mon Sep 17 00:00:00 2001 From: Kambiz Khojasteh Date: Sun, 21 Dec 2025 20:03:29 +0800 Subject: [PATCH 3/5] Update workflow to .NET 10 and set keep_files to false Switched GitHub Actions workflow to use .NET SDK 10.0.x. Set keep_files: false in gh-pages deploy to remove old files. --- .github/workflows/main.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ae8ad23..38d78dc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,10 +17,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Setup .NET 8.0 + - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: '8.0.x' + dotnet-version: '10.0.x' - name: Install Kampose run: dotnet tool install --global kampose @@ -43,3 +43,4 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./.site + keep_files: false From cbe25cc31a1de2c8b508f4d80d056b8405c10ffc Mon Sep 17 00:00:00 2001 From: Kambiz Khojasteh Date: Sun, 21 Dec 2025 20:21:44 +0800 Subject: [PATCH 4/5] Update copyright year to 2025 in all source files --- .../HttpRestClientXmlExtensions.cs | 2 +- src/Kampute.HttpClient.DataContract/NamespaceDoc.cs | 2 +- src/Kampute.HttpClient.DataContract/XmlContent.cs | 2 +- src/Kampute.HttpClient.DataContract/XmlContentDeserializer.cs | 2 +- src/Kampute.HttpClient.Json/HttpRestClientJsonExtensions.cs | 2 +- src/Kampute.HttpClient.Json/JsonContent.cs | 2 +- src/Kampute.HttpClient.Json/JsonContentDeserializer.cs | 2 +- src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj | 2 +- src/Kampute.HttpClient.Json/NamespaceDoc.cs | 2 +- .../HttpRestClientJsonExtensions.cs | 2 +- src/Kampute.HttpClient.NewtonsoftJson/JsonContent.cs | 2 +- .../JsonContentDeserializer.cs | 2 +- src/Kampute.HttpClient.NewtonsoftJson/NamespaceDoc.cs | 2 +- src/Kampute.HttpClient.Xml/HttpRestClientXmlExtensions.cs | 2 +- src/Kampute.HttpClient.Xml/NamespaceDoc.cs | 2 +- src/Kampute.HttpClient.Xml/XmlContent.cs | 2 +- src/Kampute.HttpClient.Xml/XmlContentDeserializer.cs | 2 +- src/Kampute.HttpClient/AuthSchemes.cs | 2 +- src/Kampute.HttpClient/BackoffStrategies.cs | 2 +- src/Kampute.HttpClient/Content/Abstracts/NamespaceDoc.cs | 2 +- .../Content/Compression/Abstracts/NamespaceDoc.cs | 2 +- src/Kampute.HttpClient/Content/Compression/NamespaceDoc.cs | 2 +- src/Kampute.HttpClient/Content/NamespaceDoc.cs | 2 +- src/Kampute.HttpClient/ErrorHandlers/Abstracts/NamespaceDoc.cs | 2 +- .../ErrorHandlers/Abstracts/RetryableHttpErrorHandler.cs | 2 +- src/Kampute.HttpClient/ErrorHandlers/DynamicHttpErrorHandler.cs | 2 +- src/Kampute.HttpClient/ErrorHandlers/HttpError401Handler.cs | 2 +- src/Kampute.HttpClient/ErrorHandlers/HttpError429Handler.cs | 2 +- src/Kampute.HttpClient/ErrorHandlers/HttpError503Handler.cs | 2 +- src/Kampute.HttpClient/ErrorHandlers/NamespaceDoc.cs | 2 +- .../ErrorHandlers/TransientHttpErrorHandler.cs | 2 +- src/Kampute.HttpClient/HttpContentDeserializerCollection.cs | 2 +- src/Kampute.HttpClient/HttpContentException.cs | 2 +- src/Kampute.HttpClient/HttpContentExtensions.cs | 2 +- src/Kampute.HttpClient/HttpErrorHandlerCollection.cs | 2 +- src/Kampute.HttpClient/HttpErrorHandlerResult.cs | 2 +- src/Kampute.HttpClient/HttpRequestErrorContext.cs | 2 +- src/Kampute.HttpClient/HttpRequestMessageCloneManager.cs | 2 +- src/Kampute.HttpClient/HttpRequestMessageEventArgs.cs | 2 +- src/Kampute.HttpClient/HttpRequestMessageExtensions.cs | 2 +- src/Kampute.HttpClient/HttpRequestMessagePropertyKeys.cs | 2 +- src/Kampute.HttpClient/HttpResponseErrorContext.cs | 2 +- src/Kampute.HttpClient/HttpResponseException.cs | 2 +- src/Kampute.HttpClient/HttpResponseHeadersExtensions.cs | 2 +- src/Kampute.HttpClient/HttpResponseMessageEventArgs.cs | 2 +- src/Kampute.HttpClient/HttpRestClient.cs | 2 +- src/Kampute.HttpClient/HttpRestClientExtensions.cs | 2 +- src/Kampute.HttpClient/HttpRestClientFormExtensions.cs | 2 +- src/Kampute.HttpClient/HttpVerb.cs | 2 +- src/Kampute.HttpClient/Interfaces/IHttpBackoffProvider.cs | 2 +- src/Kampute.HttpClient/Interfaces/IHttpContentDeserializer.cs | 2 +- src/Kampute.HttpClient/Interfaces/IHttpErrorHandler.cs | 2 +- src/Kampute.HttpClient/Interfaces/IHttpErrorResponse.cs | 2 +- src/Kampute.HttpClient/Interfaces/IRetryScheduler.cs | 2 +- src/Kampute.HttpClient/Interfaces/NamespaceDoc.cs | 2 +- src/Kampute.HttpClient/MediaTypeNames.cs | 2 +- src/Kampute.HttpClient/NamespaceDoc.cs | 2 +- src/Kampute.HttpClient/RetryManagement/BackoffStrategy.cs | 2 +- .../RetryManagement/DynamicBackoffStrategy.cs | 2 +- src/Kampute.HttpClient/RetryManagement/NamespaceDoc.cs | 2 +- src/Kampute.HttpClient/RetryManagement/RetryScheduler.cs | 2 +- .../RetryManagement/Strategies/ExponentialStrategy.cs | 2 +- .../RetryManagement/Strategies/FibonacciStrategy.cs | 2 +- .../RetryManagement/Strategies/LinearStrategy.cs | 2 +- .../RetryManagement/Strategies/Modifiers/NamespaceDoc.cs | 2 +- .../RetryManagement/Strategies/NamespaceDoc.cs | 2 +- .../RetryManagement/Strategies/NoneStrategy.cs | 2 +- .../RetryManagement/Strategies/UniformStrategy.cs | 2 +- src/Kampute.HttpClient/Utilities/ExceptionExtensions.cs | 2 +- src/Kampute.HttpClient/Utilities/NamespaceDoc.cs | 2 +- src/Kampute.HttpClient/Utilities/SharedDisposable.cs | 2 +- .../ErrorHandlers/TransientHttpErrorHandlerTests.cs | 2 +- 72 files changed, 72 insertions(+), 72 deletions(-) diff --git a/src/Kampute.HttpClient.DataContract/HttpRestClientXmlExtensions.cs b/src/Kampute.HttpClient.DataContract/HttpRestClientXmlExtensions.cs index cbade5f..1f696de 100644 --- a/src/Kampute.HttpClient.DataContract/HttpRestClientXmlExtensions.cs +++ b/src/Kampute.HttpClient.DataContract/HttpRestClientXmlExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient.DataContract package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient.DataContract/NamespaceDoc.cs b/src/Kampute.HttpClient.DataContract/NamespaceDoc.cs index af65811..3c4dfd0 100644 --- a/src/Kampute.HttpClient.DataContract/NamespaceDoc.cs +++ b/src/Kampute.HttpClient.DataContract/NamespaceDoc.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient.DataContract/XmlContent.cs b/src/Kampute.HttpClient.DataContract/XmlContent.cs index d0be6e5..0c76a93 100644 --- a/src/Kampute.HttpClient.DataContract/XmlContent.cs +++ b/src/Kampute.HttpClient.DataContract/XmlContent.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient.DataContract package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient.DataContract/XmlContentDeserializer.cs b/src/Kampute.HttpClient.DataContract/XmlContentDeserializer.cs index eb51a92..c501122 100644 --- a/src/Kampute.HttpClient.DataContract/XmlContentDeserializer.cs +++ b/src/Kampute.HttpClient.DataContract/XmlContentDeserializer.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient.DataContract package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient.Json/HttpRestClientJsonExtensions.cs b/src/Kampute.HttpClient.Json/HttpRestClientJsonExtensions.cs index bdcfe5b..288f4d3 100644 --- a/src/Kampute.HttpClient.Json/HttpRestClientJsonExtensions.cs +++ b/src/Kampute.HttpClient.Json/HttpRestClientJsonExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient.Json package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient.Json/JsonContent.cs b/src/Kampute.HttpClient.Json/JsonContent.cs index 8dac131..2f5e08c 100644 --- a/src/Kampute.HttpClient.Json/JsonContent.cs +++ b/src/Kampute.HttpClient.Json/JsonContent.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient.Json package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient.Json/JsonContentDeserializer.cs b/src/Kampute.HttpClient.Json/JsonContentDeserializer.cs index 6751bc9..56c1c45 100644 --- a/src/Kampute.HttpClient.Json/JsonContentDeserializer.cs +++ b/src/Kampute.HttpClient.Json/JsonContentDeserializer.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient.Json package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj b/src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj index 573eb84..57d2a54 100644 --- a/src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj +++ b/src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj @@ -7,7 +7,7 @@ Kambiz Khojasteh 2.4.0 Kampute - Copyright (c) 2024 Kampute + Copyright (c) 2025 Kampute latest enable true diff --git a/src/Kampute.HttpClient.Json/NamespaceDoc.cs b/src/Kampute.HttpClient.Json/NamespaceDoc.cs index 084c71f..4c03a31 100644 --- a/src/Kampute.HttpClient.Json/NamespaceDoc.cs +++ b/src/Kampute.HttpClient.Json/NamespaceDoc.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient.NewtonsoftJson/HttpRestClientJsonExtensions.cs b/src/Kampute.HttpClient.NewtonsoftJson/HttpRestClientJsonExtensions.cs index e0e780f..c6894d5 100644 --- a/src/Kampute.HttpClient.NewtonsoftJson/HttpRestClientJsonExtensions.cs +++ b/src/Kampute.HttpClient.NewtonsoftJson/HttpRestClientJsonExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient.NewtonsoftJson package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient.NewtonsoftJson/JsonContent.cs b/src/Kampute.HttpClient.NewtonsoftJson/JsonContent.cs index 1fbfb6d..c714dc0 100644 --- a/src/Kampute.HttpClient.NewtonsoftJson/JsonContent.cs +++ b/src/Kampute.HttpClient.NewtonsoftJson/JsonContent.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient.NewtonsoftJson package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient.NewtonsoftJson/JsonContentDeserializer.cs b/src/Kampute.HttpClient.NewtonsoftJson/JsonContentDeserializer.cs index 3de04ad..8135531 100644 --- a/src/Kampute.HttpClient.NewtonsoftJson/JsonContentDeserializer.cs +++ b/src/Kampute.HttpClient.NewtonsoftJson/JsonContentDeserializer.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient.NewtonsoftJson package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient.NewtonsoftJson/NamespaceDoc.cs b/src/Kampute.HttpClient.NewtonsoftJson/NamespaceDoc.cs index 6af8af3..afb947e 100644 --- a/src/Kampute.HttpClient.NewtonsoftJson/NamespaceDoc.cs +++ b/src/Kampute.HttpClient.NewtonsoftJson/NamespaceDoc.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient.Xml/HttpRestClientXmlExtensions.cs b/src/Kampute.HttpClient.Xml/HttpRestClientXmlExtensions.cs index a178418..c0f8c9f 100644 --- a/src/Kampute.HttpClient.Xml/HttpRestClientXmlExtensions.cs +++ b/src/Kampute.HttpClient.Xml/HttpRestClientXmlExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient.Xml package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient.Xml/NamespaceDoc.cs b/src/Kampute.HttpClient.Xml/NamespaceDoc.cs index e04ff5e..c55a2d8 100644 --- a/src/Kampute.HttpClient.Xml/NamespaceDoc.cs +++ b/src/Kampute.HttpClient.Xml/NamespaceDoc.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient.Xml/XmlContent.cs b/src/Kampute.HttpClient.Xml/XmlContent.cs index be8ec7a..41bcf09 100644 --- a/src/Kampute.HttpClient.Xml/XmlContent.cs +++ b/src/Kampute.HttpClient.Xml/XmlContent.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient.Xml package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient.Xml/XmlContentDeserializer.cs b/src/Kampute.HttpClient.Xml/XmlContentDeserializer.cs index adb495a..7ac0339 100644 --- a/src/Kampute.HttpClient.Xml/XmlContentDeserializer.cs +++ b/src/Kampute.HttpClient.Xml/XmlContentDeserializer.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient.Xml package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/AuthSchemes.cs b/src/Kampute.HttpClient/AuthSchemes.cs index 61c3706..f37718a 100644 --- a/src/Kampute.HttpClient/AuthSchemes.cs +++ b/src/Kampute.HttpClient/AuthSchemes.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/BackoffStrategies.cs b/src/Kampute.HttpClient/BackoffStrategies.cs index e57fdf4..e305cae 100644 --- a/src/Kampute.HttpClient/BackoffStrategies.cs +++ b/src/Kampute.HttpClient/BackoffStrategies.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/Content/Abstracts/NamespaceDoc.cs b/src/Kampute.HttpClient/Content/Abstracts/NamespaceDoc.cs index 339a989..3e78d72 100644 --- a/src/Kampute.HttpClient/Content/Abstracts/NamespaceDoc.cs +++ b/src/Kampute.HttpClient/Content/Abstracts/NamespaceDoc.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/Content/Compression/Abstracts/NamespaceDoc.cs b/src/Kampute.HttpClient/Content/Compression/Abstracts/NamespaceDoc.cs index 97f16df..8fcf83d 100644 --- a/src/Kampute.HttpClient/Content/Compression/Abstracts/NamespaceDoc.cs +++ b/src/Kampute.HttpClient/Content/Compression/Abstracts/NamespaceDoc.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/Content/Compression/NamespaceDoc.cs b/src/Kampute.HttpClient/Content/Compression/NamespaceDoc.cs index 8eff1fd..e915de3 100644 --- a/src/Kampute.HttpClient/Content/Compression/NamespaceDoc.cs +++ b/src/Kampute.HttpClient/Content/Compression/NamespaceDoc.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/Content/NamespaceDoc.cs b/src/Kampute.HttpClient/Content/NamespaceDoc.cs index f3c416d..5808a4f 100644 --- a/src/Kampute.HttpClient/Content/NamespaceDoc.cs +++ b/src/Kampute.HttpClient/Content/NamespaceDoc.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/ErrorHandlers/Abstracts/NamespaceDoc.cs b/src/Kampute.HttpClient/ErrorHandlers/Abstracts/NamespaceDoc.cs index 5f2e554..1ef59a7 100644 --- a/src/Kampute.HttpClient/ErrorHandlers/Abstracts/NamespaceDoc.cs +++ b/src/Kampute.HttpClient/ErrorHandlers/Abstracts/NamespaceDoc.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/ErrorHandlers/Abstracts/RetryableHttpErrorHandler.cs b/src/Kampute.HttpClient/ErrorHandlers/Abstracts/RetryableHttpErrorHandler.cs index 087eda3..eef599d 100644 --- a/src/Kampute.HttpClient/ErrorHandlers/Abstracts/RetryableHttpErrorHandler.cs +++ b/src/Kampute.HttpClient/ErrorHandlers/Abstracts/RetryableHttpErrorHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/ErrorHandlers/DynamicHttpErrorHandler.cs b/src/Kampute.HttpClient/ErrorHandlers/DynamicHttpErrorHandler.cs index 7635108..9328361 100644 --- a/src/Kampute.HttpClient/ErrorHandlers/DynamicHttpErrorHandler.cs +++ b/src/Kampute.HttpClient/ErrorHandlers/DynamicHttpErrorHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/ErrorHandlers/HttpError401Handler.cs b/src/Kampute.HttpClient/ErrorHandlers/HttpError401Handler.cs index 44638bb..5bd7ce6 100644 --- a/src/Kampute.HttpClient/ErrorHandlers/HttpError401Handler.cs +++ b/src/Kampute.HttpClient/ErrorHandlers/HttpError401Handler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/ErrorHandlers/HttpError429Handler.cs b/src/Kampute.HttpClient/ErrorHandlers/HttpError429Handler.cs index 231c830..8f5269c 100644 --- a/src/Kampute.HttpClient/ErrorHandlers/HttpError429Handler.cs +++ b/src/Kampute.HttpClient/ErrorHandlers/HttpError429Handler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/ErrorHandlers/HttpError503Handler.cs b/src/Kampute.HttpClient/ErrorHandlers/HttpError503Handler.cs index e200e6d..640bf11 100644 --- a/src/Kampute.HttpClient/ErrorHandlers/HttpError503Handler.cs +++ b/src/Kampute.HttpClient/ErrorHandlers/HttpError503Handler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/ErrorHandlers/NamespaceDoc.cs b/src/Kampute.HttpClient/ErrorHandlers/NamespaceDoc.cs index 5c8f3f2..d4047c9 100644 --- a/src/Kampute.HttpClient/ErrorHandlers/NamespaceDoc.cs +++ b/src/Kampute.HttpClient/ErrorHandlers/NamespaceDoc.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/ErrorHandlers/TransientHttpErrorHandler.cs b/src/Kampute.HttpClient/ErrorHandlers/TransientHttpErrorHandler.cs index 6632176..76f056f 100644 --- a/src/Kampute.HttpClient/ErrorHandlers/TransientHttpErrorHandler.cs +++ b/src/Kampute.HttpClient/ErrorHandlers/TransientHttpErrorHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/HttpContentDeserializerCollection.cs b/src/Kampute.HttpClient/HttpContentDeserializerCollection.cs index 4101158..d38f0dd 100644 --- a/src/Kampute.HttpClient/HttpContentDeserializerCollection.cs +++ b/src/Kampute.HttpClient/HttpContentDeserializerCollection.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/HttpContentException.cs b/src/Kampute.HttpClient/HttpContentException.cs index 1b0e42d..72295d9 100644 --- a/src/Kampute.HttpClient/HttpContentException.cs +++ b/src/Kampute.HttpClient/HttpContentException.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/HttpContentExtensions.cs b/src/Kampute.HttpClient/HttpContentExtensions.cs index cf49f83..4c53ebd 100644 --- a/src/Kampute.HttpClient/HttpContentExtensions.cs +++ b/src/Kampute.HttpClient/HttpContentExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/HttpErrorHandlerCollection.cs b/src/Kampute.HttpClient/HttpErrorHandlerCollection.cs index c00826a..77f5ebb 100644 --- a/src/Kampute.HttpClient/HttpErrorHandlerCollection.cs +++ b/src/Kampute.HttpClient/HttpErrorHandlerCollection.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/HttpErrorHandlerResult.cs b/src/Kampute.HttpClient/HttpErrorHandlerResult.cs index 2b0b3f2..b3acceb 100644 --- a/src/Kampute.HttpClient/HttpErrorHandlerResult.cs +++ b/src/Kampute.HttpClient/HttpErrorHandlerResult.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/HttpRequestErrorContext.cs b/src/Kampute.HttpClient/HttpRequestErrorContext.cs index d139fd8..538ddb3 100644 --- a/src/Kampute.HttpClient/HttpRequestErrorContext.cs +++ b/src/Kampute.HttpClient/HttpRequestErrorContext.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/HttpRequestMessageCloneManager.cs b/src/Kampute.HttpClient/HttpRequestMessageCloneManager.cs index 4ae7e6a..7c3d021 100644 --- a/src/Kampute.HttpClient/HttpRequestMessageCloneManager.cs +++ b/src/Kampute.HttpClient/HttpRequestMessageCloneManager.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/HttpRequestMessageEventArgs.cs b/src/Kampute.HttpClient/HttpRequestMessageEventArgs.cs index 80e3612..b565bf0 100644 --- a/src/Kampute.HttpClient/HttpRequestMessageEventArgs.cs +++ b/src/Kampute.HttpClient/HttpRequestMessageEventArgs.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/HttpRequestMessageExtensions.cs b/src/Kampute.HttpClient/HttpRequestMessageExtensions.cs index 4c3fa56..711ab3f 100644 --- a/src/Kampute.HttpClient/HttpRequestMessageExtensions.cs +++ b/src/Kampute.HttpClient/HttpRequestMessageExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/HttpRequestMessagePropertyKeys.cs b/src/Kampute.HttpClient/HttpRequestMessagePropertyKeys.cs index cf7bedd..a357d26 100644 --- a/src/Kampute.HttpClient/HttpRequestMessagePropertyKeys.cs +++ b/src/Kampute.HttpClient/HttpRequestMessagePropertyKeys.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/HttpResponseErrorContext.cs b/src/Kampute.HttpClient/HttpResponseErrorContext.cs index 94baf26..9e6d69b 100644 --- a/src/Kampute.HttpClient/HttpResponseErrorContext.cs +++ b/src/Kampute.HttpClient/HttpResponseErrorContext.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/HttpResponseException.cs b/src/Kampute.HttpClient/HttpResponseException.cs index a3b419b..7dd0bab 100644 --- a/src/Kampute.HttpClient/HttpResponseException.cs +++ b/src/Kampute.HttpClient/HttpResponseException.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/HttpResponseHeadersExtensions.cs b/src/Kampute.HttpClient/HttpResponseHeadersExtensions.cs index 4d1cd93..4565378 100644 --- a/src/Kampute.HttpClient/HttpResponseHeadersExtensions.cs +++ b/src/Kampute.HttpClient/HttpResponseHeadersExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/HttpResponseMessageEventArgs.cs b/src/Kampute.HttpClient/HttpResponseMessageEventArgs.cs index 99285fa..ed8bb07 100644 --- a/src/Kampute.HttpClient/HttpResponseMessageEventArgs.cs +++ b/src/Kampute.HttpClient/HttpResponseMessageEventArgs.cs @@ -1,5 +1,5 @@  -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/HttpRestClient.cs b/src/Kampute.HttpClient/HttpRestClient.cs index db6d1fa..566a532 100644 --- a/src/Kampute.HttpClient/HttpRestClient.cs +++ b/src/Kampute.HttpClient/HttpRestClient.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/HttpRestClientExtensions.cs b/src/Kampute.HttpClient/HttpRestClientExtensions.cs index 8af375d..967f475 100644 --- a/src/Kampute.HttpClient/HttpRestClientExtensions.cs +++ b/src/Kampute.HttpClient/HttpRestClientExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/HttpRestClientFormExtensions.cs b/src/Kampute.HttpClient/HttpRestClientFormExtensions.cs index 1c65218..358626f 100644 --- a/src/Kampute.HttpClient/HttpRestClientFormExtensions.cs +++ b/src/Kampute.HttpClient/HttpRestClientFormExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/HttpVerb.cs b/src/Kampute.HttpClient/HttpVerb.cs index 6a6870b..31d0db0 100644 --- a/src/Kampute.HttpClient/HttpVerb.cs +++ b/src/Kampute.HttpClient/HttpVerb.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/Interfaces/IHttpBackoffProvider.cs b/src/Kampute.HttpClient/Interfaces/IHttpBackoffProvider.cs index f438ce7..405e2f1 100644 --- a/src/Kampute.HttpClient/Interfaces/IHttpBackoffProvider.cs +++ b/src/Kampute.HttpClient/Interfaces/IHttpBackoffProvider.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/Interfaces/IHttpContentDeserializer.cs b/src/Kampute.HttpClient/Interfaces/IHttpContentDeserializer.cs index fb4447f..ebff42b 100644 --- a/src/Kampute.HttpClient/Interfaces/IHttpContentDeserializer.cs +++ b/src/Kampute.HttpClient/Interfaces/IHttpContentDeserializer.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/Interfaces/IHttpErrorHandler.cs b/src/Kampute.HttpClient/Interfaces/IHttpErrorHandler.cs index c6532bc..c392a8b 100644 --- a/src/Kampute.HttpClient/Interfaces/IHttpErrorHandler.cs +++ b/src/Kampute.HttpClient/Interfaces/IHttpErrorHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/Interfaces/IHttpErrorResponse.cs b/src/Kampute.HttpClient/Interfaces/IHttpErrorResponse.cs index 547442e..5e0f7f7 100644 --- a/src/Kampute.HttpClient/Interfaces/IHttpErrorResponse.cs +++ b/src/Kampute.HttpClient/Interfaces/IHttpErrorResponse.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/Interfaces/IRetryScheduler.cs b/src/Kampute.HttpClient/Interfaces/IRetryScheduler.cs index cda146d..26e8101 100644 --- a/src/Kampute.HttpClient/Interfaces/IRetryScheduler.cs +++ b/src/Kampute.HttpClient/Interfaces/IRetryScheduler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/Interfaces/NamespaceDoc.cs b/src/Kampute.HttpClient/Interfaces/NamespaceDoc.cs index eb400b5..97258b9 100644 --- a/src/Kampute.HttpClient/Interfaces/NamespaceDoc.cs +++ b/src/Kampute.HttpClient/Interfaces/NamespaceDoc.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/MediaTypeNames.cs b/src/Kampute.HttpClient/MediaTypeNames.cs index b915705..333eb60 100644 --- a/src/Kampute.HttpClient/MediaTypeNames.cs +++ b/src/Kampute.HttpClient/MediaTypeNames.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/NamespaceDoc.cs b/src/Kampute.HttpClient/NamespaceDoc.cs index 94d260d..7e3b069 100644 --- a/src/Kampute.HttpClient/NamespaceDoc.cs +++ b/src/Kampute.HttpClient/NamespaceDoc.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/RetryManagement/BackoffStrategy.cs b/src/Kampute.HttpClient/RetryManagement/BackoffStrategy.cs index 864d0be..1ee5322 100644 --- a/src/Kampute.HttpClient/RetryManagement/BackoffStrategy.cs +++ b/src/Kampute.HttpClient/RetryManagement/BackoffStrategy.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/RetryManagement/DynamicBackoffStrategy.cs b/src/Kampute.HttpClient/RetryManagement/DynamicBackoffStrategy.cs index c54806e..ae5bc2a 100644 --- a/src/Kampute.HttpClient/RetryManagement/DynamicBackoffStrategy.cs +++ b/src/Kampute.HttpClient/RetryManagement/DynamicBackoffStrategy.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/RetryManagement/NamespaceDoc.cs b/src/Kampute.HttpClient/RetryManagement/NamespaceDoc.cs index 00a547d..358ce62 100644 --- a/src/Kampute.HttpClient/RetryManagement/NamespaceDoc.cs +++ b/src/Kampute.HttpClient/RetryManagement/NamespaceDoc.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/RetryManagement/RetryScheduler.cs b/src/Kampute.HttpClient/RetryManagement/RetryScheduler.cs index b0a4ae8..907840b 100644 --- a/src/Kampute.HttpClient/RetryManagement/RetryScheduler.cs +++ b/src/Kampute.HttpClient/RetryManagement/RetryScheduler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/RetryManagement/Strategies/ExponentialStrategy.cs b/src/Kampute.HttpClient/RetryManagement/Strategies/ExponentialStrategy.cs index c583730..277a28b 100644 --- a/src/Kampute.HttpClient/RetryManagement/Strategies/ExponentialStrategy.cs +++ b/src/Kampute.HttpClient/RetryManagement/Strategies/ExponentialStrategy.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/RetryManagement/Strategies/FibonacciStrategy.cs b/src/Kampute.HttpClient/RetryManagement/Strategies/FibonacciStrategy.cs index 0e4f322..8400c7e 100644 --- a/src/Kampute.HttpClient/RetryManagement/Strategies/FibonacciStrategy.cs +++ b/src/Kampute.HttpClient/RetryManagement/Strategies/FibonacciStrategy.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/RetryManagement/Strategies/LinearStrategy.cs b/src/Kampute.HttpClient/RetryManagement/Strategies/LinearStrategy.cs index bdb80cd..bafaa5f 100644 --- a/src/Kampute.HttpClient/RetryManagement/Strategies/LinearStrategy.cs +++ b/src/Kampute.HttpClient/RetryManagement/Strategies/LinearStrategy.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/RetryManagement/Strategies/Modifiers/NamespaceDoc.cs b/src/Kampute.HttpClient/RetryManagement/Strategies/Modifiers/NamespaceDoc.cs index fa8289b..c781982 100644 --- a/src/Kampute.HttpClient/RetryManagement/Strategies/Modifiers/NamespaceDoc.cs +++ b/src/Kampute.HttpClient/RetryManagement/Strategies/Modifiers/NamespaceDoc.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/RetryManagement/Strategies/NamespaceDoc.cs b/src/Kampute.HttpClient/RetryManagement/Strategies/NamespaceDoc.cs index afee57f..d6af15f 100644 --- a/src/Kampute.HttpClient/RetryManagement/Strategies/NamespaceDoc.cs +++ b/src/Kampute.HttpClient/RetryManagement/Strategies/NamespaceDoc.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/RetryManagement/Strategies/NoneStrategy.cs b/src/Kampute.HttpClient/RetryManagement/Strategies/NoneStrategy.cs index b810fbc..ca40f65 100644 --- a/src/Kampute.HttpClient/RetryManagement/Strategies/NoneStrategy.cs +++ b/src/Kampute.HttpClient/RetryManagement/Strategies/NoneStrategy.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/RetryManagement/Strategies/UniformStrategy.cs b/src/Kampute.HttpClient/RetryManagement/Strategies/UniformStrategy.cs index 7f4aafc..068a21e 100644 --- a/src/Kampute.HttpClient/RetryManagement/Strategies/UniformStrategy.cs +++ b/src/Kampute.HttpClient/RetryManagement/Strategies/UniformStrategy.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/Utilities/ExceptionExtensions.cs b/src/Kampute.HttpClient/Utilities/ExceptionExtensions.cs index 0433df2..5bed7fb 100644 --- a/src/Kampute.HttpClient/Utilities/ExceptionExtensions.cs +++ b/src/Kampute.HttpClient/Utilities/ExceptionExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/Utilities/NamespaceDoc.cs b/src/Kampute.HttpClient/Utilities/NamespaceDoc.cs index bd7de8f..3309cd9 100644 --- a/src/Kampute.HttpClient/Utilities/NamespaceDoc.cs +++ b/src/Kampute.HttpClient/Utilities/NamespaceDoc.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/src/Kampute.HttpClient/Utilities/SharedDisposable.cs b/src/Kampute.HttpClient/Utilities/SharedDisposable.cs index 012db17..225e25c 100644 --- a/src/Kampute.HttpClient/Utilities/SharedDisposable.cs +++ b/src/Kampute.HttpClient/Utilities/SharedDisposable.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. diff --git a/tests/Kampute.HttpClient.Test/ErrorHandlers/TransientHttpErrorHandlerTests.cs b/tests/Kampute.HttpClient.Test/ErrorHandlers/TransientHttpErrorHandlerTests.cs index afded15..276ffdf 100644 --- a/tests/Kampute.HttpClient.Test/ErrorHandlers/TransientHttpErrorHandlerTests.cs +++ b/tests/Kampute.HttpClient.Test/ErrorHandlers/TransientHttpErrorHandlerTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Kampute +// Copyright (C) 2025 Kampute // // This file is part of the Kampute.HttpClient package and is released under the terms of the MIT license. // See the LICENSE file in the project root for the full license text. From 1ce46b99c149d1538fdb6a844c769ed41bef47ed Mon Sep 17 00:00:00 2001 From: Kambiz Khojasteh Date: Sun, 21 Dec 2025 20:28:27 +0800 Subject: [PATCH 5/5] Bump version to 2.5.0 in project file --- src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj b/src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj index 57d2a54..1029cbc 100644 --- a/src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj +++ b/src/Kampute.HttpClient.Json/Kampute.HttpClient.Json.csproj @@ -5,7 +5,7 @@ Kampute.HttpClient.Json This package is an extension package for Kampute.HttpClient, enhancing it to manage application/json content types, using System.Text.Json library for serialization and deserialization of JSON responses and payloads. Kambiz Khojasteh - 2.4.0 + 2.5.0 Kampute Copyright (c) 2025 Kampute latest