Skip to content

feat: Add PATCH Method Support to HttpClientAdapter and HttpClientWithCache#4

Merged
akrisanov merged 2 commits intomainfrom
feat/add-patch-method-support
Sep 22, 2025
Merged

feat: Add PATCH Method Support to HttpClientAdapter and HttpClientWithCache#4
akrisanov merged 2 commits intomainfrom
feat/add-patch-method-support

Conversation

@dterenin-the-dev
Copy link
Collaborator

Description

This pull request introduces support for the HTTP PATCH method in the HttpClientAdapter and HttpClientWithCache classes within the Reliable.HttpClient library. The changes include adding new PATCH method overloads to handle typed requests and responses, raw HttpResponseMessage responses, and optional headers. Comprehensive unit tests have been added to HttpClientAdapterTests.cs, CachedHttpClientTests.cs, and HttpClientWithCacheTests.cs to verify the functionality of the PATCH methods, including request execution, response handling, header support, and cache invalidation behavior.

Changes

HttpClientAdapter

  • Added PATCH Method Support:
    • Implemented PatchAsync<TRequest, TResponse>(string, TRequest, CancellationToken) to send a PATCH request with a typed request and response, using SendWithResponseHandlerAsync with HttpMethod.Patch and JsonContent.Create.
    • Implemented PatchAsync<TRequest, TResponse>(string, TRequest, IDictionary<string, string>, CancellationToken) to include custom headers.
    • Implemented PatchAsync<TRequest>(string, TRequest, CancellationToken) to return a raw HttpResponseMessage.
    • Implemented PatchAsync<TRequest>(string, TRequest, IDictionary<string, string>, CancellationToken) to return a raw HttpResponseMessage with headers.
  • IHttpClientAdapter Interface:
    • Updated to include the new PATCH method signatures, ensuring consistency with other HTTP methods (GET, POST, PUT, DELETE).

HttpClientWithCache

  • Added PATCH Method Support:
    • Implemented PatchAsync<TRequest, TResponse>(string, TRequest, CancellationToken) to send a PATCH request with a typed request and response, using CreateRequestWithHeaders(HttpMethod.Patch, ...) and SendAsync.
    • Implemented PatchAsync<TRequest, TResponse>(Uri, TRequest, CancellationToken) as a delegate to the string-based overload.
    • Implemented PatchAsync<TRequest, TResponse>(string, TRequest, IDictionary<string, string>, CancellationToken) to include custom headers.
    • Implemented PatchAsync<TRequest, TResponse>(Uri, TRequest, IDictionary<string, string>, CancellationToken) as a delegate to the string-based overload with headers.
    • Implemented PatchAsync<TRequest>(string, TRequest, CancellationToken) and PatchAsync<TRequest>(string, TRequest, IDictionary<string, string>, CancellationToken) to return raw HttpResponseMessage responses.
    • Added explicit interface implementations for IHttpClientAdapter.PatchAsync to support the new PATCH methods.
  • Cache Invalidation:
    • Ensured PATCH methods call InvalidateRelatedCacheAsync after successful requests, consistent with POST, PUT, and DELETE behavior, to invalidate related cache entries.

HttpClientAdapterTests.cs

  • Added Tests for PATCH Methods:
    • PatchAsync_WithTypedResponse_CallsResponseHandler: Verifies that PatchAsync<TRequest, TResponse>(string, TRequest, CancellationToken) sends a PATCH request and calls the response handler with the expected TestResponse.
    • PatchAsync_WithHeaders_CallsResponseHandler: Tests PatchAsync<TRequest, TResponse>(string, TRequest, IDictionary<string, string>, CancellationToken) with custom headers, ensuring the response handler is called.
    • PatchAsync_WithoutTypedResponse_ReturnsHttpResponseMessage: Confirms that PatchAsync<TRequest>(string, TRequest, CancellationToken) returns a raw HttpResponseMessage with status code OK.
    • PatchAsync_WithHeaders_WithoutTypedResponse_ReturnsHttpResponseMessage: Verifies that PatchAsync<TRequest>(string, TRequest, IDictionary<string, string>, CancellationToken) returns a raw HttpResponseMessage with headers.
  • Consistency:
    • Tests mirror the structure of existing POST, PUT, and DELETE tests, using MockHttpMessageHandler and FluentAssertions.
    • Added TestRequest class to support PATCH request payloads.

