using Omika.Diagnostics.Queries; using Omika.Diagnostics.Tests.Fakes; namespace Omika.Diagnostics.Tests; /// /// Tâche 12 — Options de filtre : conservation de la valeur active (C8/C9) et /// comportement des valeurs distinctes (borne, tri, exclusion des vides). /// public class DiagnosticFilterOptionsTests { private static readonly DateTimeOffset From = new(2025, 1, 1, 0, 0, 0, TimeSpan.Zero); // --------------------------- C8 : filtre EventCode conserve sa valeur ---- [Fact] public void EnsureIncluded_KeepsActiveEventCodeAtFrontWithoutDuplicate() { IReadOnlyList options = new[] { "OMK-API-HTTP-001", "OMK-API-HTTP-003" }; IReadOnlyList result = DiagnosticFilterDefaults.EnsureIncluded(options, "OMK-WKR-SYNC-002"); Assert.Equal(3, result.Count); Assert.Equal("OMK-WKR-SYNC-002", result[0]); Assert.Equal("OMK-API-HTTP-001", result[1]); Assert.Equal("OMK-API-HTTP-003", result[2]); } // ----------------------------- C9 : filtre version conserve sa valeur ---- [Fact] public void EnsureIncluded_KeepsActiveVersionWithoutDuplicate() { IReadOnlyList options = new[] { "1.0.0.0", "1.1.0.0" }; IReadOnlyList result = DiagnosticFilterDefaults.EnsureIncluded(options, "2.0.0.0"); Assert.Equal(3, result.Count); Assert.Contains("2.0.0.0", result); Assert.Equal(1, result.Count(v => v == "2.0.0.0")); } [Fact] public void EnsureIncluded_ReturnsSameListWhenAlreadyPresent() { IReadOnlyList options = new[] { "1.0.0.0", "1.1.0.0" }; IReadOnlyList result = DiagnosticFilterDefaults.EnsureIncluded(options, "1.0.0.0"); Assert.Same(options, result); Assert.Equal(2, result.Count); } // ------------------------------------ Options : distinct, triées, bornées ---- [Fact] public async Task GetFilterOptions_ReturnsDistinctSortedBoundedValues() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { DiagnosticTestData.Event(Guid.NewGuid(), "A", From.AddMinutes(1), component: "Zeta"), DiagnosticTestData.Event(Guid.NewGuid(), "B", From.AddMinutes(2), component: "Alpha"), DiagnosticTestData.Event(Guid.NewGuid(), "C", From.AddMinutes(3), component: "Alpha"), DiagnosticTestData.Event(Guid.NewGuid(), "D", From.AddMinutes(4), component: null), // exclu (null) DiagnosticTestData.Event(Guid.NewGuid(), "E", From.AddMinutes(5), component: " "), // exclu (vide) }); IReadOnlyList result = await repo.GetFilterOptionsAsync( DiagnosticFilterDimension.Component, From, 10); Assert.Equal(new[] { "Alpha", "Zeta" }, result); } [Fact] public async Task GetFilterOptions_RespectsLimit() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { DiagnosticTestData.Event(Guid.NewGuid(), "A", From.AddMinutes(1), component: "C1"), DiagnosticTestData.Event(Guid.NewGuid(), "B", From.AddMinutes(2), component: "C2"), DiagnosticTestData.Event(Guid.NewGuid(), "C", From.AddMinutes(3), component: "C3"), }); IReadOnlyList result = await repo.GetFilterOptionsAsync( DiagnosticFilterDimension.Component, From, 2); Assert.Equal(2, result.Count); } // ---------------- T20 §3/§6 : options Component normalisées (une seule option) ---- [Fact] public async Task GetFilterOptions_Component_NormalizesCaseAndWhitespace() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { DiagnosticTestData.Event(Guid.NewGuid(), "A", From.AddMinutes(1), component: "Omika.Api"), DiagnosticTestData.Event(Guid.NewGuid(), "B", From.AddMinutes(2), component: "omika.api"), DiagnosticTestData.Event(Guid.NewGuid(), "C", From.AddMinutes(3), component: "OMIKA.API"), DiagnosticTestData.Event(Guid.NewGuid(), "D", From.AddMinutes(4), component: " Omika.Api "), DiagnosticTestData.Event(Guid.NewGuid(), "E", From.AddMinutes(5), component: "Omika.Worker"), }); IReadOnlyList result = await repo.GetFilterOptionsAsync( DiagnosticFilterDimension.Component, From, 10); // 4 variantes du même composant + 1 autre = 2 options logiques (tri ascendant). Assert.Equal(new[] { "Omika.Api", "Omika.Worker" }, result); } // ---------------- T20 §4/§6 : options ErrorCode normalisées (une seule option) ---- [Fact] public async Task GetFilterOptions_ErrorCode_NormalizesCaseAndWhitespace() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { DiagnosticTestData.Event(Guid.NewGuid(), "A", From.AddMinutes(1), errorCode: "timeout"), DiagnosticTestData.Event(Guid.NewGuid(), "B", From.AddMinutes(2), errorCode: "TIMEOUT"), DiagnosticTestData.Event(Guid.NewGuid(), "C", From.AddMinutes(3), errorCode: " Timeout"), DiagnosticTestData.Event(Guid.NewGuid(), "D", From.AddMinutes(4), errorCode: " timeout "), DiagnosticTestData.Event(Guid.NewGuid(), "E", From.AddMinutes(5), errorCode: "CRASH"), }); IReadOnlyList result = await repo.GetFilterOptionsAsync( DiagnosticFilterDimension.ErrorCode, From, 10); // 4 variantes du même code + 1 autre = 2 options logiques (tri ascendant). Assert.Equal(new[] { "CRASH", "TIMEOUT" }, result); } }