Files
Omika/tests/Omika.Tests/Observability/Outbox/DiagnosticUploadWorkerTests.cs
T
2026-10-23 18:46:50 +02:00

151 lines
5.8 KiB
C#

using Omika.Core.Observability;
using Omika.Core.Observability.Outbox;
namespace Omika.Tests.Observability.Outbox;
/// <summary>Tests du worker d'upload : succès/suppression, retry, auth, poison, split, anti-loop.</summary>
public class DiagnosticUploadWorkerTests
{
private static (DiagnosticUploadWorker Worker, InMemoryDiagnosticOutboxStore Store, DiagnosticCounters Counters, FakeUploadClient Client) Create(
DiagnosticUploadOptions? uploadOptions = null,
string? token = "token-1",
Func<DiagnosticBatchDto, DiagnosticUploadResult>? handler = null)
{
var store = new InMemoryDiagnosticOutboxStore();
var counters = new DiagnosticCounters();
var outbox = new DiagnosticOutbox(store, new DiagnosticOutboxOptions(), counters);
var client = new FakeUploadClient { Handler = handler ?? (_ => new DiagnosticUploadResult(DiagnosticUploadOutcome.Success)) };
var worker = new DiagnosticUploadWorker(
outbox,
client,
uploadOptions ?? new DiagnosticUploadOptions(),
counters,
() => token);
return (worker, store, counters, client);
}
[Fact]
public async Task Success_DeletesRows_AndCounts()
{
(DiagnosticUploadWorker worker, InMemoryDiagnosticOutboxStore store, DiagnosticCounters counters, _) = Create();
await SeedAsync(store, 1);
bool attempted = await worker.FlushAsync();
Assert.True(attempted);
Assert.Equal(0, await store.CountAsync());
Assert.Equal(1, counters.UploadSuccess);
Assert.Equal(1, counters.UploadEventsSent);
}
[Fact]
public async Task TransientFailure_ConservesRows_IncrementsAttempt()
{
(DiagnosticUploadWorker worker, InMemoryDiagnosticOutboxStore store, DiagnosticCounters counters, _) =
Create(handler: _ => new DiagnosticUploadResult(DiagnosticUploadOutcome.Transient));
await SeedAsync(store, 1);
await worker.FlushAsync();
Assert.Equal(1, await store.CountAsync());
Assert.Equal(1, counters.UploadFailure);
DiagnosticOutboxRow row = store.Snapshot().Single();
Assert.Equal(1, row.AttemptCount);
Assert.True(row.NextAttemptUtc > DateTimeOffset.UtcNow);
}
[Fact]
public async Task Unauthorized_ConservesRows_NoDelete()
{
(DiagnosticUploadWorker worker, InMemoryDiagnosticOutboxStore store, DiagnosticCounters counters, _) =
Create(handler: _ => new DiagnosticUploadResult(DiagnosticUploadOutcome.Unauthorized));
await SeedAsync(store, 1);
await worker.FlushAsync();
Assert.Equal(1, await store.CountAsync());
Assert.Equal(1, counters.UploadUnauthorized);
Assert.Equal(0, counters.UploadSuccess);
}
[Fact]
public async Task RejectedSingleton_IsDroppedAsPoison()
{
(DiagnosticUploadWorker worker, InMemoryDiagnosticOutboxStore store, DiagnosticCounters counters, _) =
Create(handler: _ => new DiagnosticUploadResult(DiagnosticUploadOutcome.Rejected));
await SeedAsync(store, 1);
await worker.FlushAsync();
Assert.Equal(0, await store.CountAsync());
Assert.Equal(1, counters.UploadPoisonDropped);
}
[Fact]
public async Task RejectedLargeBatch_Splits_UntilSingletonsSucceed()
{
// Le serveur rejette tout lot de plus d'un événement (400) : le worker divise
// jusqu'aux singletons, qui sont acceptés. Aucun poison, tout est envoyé.
(DiagnosticUploadWorker worker, InMemoryDiagnosticOutboxStore store, DiagnosticCounters counters, _) =
Create(handler: batch => batch.Events.Count > 1
? new DiagnosticUploadResult(DiagnosticUploadOutcome.Rejected)
: new DiagnosticUploadResult(DiagnosticUploadOutcome.Success));
await SeedAsync(store, 4);
await worker.FlushAsync();
Assert.Equal(0, await store.CountAsync());
Assert.Equal(4, counters.UploadEventsSent);
Assert.Equal(0, counters.UploadPoisonDropped);
}
[Fact]
public async Task NoToken_DoesNotUpload_ConservesRows()
{
(DiagnosticUploadWorker worker, InMemoryDiagnosticOutboxStore store, _, FakeUploadClient client) = Create(token: null);
await SeedAsync(store, 1);
await worker.FlushAsync();
Assert.Equal(1, await store.CountAsync());
Assert.Equal(0, client.Calls);
}
[Fact]
public async Task Disabled_LoopDoesNotUpload()
{
var options = new DiagnosticUploadOptions { Enabled = false, Interval = TimeSpan.FromMilliseconds(10) };
(DiagnosticUploadWorker worker, InMemoryDiagnosticOutboxStore store, _, FakeUploadClient client) = Create(uploadOptions: options);
await SeedAsync(store, 1);
worker.Start();
await Task.Delay(50);
await worker.StopAsync();
Assert.Equal(1, await store.CountAsync());
Assert.Equal(0, client.Calls);
}
private static async Task SeedAsync(InMemoryDiagnosticOutboxStore store, int count)
{
// Insère des événements mappés dans le store pour isoler le comportement d'upload.
var outbox = new DiagnosticOutbox(store, new DiagnosticOutboxOptions(), new DiagnosticCounters());
await outbox.StoreAsync(Enumerable.Range(0, count)
.Select(_ => OutboxTestHarness.NewEvent(TelemetryPriority.P3))
.ToList());
}
private sealed class FakeUploadClient : IDiagnosticUploadClient
{
public Func<DiagnosticBatchDto, DiagnosticUploadResult>? Handler { get; set; }
public int Calls { get; private set; }
public Task<DiagnosticUploadResult> UploadAsync(DiagnosticBatchDto batch, CancellationToken cancellationToken = default)
{
Calls++;
return Task.FromResult(Handler!(batch));
}
}
}