CachedHttpClientTests.cs

  • Added Tests for PATCH Methods:
    • PatchAsync_WithTypedResponse_ExecutesRequestAndDoesNotCache: Tests PatchAsync<TRequest, TResponse>(string, TRequest, CancellationToken) to ensure it sends a PATCH request, returns the expected TestResponse, and skips caching.
    • PatchAsync_WithHeaders_ExecutesRequestAndDoesNotCache: Tests PatchAsync<TRequest, TResponse>(string, TRequest, IDictionary<string, string>, CancellationToken) with headers, verifying the request executes and skips caching.
  • Implementation Details:
    • Used SendAsync with HttpMethod.Patch to simulate PATCH behavior in the generic CachedHttpClient<TestResponse>.
    • Verified cache invalidation by ensuring _mockCache is not called for GetAsync or SetAsync, as PATCH is non-cacheable.
    • Added TestRequest class to support PATCH request payloads.

HttpClientWithCacheTests.cs

  • Added Tests for PATCH Methods:
    • PatchAsync_InvalidatesRelatedCache: Verifies that PatchAsync<object, TestResponse>(string, object, CancellationToken) executes a PATCH request, returns the expected TestResponse, and attempts cache invalidation.
    • PatchAsync_WithHeaders_InvalidatesRelatedCache: Tests PatchAsync<object, TestResponse>(string, object, IDictionary<string, string>, CancellationToken) with headers, ensuring the request executes and attempts cache invalidation.
    • PatchAsync_WithoutTypedResponse_ReturnsHttpResponseMessage: Confirms that PatchAsync(string, object, CancellationToken) returns a raw HttpResponseMessage with status code OK.
    • PatchAsync_WithHeaders_WithoutTypedResponse_ReturnsHttpResponseMessage: Verifies that PatchAsync(string, object, IDictionary<string, string>, CancellationToken) returns a raw HttpResponseMessage with headers.
    • PatchAsync_ResponseHandlerThrows_CacheRemainsValid: Tests that a failed response handler does not invalidate the cache.
    • PatchAsync_SuccessfulHandling_InvalidatesCache: Verifies that a successful PATCH request returns the expected response and attempts cache invalidation.
  • Consistency:
    • Tests follow the structure of PostAsync tests, using MockHttpMessageHandler, MockResponseHandler, and MemoryCache.
    • Cache invalidation is verified via successful completion, as InvalidateCacheAsync is a no-op (placeholder assertion Assert.True(true)).
    • Added System.Net.Http.Json for JsonContent.Create usage.

Motivation

  • Extended HTTP Method Support: Added PATCH method support to HttpClientAdapter and HttpClientWithCache to enable partial updates, aligning with RESTful API best practices.
  • Comprehensive Testing: Added tests to cover all PATCH method overloads, ensuring robust validation of request execution, response handling, header support, and cache behavior.
  • Consistency: Ensured PATCH methods align with the behavior of POST, PUT, and DELETE methods, particularly in terms of response handling and cache invalidation.

Testing

  • Unit Tests:
    • Added tests for all PATCH method overloads in HttpClientAdapterTests.cs, CachedHttpClientTests.cs, and HttpClientWithCacheTests.cs.
    • Verified HTTP request execution, response handling, header inclusion, and cache invalidation behavior.
    • Used Moq for mocking dependencies (HttpMessageHandler, IHttpResponseHandler, ISimpleCacheKeyGenerator) and FluentAssertions/XUnit for assertions.
  • Cache Behavior:
    • Confirmed that PATCH requests do not cache responses (as they are non-cacheable) and attempt to invalidate related cache entries.
    • Tested cache preservation when the response handler throws an exception.
  • .NET Compatibility:
    • Used JsonContent.Create for JSON serialization to ensure compatibility with .NET 6 and later.

Additional Notes

  • Cache Invalidation Limitation: The current InvalidateCacheAsync implementation in HttpClientWithCache is a no-op (returns Task.CompletedTask), so cache invalidation tests use placeholder assertions. A future enhancement could implement pattern-based cache invalidation.

Checklist

  • Added PATCH method support to HttpClientAdapter and HttpClientWithCache.
  • Updated IHttpClientAdapter interface with PATCH method signatures.
  • Added comprehensive tests for PATCH methods in all test files.
  • Ensured cache invalidation behavior for PATCH requests.
  • Maintained consistency with existing POST, PUT, and DELETE method behavior.
  • Used existing test patterns (Moq, FluentAssertions, XUnit) for new tests.

@codecov
Copy link

codecov bot commented Sep 22, 2025

Codecov Report

❌ Patch coverage is 89.47368% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.24%. Comparing base (440d71a) to head (cf1aa85).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
...Reliable.HttpClient.Caching/HttpClientWithCache.cs 85.96% 8 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main       #4      +/-   ##
==========================================
+ Coverage   64.08%   69.24%   +5.16%     
==========================================
  Files          26       26              
  Lines        1044     1099      +55     
  Branches      132      132              
