151 lines
5.3 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|