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