using Omika.Core.Observability; using Omika.Core.Observability.Outbox; namespace Omika.Tests.Observability.Outbox; /// Tests du DiagnosticOutbox : filtrage P0..P3, saturation, éviction, rétention, fail-open. public class DiagnosticOutboxTests { private static (DiagnosticOutbox Outbox, InMemoryDiagnosticOutboxStore Store, DiagnosticCounters Counters) Create( DiagnosticOutboxOptions? options = null) { var store = new InMemoryDiagnosticOutboxStore(); var counters = new DiagnosticCounters(); var outbox = new DiagnosticOutbox(store, options ?? new DiagnosticOutboxOptions(), counters); return (outbox, store, counters); } [Fact] public async Task Store_StoresP0ThroughP3_ByDefault() { (DiagnosticOutbox outbox, _, DiagnosticCounters counters) = Create(); int stored = await outbox.StoreAsync( [ OutboxTestHarness.NewEvent(TelemetryPriority.P0), OutboxTestHarness.NewEvent(TelemetryPriority.P1), OutboxTestHarness.NewEvent(TelemetryPriority.P2), OutboxTestHarness.NewEvent(TelemetryPriority.P3) ]); Assert.Equal(4, stored); Assert.Equal(4, counters.OutboxStored); Assert.Equal(0, counters.OutboxDropped); } [Fact] public async Task Store_DropsP4_ByDefault() { (DiagnosticOutbox outbox, _, DiagnosticCounters counters) = Create(); int stored = await outbox.StoreAsync([OutboxTestHarness.NewEvent(TelemetryPriority.P4)]); Assert.Equal(0, stored); Assert.Equal(1, counters.OutboxDropped); } [Fact] public async Task Store_IncludeP4Option_StoresP4() { var options = new DiagnosticOutboxOptions { IncludeP4 = true }; (DiagnosticOutbox outbox, _, DiagnosticCounters counters) = Create(options); int stored = await outbox.StoreAsync([OutboxTestHarness.NewEvent(TelemetryPriority.P4)]); Assert.Equal(1, stored); Assert.Equal(1, counters.OutboxStored); } [Fact] public async Task Store_EvictsP3_BeforeP0_WhenSaturated() { var options = new DiagnosticOutboxOptions { MaxRows = 10 }; (DiagnosticOutbox outbox, InMemoryDiagnosticOutboxStore store, DiagnosticCounters counters) = Create(options); // Remplit l'outbox avec 10 P3. List p3s = Enumerable.Range(0, 10) .Select(_ => (OmikaEvent)OutboxTestHarness.NewEvent(TelemetryPriority.P3)) .ToList(); await outbox.StoreAsync(p3s); Assert.Equal(10, await store.CountAsync()); // Un nouveau P0 doit évincer un P3 (pas un P0). await outbox.StoreAsync([OutboxTestHarness.NewEvent(TelemetryPriority.P0)]); Assert.Equal(10, await store.CountAsync()); Assert.Contains(store.Snapshot(), r => r.Priority == TelemetryPriority.P0); Assert.Equal(1, counters.OutboxEvictedP3); Assert.Equal(0, counters.OutboxEvictedP0); } [Fact] public async Task Store_FullOfP0_DropsNewP0_WithCounter() { var options = new DiagnosticOutboxOptions { MaxRows = 5 }; (DiagnosticOutbox outbox, InMemoryDiagnosticOutboxStore store, DiagnosticCounters counters) = Create(options); await outbox.StoreAsync(Enumerable.Range(0, 5) .Select(_ => OutboxTestHarness.NewEvent(TelemetryPriority.P0)) .ToList()); Assert.Equal(5, await store.CountAsync()); int stored = await outbox.StoreAsync([OutboxTestHarness.NewEvent(TelemetryPriority.P0)]); Assert.Equal(0, stored); Assert.Equal(5, await store.CountAsync()); Assert.Equal(1, counters.OutboxDroppedFull); } [Fact] public async Task Store_Disabled_StoresNothing() { var options = new DiagnosticOutboxOptions { Enabled = false }; (DiagnosticOutbox outbox, InMemoryDiagnosticOutboxStore store, _) = Create(options); int stored = await outbox.StoreAsync([OutboxTestHarness.NewEvent(TelemetryPriority.P0)]); Assert.Equal(0, stored); Assert.Equal(0, await store.CountAsync()); } [Fact] public async Task CleanupExpired_RemovesExpiredOnly() { (DiagnosticOutbox outbox, InMemoryDiagnosticOutboxStore store, DiagnosticCounters counters) = Create(); // Utilise une rétention courte pour forcer l'expiration. var options = new DiagnosticOutboxOptions { Retention = TimeSpan.FromMilliseconds(1) }; (outbox, store, counters) = Create(options); await outbox.StoreAsync([OutboxTestHarness.NewEvent(TelemetryPriority.P3)]); await Task.Delay(20); int removed = await outbox.CleanupExpiredAsync(); Assert.Equal(1, removed); Assert.Equal(1, counters.OutboxExpired); Assert.Equal(0, await store.CountAsync()); } [Fact] public async Task Store_ThrowingStore_FailOpen_DegradesAndCounts() { var store = new InMemoryDiagnosticOutboxStore { ThrowOnStore = true }; var counters = new DiagnosticCounters(); var outbox = new DiagnosticOutbox(store, new DiagnosticOutboxOptions(), counters); int stored = await outbox.StoreAsync([OutboxTestHarness.NewEvent(TelemetryPriority.P0)]); Assert.Equal(0, stored); Assert.Equal(1, counters.OutboxDegraded); Assert.True(outbox.IsDegraded); } }