Files
2026-10-23 18:46:50 +02:00

151 lines
5.3 KiB
C#

using System.Net;
using System.Net.Http.Headers;
using System.Text;
using Omika.Core.Observability.Outbox;
namespace Omika.Tests.Observability.Outbox;
/// <summary>Tests du client HTTP d'upload : classification des statuts, timeout, anti-boucle.</summary>
public class DiagnosticUploadClientTests
{
private static DiagnosticBatchDto NewBatch() =>
new()
{
Events =
[
new DiagnosticEventDto { EventId = Guid.NewGuid(), EventCode = "OMK-MOB-APP-001" }
]
};
private static DiagnosticUploadClient CreateClient(
StubHandler handler,
DiagnosticUploadOptions? options = null,
string? token = "token-1") =>
new(
new HttpClient(handler),
"http://localhost:5000",
() => token,
options ?? new DiagnosticUploadOptions { Timeout = TimeSpan.FromSeconds(5) });
[Fact]
public async Task NoToken_ReturnsUnauthorized_WithoutHttp()
{
var handler = new StubHandler { StatusCode = HttpStatusCode.OK };
DiagnosticUploadClient client = CreateClient(handler, token: null);
DiagnosticUploadResult result = await client.UploadAsync(NewBatch());
Assert.Equal(DiagnosticUploadOutcome.Unauthorized, result.Outcome);
Assert.Equal(0, handler.Calls);
}
[Fact]
public async Task Success_MapsToSuccess_WithAcceptedCount()
{
var handler = new StubHandler
{
StatusCode = HttpStatusCode.OK,
ResponseBody = """{"batchId":"00000000-0000-0000-0000-000000000001","acceptedCount":3}"""
};
DiagnosticUploadClient client = CreateClient(handler);
DiagnosticUploadResult result = await client.UploadAsync(NewBatch());
Assert.Equal(DiagnosticUploadOutcome.Success, result.Outcome);
Assert.Equal(3, result.AcceptedCount);
}
[Theory]
[InlineData(HttpStatusCode.Unauthorized, DiagnosticUploadOutcome.Unauthorized)]
[InlineData(HttpStatusCode.Forbidden, DiagnosticUploadOutcome.Unauthorized)]
[InlineData(HttpStatusCode.BadRequest, DiagnosticUploadOutcome.Rejected)]
[InlineData(HttpStatusCode.RequestEntityTooLarge, DiagnosticUploadOutcome.TooLarge)]
[InlineData(HttpStatusCode.InternalServerError, DiagnosticUploadOutcome.Transient)]
[InlineData(HttpStatusCode.ServiceUnavailable, DiagnosticUploadOutcome.Transient)]
[InlineData(HttpStatusCode.GatewayTimeout, DiagnosticUploadOutcome.Transient)]
public async Task StatusCodes_MapToExpectedOutcome(HttpStatusCode status, DiagnosticUploadOutcome expected)
{
var handler = new StubHandler { StatusCode = status };
DiagnosticUploadClient client = CreateClient(handler);
DiagnosticUploadResult result = await client.UploadAsync(NewBatch());
Assert.Equal(expected, result.Outcome);
Assert.Equal((int)status, result.StatusCode);
}
[Fact]
public async Task RateLimited_HonorsRetryAfter()
{
var handler = new StubHandler
{
StatusCode = HttpStatusCode.TooManyRequests,
RetryAfterSeconds = 42
};
DiagnosticUploadClient client = CreateClient(handler);
DiagnosticUploadResult result = await client.UploadAsync(NewBatch());
Assert.Equal(DiagnosticUploadOutcome.RateLimited, result.Outcome);
Assert.Equal(TimeSpan.FromSeconds(42), result.RetryAfter);
}
[Fact]
public async Task Timeout_MapsToTransient()
{
var handler = new StubHandler { DelayMs = 2000, StatusCode = HttpStatusCode.OK };
DiagnosticUploadClient client = CreateClient(handler, new DiagnosticUploadOptions { Timeout = TimeSpan.FromMilliseconds(10) });
DiagnosticUploadResult result = await client.UploadAsync(NewBatch());
Assert.Equal(DiagnosticUploadOutcome.Transient, result.Outcome);
}
[Fact]
public async Task Upload_SendsBearerToken()
{
var handler = new StubHandler { StatusCode = HttpStatusCode.OK };
DiagnosticUploadClient client = CreateClient(handler, token: "secret-token");
await client.UploadAsync(NewBatch());
Assert.Equal("Bearer secret-token", handler.LastAuthorization);
}
private sealed class StubHandler : HttpMessageHandler
{
public HttpStatusCode StatusCode { get; set; } = HttpStatusCode.OK;
public int DelayMs { get; set; }
public string ResponseBody { get; set; } = "{}";
public int? RetryAfterSeconds { get; set; }
public int Calls { get; private set; }
public string? LastAuthorization { get; private set; }
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
Calls++;
LastAuthorization = request.Headers.Authorization?.ToString();
if (DelayMs > 0)
{
await Task.Delay(DelayMs, cancellationToken);
}
var response = new HttpResponseMessage(StatusCode);
if (RetryAfterSeconds is { } seconds)
{
response.Headers.RetryAfter = new RetryConditionHeaderValue(TimeSpan.FromSeconds(seconds));
}
response.Content = new StringContent(ResponseBody, Encoding.UTF8, "application/json");
return response;
}
}
}