Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,35 @@ await client.PutAndEnsureForbiddenAsync("/authors/1", content);
await client.PutAndEnsureNotFoundAsync("/wrongendpoint", content)
```

#### [DELETE](src/Ardalis.HttpClientTestExtensions/HttpClientDeleteExtensionMethods.cs)
#### [PATCH](src\Ardalis.HttpClientTestExtensions\HttpClientPatchExtensionMethods.cs)

```csharp
var content = new StringContent(JsonSerializer.Serialize(dto), Encoding.UTF8, "application/json");

// PATCH and return an object T
AuthorDto result = await client.PatchAndDeserializeAsync("/authors/1", content);

// PATCH and ensure response contains a substring
string result = client.PatchAndEnsureSubstringAsync("/authors/1", content, "OMG!");

// PATCH and assert a 302 is returned
var client = _factory.CreateClient(new WebApplicationFactoryClientOptions() { AllowAutoRedirect = false });
await client.PatchAndEnsureRedirectAsync("/oldone", content, "/newone");

// PATCH and assert a 400 is returned
await client.PatchAndEnsureBadRequestAsync("/authors/1", "banana");

// PATCH and assert a 401 is returned
await client.PatchAndEnsureUnauthorizedAsync("/authors/1", content);

// PATCH and assert a 403 is returned
await client.PatchAndEnsureForbiddenAsync("/authors/1", content);

// PATCH and assert a 404 is returned
await client.PatchAndEnsureNotFoundAsync("/wrongendpoint", content)
```

#### [DELETE](src\Ardalis.HttpClientTestExtensions\HttpClientDeleteExtensionMethods.cs)

```csharp
// DELETE and return an object T
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Xunit.Abstractions;

namespace Ardalis.HttpClientTestExtensions;

