using Omika.Diagnostics.Models; using Omika.Diagnostics.Queries; using Omika.Diagnostics.Tests.Fakes; namespace Omika.Diagnostics.Tests; /// /// Tâche 13-F — Filtres de contexte de déploiement (ClientCode / ServerInstance / /// Environment) : filtres backend (F1-F5), regroupement d'erreurs pré-agrégation /// (F6), et valeurs distinctes bornées/ordonnées/sans doublon (F7-F10). Le tout sur /// le (in-memory), miroir du repository /// PostgreSQL réel (mêmes contrats, §51). /// public class DiagnosticDeploymentFilterTests { private static readonly DateTimeOffset From = new(2025, 1, 1, 0, 0, 0, TimeSpan.Zero); private static DiagnosticEventRecord Ev( string eventCode = "TEST", string? clientCode = null, string? serverInstance = null, string? environment = null, DateTimeOffset? ts = null, string level = "Info", string? exceptionType = null, string? component = null, string? errorCode = null) { DiagnosticEventRecord record = DiagnosticTestData.Event(Guid.NewGuid(), eventCode, ts ?? From, component: component, level: level, errorCode: errorCode); record.ClientCode = clientCode; record.ServerInstance = serverInstance; record.Environment = environment; record.ExceptionType = exceptionType; return record; } // --------------------------- F1 : filtre ClientCode -------------------------- [Fact] public async Task F1_ClientCodeFilter_ReturnsOnlyMatchingClient() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev(clientCode: "CLIENT-A"), Ev(clientCode: "CLIENT-B") }); DiagnosticQueryResult result = await repo.QueryAsync(new DiagnosticEventQuery { ClientCode = "CLIENT-A" }); DiagnosticEventRecord record = Assert.Single(result.Items); Assert.Equal("CLIENT-A", record.ClientCode); } // --------------------------- F2 : filtre ServerInstance ---------------------- [Fact] public async Task F2_ServerInstanceFilter_ReturnsOnlyMatchingInstance() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev(serverInstance: "WKR-SRV-01"), Ev(serverInstance: "API-SRV-01") }); DiagnosticQueryResult result = await repo.QueryAsync(new DiagnosticEventQuery { ServerInstance = "WKR-SRV-01" }); DiagnosticEventRecord record = Assert.Single(result.Items); Assert.Equal("WKR-SRV-01", record.ServerInstance); } // --------------------------- F3 : filtre Environment ------------------------- [Fact] public async Task F3_EnvironmentFilter_ExcludesOtherEnvironments() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev(environment: "Production"), Ev(environment: "Staging"), Ev(environment: "Development") }); DiagnosticQueryResult result = await repo.QueryAsync(new DiagnosticEventQuery { Environment = "Production" }); DiagnosticEventRecord record = Assert.Single(result.Items); Assert.Equal("Production", record.Environment); } // --------------------------- F4 : combinaison des trois ---------------------- [Fact] public async Task F4_CombinedFilters_ApplyAllThreeSimultaneously() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev(clientCode: "CLIENT-A", serverInstance: "API-SRV-01", environment: "Production"), Ev(clientCode: "CLIENT-A", serverInstance: "API-SRV-01", environment: "Staging"), Ev(clientCode: "CLIENT-A", serverInstance: "WKR-SRV-01", environment: "Production"), Ev(clientCode: "CLIENT-B", serverInstance: "API-SRV-01", environment: "Production") }); DiagnosticQueryResult result = await repo.QueryAsync(new DiagnosticEventQuery { ClientCode = "CLIENT-A", ServerInstance = "API-SRV-01", Environment = "Production" }); DiagnosticEventRecord record = Assert.Single(result.Items); Assert.Equal("CLIENT-A", record.ClientCode); Assert.Equal("API-SRV-01", record.ServerInstance); Assert.Equal("Production", record.Environment); } // --------------------------- F5 : NULL historiques --------------------------- [Fact] public async Task F5_HistoricalNullContext_IsReturnedWithoutFilter() { var repo = new FakeDiagnosticEventRepository(); DiagnosticEventRecord historical = Ev(clientCode: null, serverInstance: null, environment: null); await repo.InsertBatchAsync(new[] { historical, Ev(clientCode: "CLIENT-A", serverInstance: "SRV-1", environment: "Production") }); DiagnosticQueryResult result = await repo.QueryAsync(new DiagnosticEventQuery()); Assert.Equal(2, result.TotalCount); Assert.Contains(result.Items, e => e.ClientCode is null && e.ServerInstance is null && e.Environment is null); } // --------------------------- F6 : ErrorGroup pré-agrégation ------------------ [Fact] public async Task F6_ErrorGroupFilter_IsAppliedBeforeAggregation() { var repo = new FakeDiagnosticEventRepository(); // Même signature d'erreur logique (event_code + exception + component) répartie // sur deux clients / environnements. await repo.InsertBatchAsync(new[] { Ev("ERR", "CLIENT-A", "SRV-1", "Production", exceptionType: "System.E", component: "Omika.Api", level: "Error"), Ev("ERR", "CLIENT-A", "SRV-1", "Production", exceptionType: "System.E", component: "Omika.Api", level: "Error"), Ev("ERR", "CLIENT-B", "SRV-1", "Staging", exceptionType: "System.E", component: "Omika.Api", level: "Error"), Ev("ERR", "CLIENT-A", "SRV-1", "Staging", exceptionType: "System.E", component: "Omika.Api", level: "Error") }); IReadOnlyList all = await repo.GroupErrorsAsync( new DiagnosticErrorGroupQuery { FromUtc = From, ToUtc = From.AddDays(1) }); DiagnosticErrorGroup allGroup = Assert.Single(all); Assert.Equal(4, allGroup.OccurrenceCount); IReadOnlyList filtered = await repo.GroupErrorsAsync( new DiagnosticErrorGroupQuery { FromUtc = From, ToUtc = From.AddDays(1), ClientCode = "CLIENT-A", Environment = "Production" }); DiagnosticErrorGroup filteredGroup = Assert.Single(filtered); Assert.Equal(2, filteredGroup.OccurrenceCount); } // --------------------------- F7 : distinct ClientCode ------------------------ [Fact] public async Task F7_DistinctClientCodes_AreDistinctSorted() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev(clientCode: "CLIENT-B"), Ev(clientCode: "CLIENT-A"), Ev(clientCode: "CLIENT-A"), Ev(clientCode: null) // exclu }); IReadOnlyList result = await repo.GetFilterOptionsAsync( DiagnosticFilterDimension.ClientCode, From, DiagnosticFilterDefaults.ClientCodeOptionsLimit); Assert.Equal(new[] { "CLIENT-A", "CLIENT-B" }, result); } // --------------------------- F8 : distinct ServerInstance -------------------- [Fact] public async Task F8_DistinctServerInstances_AreDistinctSorted() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev(serverInstance: "WKR-SRV-01"), Ev(serverInstance: "API-SRV-01"), Ev(serverInstance: "WKR-SRV-01") }); IReadOnlyList result = await repo.GetFilterOptionsAsync( DiagnosticFilterDimension.ServerInstance, From, DiagnosticFilterDefaults.ServerInstanceOptionsLimit); Assert.Equal(new[] { "API-SRV-01", "WKR-SRV-01" }, result); } // --------------------------- F9 : distinct Environment ----------------------- [Fact] public async Task F9_DistinctEnvironments_AreDistinctSorted() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev(environment: "Production"), Ev(environment: "Development"), Ev(environment: "Production"), Ev(environment: "Staging") }); IReadOnlyList result = await repo.GetFilterOptionsAsync( DiagnosticFilterDimension.Environment, From, DiagnosticFilterDefaults.EnvironmentOptionsLimit); Assert.Equal(new[] { "Development", "Production", "Staging" }, result); } // --------------------------- F10 : limite des options ------------------------ [Fact] public async Task F10_DistinctOptions_RespectLimit() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev(clientCode: "CLIENT-A"), Ev(clientCode: "CLIENT-B"), Ev(clientCode: "CLIENT-C"), Ev(clientCode: "CLIENT-D") }); IReadOnlyList result = await repo.GetFilterOptionsAsync( DiagnosticFilterDimension.ClientCode, From, 2); Assert.Equal(2, result.Count); } }