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);
}
}