==========================================
+ Hits          669      761      +92     
+ Misses        356      319      -37     
  Partials       19       19              
Files with missing lines Coverage Δ
...nt.Caching/Abstractions/HttpCacheOptionsBuilder.cs 50.00% <ø> (ø)
src/Reliable.HttpClient/HttpClientAdapter.cs 65.88% <100.00%> (+31.03%) ⬆️
...Reliable.HttpClient.Caching/HttpClientWithCache.cs 79.89% <85.96%> (+20.43%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@dterenin-the-dev dterenin-the-dev added the enhancement New feature or request label Sep 22, 2025
@dterenin-the-dev dterenin-the-dev changed the title Add PATCH Method Support to HttpClientAdapter and HttpClientWithCache feat: Add PATCH Method Support to HttpClientAdapter and HttpClientWithCache Sep 22, 2025
@akrisanov
Copy link
Contributor

Thanks for a solid PR. While the changes are generally correct and well implemented, there are a couple of things that can be improved. First, the HttpClientWithCache class needs explicit interface implementations for the PATCH methods that return HttpResponseMessage:

async Task<HttpResponseMessage> IHttpClientAdapter.PatchAsync<TRequest>(
    string requestUri, TRequest content, CancellationToken cancellationToken)
{
    Dictionary<string, string> allHeaders = MergeHeaders(_cacheOptions.DefaultHeaders, new Dictionary<string, string>());
    using HttpRequestMessage request = CreateRequestWithHeaders(HttpMethod.Patch, requestUri, allHeaders);
    request.Content = JsonContent.Create(content);

    HttpResponseMessage response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);

    // Invalidate cache only after successful HTTP request (before response handler to maintain adapter contract)
    await InvalidateRelatedCacheAsync(requestUri).ConfigureAwait(false);

    return response;
}

async Task<HttpResponseMessage> IHttpClientAdapter.PatchAsync<TRequest>(
    string requestUri, TRequest content, IDictionary<string, string> headers, CancellationToken cancellationToken)
{
    Dictionary<string, string> allHeaders = MergeHeaders(_cacheOptions.DefaultHeaders, headers);
    using HttpRequestMessage request = CreateRequestWithHeaders(HttpMethod.Patch, requestUri, allHeaders);
    request.Content = JsonContent.Create(content);

    HttpResponseMessage response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);

    // Invalidate cache only after successful HTTP request (before response handler to maintain adapter contract)
    await InvalidateRelatedCacheAsync(requestUri).ConfigureAwait(false);

    return response;
}

Task<TResponse> IHttpClientAdapter.PatchAsync<TRequest, TResponse>(
    string requestUri, TRequest content, CancellationToken cancellationToken) =>
    PatchAsync<TRequest, TResponse>(requestUri, content, cancellationToken);

Task<TResponse> IHttpClientAdapter.PatchAsync<TRequest, TResponse>(
    string requestUri, TRequest content, IDictionary<string, string> headers, CancellationToken cancellationToken) =>
    PatchAsync<TRequest, TResponse>(requestUri, content, headers, cancellationToken);

Secondly, you could add tests for the raw HttpResponseMessage PATCH methods in HttpClientWithCacheTests:

[Fact]
public async Task PatchAsync_WithoutTypedResponse_ReturnsHttpResponseMessage()
{
    // Similar to existing PostAsync tests but for PATCH
}

[Fact]
public async Task PatchAsync_WithHeaders_WithoutTypedResponse_ReturnsHttpResponseMessage()
{
    // Similar to existing PostAsync tests but for PATCH with headers
}

Once these are addressed, the PR will have complete coverage and full functionality. The implementation will be consistent with existing patterns, well-tested, and it will follow good coding practices.

@akrisanov
Copy link
Contributor

@akrisanov
Copy link
Contributor

- add HttpResponseMessage overloads for PatchAsync methods of HttpClientWithCache;
- enriched PatchAsync test suite;
- aligned test suites between post, put, patch and delete methods
@dterenin-the-dev
Copy link
Collaborator Author

That's a good points! I have implemented your suggestions in my latest commit and additionally aligned test suites between POST, PUT, PATCH and DELETE methods to further improve reliability and test coverage

@akrisanov
Copy link
Contributor

@dterenin-the-dev awesome! I think we're good to go. Merging.

@akrisanov akrisanov merged commit 40b2ce0 into main Sep 22, 2025
3 checks passed
@akrisanov akrisanov deleted the feat/add-patch-method-support branch September 22, 2025 10:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants