using Omika.Diagnostics.Models; using Omika.Diagnostics.Queries; using Omika.Diagnostics.Tests.Fakes; namespace Omika.Diagnostics.Tests; /// /// Tâche 4 — Dashboard : compteurs agrégés (CountAsync, /// CountErrorGroupsAsync) et prédicat ErrorsOnly (niveaux d'erreur réels). /// Les tests s'appuient sur le (in-memory) ; /// l'implémentation PostgreSQL exécute les mêmes contrats via count(*) et /// LOWER(level) IN ('error','critical'). /// public class DiagnosticDashboardTests { private static readonly DateTimeOffset From = new(2025, 1, 1, 0, 0, 0, TimeSpan.Zero); private static readonly DateTimeOffset To = new(2025, 2, 1, 0, 0, 0, TimeSpan.Zero); private static DiagnosticEventRecord Ev( string eventCode = "TEST", DateTimeOffset? ts = null, string? component = null, string? errorCode = null, string? exceptionType = null, string level = "Info", string priority = "P2") => new() { EventId = Guid.NewGuid(), EventCode = eventCode, TimestampUtc = ts ?? From.AddMinutes(1), Component = component, ErrorCode = errorCode, ExceptionType = exceptionType, Level = level, Priority = priority, Source = DiagnosticSources.Mobile, IngestedAtUtc = DateTimeOffset.UtcNow }; private static DiagnosticAggregateQuery All() => new() { FromUtc = From, ToUtc = To }; // --------------------------------------------------- CountAsync : total ---- [Fact] public async Task CountAsync_Total_ReturnsCorrectTotal() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("A", From.AddMinutes(1)), Ev("B", From.AddMinutes(2)), Ev("C", From.AddMinutes(3)) }); long count = await repo.CountAsync(All()); Assert.Equal(3, count); } // --------------------------------------------------- CountAsync : période ---- [Fact] public async Task CountAsync_RespectsPeriod() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("A", From.AddMinutes(-5)), // avant Ev("B", From.AddMinutes(5)), // dans Ev("C", To.AddMinutes(5)) // après (borne exclusive) }); long count = await repo.CountAsync(All()); Assert.Equal(1, count); } // --------------------------------------------------- CountAsync : zéro ---- [Fact] public async Task CountAsync_NoData_ReturnsZero() { var repo = new FakeDiagnosticEventRepository(); long count = await repo.CountAsync(All()); Assert.Equal(0, count); } // ---------------------------------------------- CountAsync : ErrorsOnly ---- [Fact] public async Task CountAsync_ErrorsOnly_CountsErrorLevelsCaseInsensitively() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("A", From.AddMinutes(1), level: "Error"), Ev("B", From.AddMinutes(2), level: "error"), // minuscule (ServerDiagnosticMiddleware) Ev("C", From.AddMinutes(3), level: "Critical"), Ev("D", From.AddMinutes(4), level: "Info"), Ev("E", From.AddMinutes(5), level: "Warning") }); long count = await repo.CountAsync(new DiagnosticAggregateQuery { FromUtc = From, ToUtc = To, ErrorsOnly = true }); Assert.Equal(3, count); } // --------------------------------------------- CountAsync : filtre priority ---- [Fact] public async Task CountAsync_RespectsPriorityFilter() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("A", From.AddMinutes(1), priority: "P0"), Ev("B", From.AddMinutes(2), priority: "P0"), Ev("C", From.AddMinutes(3), priority: "P1") }); long count = await repo.CountAsync(new DiagnosticAggregateQuery { FromUtc = From, ToUtc = To, Priority = "P0" }); Assert.Equal(2, count); } // ------------------------------------------ CountErrorGroupsAsync : distinct ---- [Fact] public async Task CountErrorGroups_DistinctGroups_AreCounted() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("A", From.AddMinutes(1), exceptionType: "System.TimeoutException"), Ev("A", From.AddMinutes(2), exceptionType: "System.TimeoutException"), Ev("A", From.AddMinutes(3), exceptionType: "System.NullReferenceException") }); long count = await repo.CountErrorGroupsAsync( new DiagnosticErrorGroupQuery { FromUtc = From, ToUtc = To }); Assert.Equal(2, count); // 2 exception_type distincts pour le même event_code } [Fact] public async Task CountErrorGroups_EquivalentErrors_SingleGroup() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("ERR", From.AddMinutes(1), exceptionType: "System.TimeoutException"), Ev("ERR", From.AddMinutes(2), exceptionType: "system.timeoutexception") // casse ≠ → même groupe }); long count = await repo.CountErrorGroupsAsync( new DiagnosticErrorGroupQuery { FromUtc = From, ToUtc = To }); Assert.Equal(1, count); } [Fact] public async Task CountErrorGroups_ErrorsOnly_ExcludesNonErrors() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("ERR", From.AddMinutes(1), exceptionType: "System.Exception", level: "Error"), Ev("INFO", From.AddMinutes(2), exceptionType: null, level: "Info") // non-erreur → exclu }); long count = await repo.CountErrorGroupsAsync( new DiagnosticErrorGroupQuery { FromUtc = From, ToUtc = To, ErrorsOnly = true }); Assert.Equal(1, count); } // ------------------------------------- série temporelle : ErrorsOnly ---- [Fact] public async Task CountByTimeBucket_ErrorsOnly_OnlyErrorLevels() { var repo = new FakeDiagnosticEventRepository(); var t = new DateTimeOffset(2025, 1, 1, 10, 0, 0, TimeSpan.Zero); await repo.InsertBatchAsync(new[] { Ev("A", t, level: "Error"), Ev("B", t.AddMinutes(5), level: "Info"), Ev("C", t.AddMinutes(10), level: "critical") }); IReadOnlyList result = await repo.CountByTimeBucketAsync( DiagnosticTimeBucket.Hour, new DiagnosticAggregateQuery { FromUtc = From, ToUtc = To, ErrorsOnly = true }); DiagnosticBucketCount bucket = Assert.Single(result); Assert.Equal(2, bucket.Count); // Error + critical ; Info exclu } // ------------------------------------- groupé : ErrorsOnly ---- [Fact] public async Task CountGrouped_ErrorsOnly_OnlyErrorLevels() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("ERR", From.AddMinutes(1), level: "Error"), Ev("ERR", From.AddMinutes(2), level: "Info") }); IReadOnlyList result = await repo.CountGroupedAsync( DiagnosticGroupDimension.EventCode, new DiagnosticAggregateQuery { FromUtc = From, ToUtc = To, ErrorsOnly = true }); DiagnosticGroupCount group = Assert.Single(result); Assert.Equal("ERR", group.Key); Assert.Equal(1, group.Count); } }