using Omika.Diagnostics.Models; using Omika.Diagnostics.Queries; using Omika.Diagnostics.Tests.Fakes; namespace Omika.Diagnostics.Tests; /// /// Tâche 7 — Versions applicatives : résumés par version et totaux. Tests sur /// ; la production exécute la même sémantique /// via COUNT/FILTER/MIN/MAX/COUNT(DISTINCT) côté PostgreSQL. /// public class DiagnosticVersionTests { 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? appVersion = null, string level = "Info", string? platform = null) => new() { EventId = Guid.NewGuid(), EventCode = eventCode, TimestampUtc = ts ?? From.AddMinutes(1), AppVersion = appVersion, Level = level, Priority = "P2", Platform = platform, Source = DiagnosticSources.Mobile, IngestedAtUtc = DateTimeOffset.UtcNow }; private static DiagnosticAggregateQuery All(int limit = DiagnosticAggregateDefaults.MaxLimit) => new() { FromUtc = From, ToUtc = To, Limit = limit }; // ------------------------------------------------- V1 : deux versions ---- [Fact] public async Task GetVersionSummaries_TwoVersions_TwoSummaries() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("A", appVersion: "1.0.0"), Ev("B", appVersion: "2.0.0") }); IReadOnlyList result = await repo.GetVersionSummariesAsync(All()); Assert.Equal(2, result.Count); } // ------------------------------------------------- V2 : EventCount ---- [Fact] public async Task GetVersionSummaries_EventCount_IsCorrect() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("A", appVersion: "1.0.0"), Ev("B", appVersion: "1.0.0"), Ev("C", appVersion: "1.0.0"), Ev("D", appVersion: "2.0.0") }); IReadOnlyList result = await repo.GetVersionSummariesAsync(All()); DiagnosticVersionSummary v1 = Assert.Single(result, x => x.AppVersion == "1.0.0"); Assert.Equal(3, v1.EventCount); } // --------------------------------- V3 : ErrorCount casse mixte ---- [Fact] public async Task GetVersionSummaries_ErrorCount_HandlesMixedCase() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("A", appVersion: "1.0.0", level: "Error"), Ev("B", appVersion: "1.0.0", level: "error"), // minuscule Ev("C", appVersion: "1.0.0", level: "Critical"), Ev("D", appVersion: "1.0.0", level: "Info") }); IReadOnlyList result = await repo.GetVersionSummariesAsync(All()); DiagnosticVersionSummary v1 = Assert.Single(result); Assert.Equal(3, v1.ErrorCount); // Error + error + Critical ; Info exclu } // --------------------------------- V4 : FirstSeen/LastSeen ---- [Fact] public async Task GetVersionSummaries_FirstAndLastSeen_AreCorrect() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("A", From.AddMinutes(10), appVersion: "1.0.0"), Ev("B", From.AddMinutes(5), appVersion: "1.0.0"), Ev("C", From.AddMinutes(20), appVersion: "1.0.0") }); IReadOnlyList result = await repo.GetVersionSummariesAsync(All()); DiagnosticVersionSummary v1 = Assert.Single(result); Assert.Equal(From.AddMinutes(5), v1.FirstSeenUtc); Assert.Equal(From.AddMinutes(20), v1.LastSeenUtc); } // --------------------------------- V5 : PlatformCount distinct ---- [Fact] public async Task GetVersionSummaries_PlatformCount_IsDistinct() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("A", appVersion: "1.0.0", platform: "Android"), Ev("B", appVersion: "1.0.0", platform: "Android"), Ev("C", appVersion: "1.0.0", platform: "iOS") }); IReadOnlyList result = await repo.GetVersionSummariesAsync(All()); DiagnosticVersionSummary v1 = Assert.Single(result); Assert.Equal(2, v1.PlatformCount); // Android + iOS (distinct) } // --------------------------------- V6 : version null exclue ---- [Fact] public async Task GetVersionSummaries_NullVersion_IsExcluded() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("A", appVersion: "1.0.0"), Ev("B", appVersion: null), Ev("C", appVersion: string.Empty) }); IReadOnlyList result = await repo.GetVersionSummariesAsync(All()); DiagnosticVersionSummary v1 = Assert.Single(result); Assert.Equal("1.0.0", v1.AppVersion); } // --------------------------------- V7 : période appliquée ---- [Fact] public async Task GetVersionSummaries_RespectsPeriod() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("A", From.AddMinutes(-5), appVersion: "1.0.0"), // avant → exclu Ev("B", From.AddMinutes(5), appVersion: "2.0.0"), // dans Ev("C", To.AddMinutes(5), appVersion: "3.0.0") // après → exclu }); IReadOnlyList result = await repo.GetVersionSummariesAsync(All()); DiagnosticVersionSummary v = Assert.Single(result); Assert.Equal("2.0.0", v.AppVersion); } // --------------------------------- V8 : limit appliqué ---- [Fact] public async Task GetVersionSummaries_RespectsLimit() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("A", appVersion: "1.0.0"), Ev("B", appVersion: "2.0.0"), Ev("C", appVersion: "3.0.0") }); IReadOnlyList result = await repo.GetVersionSummariesAsync( new DiagnosticAggregateQuery { FromUtc = From, ToUtc = To, Limit = 2 }); Assert.Equal(2, result.Count); } // --------------------------------- V9 : tri déterministe ---- [Fact] public async Task GetVersionSummaries_SortsByEventCountThenVersion() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("A", appVersion: "1.0.0"), Ev("B", appVersion: "2.0.0"), Ev("C", appVersion: "2.0.0") }); IReadOnlyList result = await repo.GetVersionSummariesAsync(All()); Assert.Equal(new[] { "2.0.0", "1.0.0" }, result.Select(x => x.AppVersion).ToArray()); } [Fact] public async Task GetVersionSummaries_EqualCounts_TieBreaksByVersion() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("A", appVersion: "1.0.0"), Ev("B", appVersion: "2.0.0") }); IReadOnlyList result = await repo.GetVersionSummariesAsync(All()); Assert.Equal(new[] { "1.0.0", "2.0.0" }, result.Select(x => x.AppVersion).ToArray()); } // --------------------------------- V10 : aucune donnée ---- [Fact] public async Task GetVersionSummaries_NoData_ReturnsEmpty() { var repo = new FakeDiagnosticEventRepository(); IReadOnlyList result = await repo.GetVersionSummariesAsync(All()); Assert.Empty(result); } // --------------------------------- Totals ---- [Fact] public async Task GetVersionTotals_ReturnsAllCounters() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("A", appVersion: "1.0.0"), Ev("B", appVersion: "1.0.0"), Ev("C", appVersion: "2.0.0"), Ev("D", appVersion: "2.0.0", level: "Error"), Ev("E", appVersion: null), Ev("F", appVersion: string.Empty) }); DiagnosticVersionTotals totals = await repo.GetVersionTotalsAsync(All()); Assert.Equal(2, totals.DistinctVersionCount); Assert.Equal(4, totals.VersionedEventCount); Assert.Equal(1, totals.VersionedErrorCount); Assert.Equal(2, totals.MissingVersionCount); } }