public static partial class HttpClientPatchExtensionMethods
{
/// <summary>
/// Makes a PATCH request to a requestUri and deserializes the response to a T object
/// </summary>
/// <param name="client"></param>
/// <param name="requestUri"></param>
/// <param name="content"></param>
/// <param name="output">Optional; used to provide details to standard output.</param>
/// <returns>The deserialized response object</returns>
public static async Task<T> PatchAndDeserializeAsync<T>(
this HttpClient client,
string requestUri,
HttpContent content,
ITestOutputHelper output = null)
{
var response = await client.PatchAsync(requestUri, content, output);
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
output?.WriteLine($"Response: {stringResponse}");
var result = JsonSerializer.Deserialize<T>(stringResponse,
Constants.DefaultJsonOptions);

return result;
}

/// <summary>
/// Ensures a PATCH to a requestUri returns a 404 Not Found response status code
/// </summary>
/// <param name="client"></param>
/// <param name="requestUri"></param>
/// <param name="content"></param>
/// <param name="output">Optional; used to provide details to standard output.</param>
/// <returns></returns>
public static async Task<HttpResponseMessage> PatchAndEnsureNotFoundAsync(
this HttpClient client,
string requestUri,
HttpContent content,
ITestOutputHelper output = null)
{
var response = await client.PatchAsync(requestUri, content, output);
response.EnsureNotFound();
return response;
}

/// <summary>
/// Ensures a PATCH to a requestUri returns a 401 Unauthorized response status code
/// </summary>
/// <param name="client"></param>
/// <param name="requestUri"></param>
/// <param name="content"></param>
/// <param name="output">Optional; used to provide details to standard output.</param>
/// <returns></returns>
public static async Task<HttpResponseMessage> PatchAndEnsureUnauthorizedAsync(
this HttpClient client,
string requestUri,
HttpContent content,
ITestOutputHelper output = null)
{
var response = await client.PatchAsync(requestUri, content, output);
response.EnsureUnauthorized();
return response;
}

/// <summary>
/// Ensures a PATCH to a requestUri returns a 403 Forbidden response status code
/// </summary>
/// <param name="client"></param>
/// <param name="requestUri"></param>
/// <param name="content"></param>
/// <param name="output">Optional; used to provide details to standard output.</param>
/// <returns></returns>
public static async Task<HttpResponseMessage> PatchAndEnsureForbiddenAsync(
this HttpClient client,
string requestUri,
HttpContent content,
ITestOutputHelper output = null)
{
var response = await client.PatchAsync(requestUri, content, output);
response.EnsureForbidden();
return response;
}

/// <summary>
/// Makes a PATCH request to a requestUri and ensures the response contains a substring
/// </summary>
/// <param name="client"></param>
/// <param name="requestUri"></param>
/// <param name="content"></param>
/// <param name="substring"></param>
/// <param name="output">Optional; used to provide details to standard output.</param>
/// <returns>The response string</returns>
public static async Task<string> PatchAndEnsureSubstringAsync(
this HttpClient client,
string requestUri,
HttpContent content,
string substring,
ITestOutputHelper output = null)
{
var response = await client.PatchAsync(requestUri, content, output);
return await response.EnsureContainsAsync(substring);
}

/// <summary>
/// Ensures a PATCH to a requestUri returns a 400 Bad Request response status code
/// </summary>
/// <param name="client"></param>
/// <param name="requestUri"></param>
/// <param name="content"></param>
/// <param name="output">Optional; used to provide details to standard output.</param>
/// <returns></returns>
public static async Task<HttpResponseMessage> PatchAndEnsureBadRequestAsync(
this HttpClient client,
string requestUri,
HttpContent content,
ITestOutputHelper output = null)
{
var response = await client.PatchAsync(requestUri, content, output);
response.EnsureBadRequest();
return response;
}

/// <summary>
/// Ensures a PATCH to a requestUri returns a 302 Redirect response status code
/// and redirects to the expected redirectUri
/// </summary>
/// <param name="client"></param>
/// <param name="requestUri"></param>
/// <param name="content"></param>
/// <param name="redirectUri"></param>
/// <param name="output">Optional; used to provide details to standard output.</param>
/// <returns></returns>
public static async Task<HttpResponseMessage> PatchAndRedirectAsync(
this HttpClient client,
string requestUri,
HttpContent content,
string redirectUri,
ITestOutputHelper output = null)
{
var response = await client.PatchAsync(requestUri, content, output);
client.EnsureNoAutoRedirect(output);
response.EnsureRedirect(redirectUri);
return response;
}

private static async Task<HttpResponseMessage> PatchAsync(
this HttpClient client,
string requestUri,
HttpContent content,
ITestOutputHelper output)
{
output?.WriteLine($"Requesting with PATCH {requestUri}");
return await client.PatchAsync(requestUri, content);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Threading;
using System.Threading.Tasks;
using Ardalis.ApiEndpoints;
using Ardalis.HttpClientTestExtensions.Api.Dtos;
using Ardalis.HttpClientTestExtensions.Core.Entities;
using Ardalis.HttpClientTestExtensions.SharedKernel.Interfaces;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;

namespace Ardalis.HttpClientTestExtensions.Api.Endpoints.CountryEndpoints;

public class Patch : EndpointBaseAsync
.WithRequest<EditCountryRequest>
.WithActionResult<CountryDto>
{
private readonly IMapper _mapper;
private readonly IReadRepository<Country> _readRepository;
private readonly IRepository<Country> _repository;

public Patch(IMapper mapper, IReadRepository<Country> readRepository, IRepository<Country> repository)
{
_mapper = mapper;
_readRepository = readRepository;
_repository = repository;
}

[HttpPatch(EditCountryRequest.Route)]
public override async Task<ActionResult<CountryDto>> HandleAsync([FromBody] EditCountryRequest countryDto, CancellationToken cancellationToken = default)
{
var entity = await _repository.GetByIdAsync(countryDto.Id, cancellationToken);
if (entity == null)
{
return NotFound();
}
var entityToSave = _mapper.Map(countryDto, entity);
await _repository.UpdateAsync(entityToSave, cancellationToken);

var response = _mapper.Map<CountryDto>(entityToSave);

return Ok(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class BadRequest : EndpointBaseSync

{
[Route("/badrequest")]
[AcceptVerbs("GET", "DELETE", "PUT", "POST")]
[AcceptVerbs("GET", "DELETE", "PUT", "PATCH", "POST")]
public override BadRequestResult Handle()
{
return BadRequest();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class Redirect : EndpointBaseSync

{
[Route("/redirect")]
[AcceptVerbs("GET", "POST", "PUT", "DELETE")]
[AcceptVerbs("GET", "POST", "PUT", "PATCH", "DELETE")]
public override RedirectResult Handle()
{
return Redirect("/redirected");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Ardalis.HttpClientTestExtensions.Api;
using Ardalis.HttpClientTestExtensions.Api.Dtos;
using Microsoft.AspNetCore.Mvc.Testing;
using Shouldly;
using Xunit;
using Xunit.Abstractions;

namespace Ardalis.HttpClientTestExtensions.Tests;

public class HttpClientPatchExtensionMethodsTests : IClassFixture<CustomWebApplicationFactory>
{
private readonly HttpClient _client;
private readonly ITestOutputHelper _outputHelper;
private readonly CustomWebApplicationFactory _factory;

public HttpClientPatchExtensionMethodsTests(CustomWebApplicationFactory factory, ITestOutputHelper outputHelper)
{
_client = factory.CreateClient();
_outputHelper = outputHelper;
_factory = factory;
}

[Fact]
public async Task PatchAndDeserializeTestAsync()
{
var expectedId = SeedData.TestCountry1.Id;
var expectedName = "United States of America";
var dto = new CountryDto { Id = expectedId, Name = expectedName };
var content = new StringContent(JsonSerializer.Serialize(dto), Encoding.UTF8, "application/json");

var response = await _client.PutAndDeserializeAsync<CountryDto>("/countries", content, _outputHelper);

response.Id.ShouldBe(expectedId);
response.Name.ShouldBe(expectedName);
}

[Fact]
public async Task PatchAndEnsureNotFoundTestAsync()
{
var dto = new CountryDto();
var content = new StringContent(JsonSerializer.Serialize(dto), Encoding.UTF8, "application/json");
_ = await _client.PatchAndEnsureNotFoundAsync("/wrongendpoint", content, _outputHelper);
}

[Fact]
public async Task PatchAndEnsureSubstringAsync_With_Matching_Substring()
{
var expectedJson = "{\"id\":\"USA\",\"name\":\"'Merica\"}";
var dto = new CountryDto { Id = SeedData.TestCountry1.Id, Name = "'Merica" };
var content = new StringContent(JsonSerializer.Serialize(dto), Encoding.UTF8, "application/json");

var response = await _client.PatchAndEnsureSubstringAsync("/countries", content, "erica", _outputHelper);

response.ShouldBe(expectedJson);
}

[Fact]
public async Task PatchAndEnsureSubstringAsync_Without_Matching_Substring()
{
var dto = new CountryDto { Id = SeedData.TestCountry1.Id, Name = "'Merica" };
var content = new StringContent(JsonSerializer.Serialize(dto), Encoding.UTF8, "application/json");

await Assert.ThrowsAsync<HttpRequestException>(() => _client.PatchAndEnsureSubstringAsync("/countries", content, "banana", _outputHelper));
}

[Fact]
public async Task PatchAndEnsureBadRequestAsync()
{
var dto = new CountryDto { Id = SeedData.TestCountry1.Id, Name = "'Merica" };
var content = new StringContent(JsonSerializer.Serialize(dto), Encoding.UTF8, "application/json");
_ = await _client.PatchAndEnsureBadRequestAsync("/badrequest", content, _outputHelper);
}

[Fact]
public async Task PutAndRedirectAsync()
{
var dto = new CountryDto { Id = "ESP", Name = "Spain" };
var content = new StringContent(JsonSerializer.Serialize(dto), Encoding.UTF8, "application/json");
var client = _factory.CreateClient(new WebApplicationFactoryClientOptions() { AllowAutoRedirect = false });
_ = await client.PatchAndRedirectAsync("/redirect", content, "/redirected", _outputHelper);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Net;
using System.Net.Http;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
Expand Down Expand Up @@ -48,7 +47,7 @@ public async Task PutAndEnsureNotFoundTestAsync()
}

[Fact]
public async Task PuAndEnsureSubstringAsync_With_Matching_Substring()
public async Task PutAndEnsureSubstringAsync_With_Matching_Substring()
{
var expectedJson = "{\"id\":\"USA\",\"name\":\"'Merica\"}";
var dto = new CountryDto { Id = SeedData.TestCountry1.Id, Name = "'Merica" };
Expand Down