Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user