Files
jdescopingtool/NEW/tests/JdeScoping.Client.Tests/Services/ApiClientBaseTests.cs
T
Joseph Doherty c626ffbd69 test: add edge case tests to ApiClientBaseTests
Add 7 edge case tests for ApiClientBase:
- GetAsync_Returns200_EmptyBody_MapsToApiError
- GetAsync_Returns200_InvalidJson_MapsToApiError
- GetAsync_Returns204_ForUnitType_MapsToSuccess
- GetAsync_Returns204_ForNonUnitType_MapsToApiError
- GetAsync_NetworkException_MapsToApiError
- GetAsync_Timeout_MapsToApiError
- GetAsync_Returns400_WithoutValidationFormat_MapsToApiError

Also fix bug in ApiClientBase where 204 NoContent for Unit type
failed due to incorrect type casting. The implicit conversion from
Unit to ApiResult<Unit> must be applied before the runtime cast.
2026-01-06 11:23:54 -05:00

223 lines
6.3 KiB
C#

using System.Net;
using System.Text.Json;
using JdeScoping.Core.ApiContracts.Results;
using RichardSzalay.MockHttp;
using Shouldly;
namespace JdeScoping.Client.Tests.Services;
public class ApiClientBaseTests
{
private readonly MockHttpMessageHandler _mockHttp;
private readonly TestableApiClient _client;
public ApiClientBaseTests()
{
_mockHttp = new MockHttpMessageHandler();
var httpClient = new HttpClient(_mockHttp) { BaseAddress = new Uri("http://localhost/") };
_client = new TestableApiClient(httpClient);
}
public record TestDto(int Id, string Name);
[Fact]
public async Task GetAsync_Returns200_MapsToSuccessValue()
{
// Arrange
var expected = new TestDto(42, "Test");
_mockHttp.When("/api/test")
.Respond("application/json", JsonSerializer.Serialize(expected));
// Act
var result = await _client.GetAsync<TestDto>("/api/test");
// Assert
result.IsSuccess.ShouldBeTrue();
result.Value.Id.ShouldBe(42);
result.Value.Name.ShouldBe("Test");
}
[Fact]
public async Task GetAsync_Returns404_MapsToNotFound()
{
// Arrange
_mockHttp.When("/api/test")
.Respond(HttpStatusCode.NotFound);
// Act
var result = await _client.GetAsync<TestDto>("/api/test");
// Assert
result.IsNotFound.ShouldBeTrue();
}
[Fact]
public async Task GetAsync_Returns400_WithValidationErrors_MapsToValidationError()
{
// Arrange - use actual ValidationProblemDetails structure to match production
var validationProblem = new Microsoft.AspNetCore.Mvc.ValidationProblemDetails
{
Errors =
{
["Name"] = new[] { "Name is required" },
["Id"] = new[] { "Id must be positive" }
}
};
_mockHttp.When("/api/test")
.Respond(HttpStatusCode.BadRequest, "application/json", JsonSerializer.Serialize(validationProblem, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
// Act
var result = await _client.GetAsync<TestDto>("/api/test");
// Assert
result.IsValidationError.ShouldBeTrue();
result.ValidationError.FieldErrors.ShouldContainKey("Name");
result.ValidationError.FieldErrors["Name"].ShouldContain("Name is required");
}
[Fact]
public async Task GetAsync_Returns401_MapsToUnauthorized()
{
// Arrange
_mockHttp.When("/api/test")
.Respond(HttpStatusCode.Unauthorized);
// Act
var result = await _client.GetAsync<TestDto>("/api/test");
// Assert
result.IsUnauthorized.ShouldBeTrue();
}
[Fact]
public async Task GetAsync_Returns403_MapsToForbidden()
{
// Arrange
_mockHttp.When("/api/test")
.Respond(HttpStatusCode.Forbidden);
// Act
var result = await _client.GetAsync<TestDto>("/api/test");
// Assert
result.IsForbidden.ShouldBeTrue();
}
[Fact]
public async Task GetAsync_Returns500_MapsToApiError()
{
// Arrange
_mockHttp.When("/api/test")
.Respond(HttpStatusCode.InternalServerError, "text/plain", "Internal Server Error");
// Act
var result = await _client.GetAsync<TestDto>("/api/test");
// Assert
result.IsError.ShouldBeTrue();
result.Error.StatusCode.ShouldBe(500);
}
// Edge cases
[Fact]
public async Task GetAsync_Returns200_EmptyBody_MapsToApiError()
{
// Arrange
_mockHttp.When("/api/test")
.Respond(HttpStatusCode.OK, "application/json", "");
// Act
var result = await _client.GetAsync<TestDto>("/api/test");
// Assert
result.IsError.ShouldBeTrue();
}
[Fact]
public async Task GetAsync_Returns200_InvalidJson_MapsToApiError()
{
// Arrange
_mockHttp.When("/api/test")
.Respond(HttpStatusCode.OK, "application/json", "not valid json {{{");
// Act
var result = await _client.GetAsync<TestDto>("/api/test");
// Assert
result.IsError.ShouldBeTrue();
}
[Fact]
public async Task GetAsync_Returns204_ForUnitType_MapsToSuccess()
{
// Arrange
_mockHttp.When("/api/test")
.Respond(HttpStatusCode.NoContent);
// Act
var result = await _client.GetAsync<Unit>("/api/test");
// Assert
result.IsSuccess.ShouldBeTrue();
}
[Fact]
public async Task GetAsync_Returns204_ForNonUnitType_MapsToApiError()
{
// Arrange
_mockHttp.When("/api/test")
.Respond(HttpStatusCode.NoContent);
// Act
var result = await _client.GetAsync<TestDto>("/api/test");
// Assert
result.IsError.ShouldBeTrue();
}
[Fact]
public async Task GetAsync_NetworkException_MapsToApiError()
{
// Arrange
_mockHttp.When("/api/test")
.Throw(new HttpRequestException("Network failure"));
// Act
var result = await _client.GetAsync<TestDto>("/api/test");
// Assert
result.IsError.ShouldBeTrue();
result.Error.Message.ShouldContain("Network failure");
}
[Fact]
public async Task GetAsync_Timeout_MapsToApiError()
{
// Arrange
_mockHttp.When("/api/test")
.Throw(new TaskCanceledException("Request timeout"));
// Act
var result = await _client.GetAsync<TestDto>("/api/test");
// Assert
result.IsError.ShouldBeTrue();
}
[Fact]
public async Task GetAsync_Returns400_WithoutValidationFormat_MapsToApiError()
{
// Arrange - Bad request without standard validation problem format
_mockHttp.When("/api/test")
.Respond(HttpStatusCode.BadRequest, "text/plain", "Bad request");
// Act
var result = await _client.GetAsync<TestDto>("/api/test");
// Assert
result.IsError.ShouldBeTrue();
result.Error.StatusCode.ShouldBe(400);
}
}