Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
using Omika.Diagnostics.Models;
|
||||
using Omika.Diagnostics.Queries;
|
||||
using Omika.Diagnostics.Tests.Fakes;
|
||||
|
||||
namespace Omika.Diagnostics.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tâche 6 — Télémétrie : nouvelles dimensions de groupement (Platform/Level normalisé/
|
||||
/// Priority/Source) et statistiques de durée (AVG/MIN/MAX en SQL, NULL ignorés). Tests
|
||||
/// sur <see cref="FakeDiagnosticEventRepository"/> ; la production exécute les mêmes
|
||||
/// contrats via GROUP BY / INITCAP(LOWER(...)) / AVG/MAX.
|
||||
/// </summary>
|
||||
public class DiagnosticTelemetryTests
|
||||
{
|
||||
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 level = "Info",
|
||||
string priority = "P2",
|
||||
string? platform = null,
|
||||
string? source = null,
|
||||
long? durationMs = null) =>
|
||||
new()
|
||||
{
|
||||
EventId = Guid.NewGuid(),
|
||||
EventCode = eventCode,
|
||||
TimestampUtc = ts ?? From.AddMinutes(1),
|
||||
Component = component,
|
||||
Level = level,
|
||||
Priority = priority,
|
||||
Platform = platform,
|
||||
Source = source ?? DiagnosticSources.Mobile,
|
||||
DurationMs = durationMs,
|
||||
IngestedAtUtc = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
private static DiagnosticAggregateQuery All(int limit = DiagnosticAggregateDefaults.MaxLimit) =>
|
||||
new() { FromUtc = From, ToUtc = To, Limit = limit };
|
||||
|
||||
// ------------------------------------------------- T1 : groupement Platform ----
|
||||
[Fact]
|
||||
public async Task CountGrouped_Platform_ReturnsGroups()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
await repo.InsertBatchAsync(new[]
|
||||
{
|
||||
Ev("A", platform: "Android"),
|
||||
Ev("B", platform: "Android"),
|
||||
Ev("C", platform: "iOS")
|
||||
});
|
||||
|
||||
IReadOnlyList<DiagnosticGroupCount> result = await repo.CountGroupedAsync(
|
||||
DiagnosticGroupDimension.Platform, All());
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, g => g.Key == "Android" && g.Count == 2);
|
||||
Assert.Contains(result, g => g.Key == "iOS" && g.Count == 1);
|
||||
}
|
||||
|
||||
// ---------------------------------- T2 : groupement Level normalisé (casse) ----
|
||||
[Fact]
|
||||
public async Task CountGrouped_Level_IsCaseInsensitive()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
await repo.InsertBatchAsync(new[]
|
||||
{
|
||||
Ev("A", level: "Error"),
|
||||
Ev("B", level: "error"), // minuscule (ServerDiagnosticMiddleware)
|
||||
Ev("C", level: "ERROR"),
|
||||
Ev("D", level: "Info")
|
||||
});
|
||||
|
||||
IReadOnlyList<DiagnosticGroupCount> result = await repo.CountGroupedAsync(
|
||||
DiagnosticGroupDimension.Level, All());
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, g => g.Key == "Error" && g.Count == 3);
|
||||
Assert.Contains(result, g => g.Key == "Info" && g.Count == 1);
|
||||
}
|
||||
|
||||
// ----------------------------------------------- T3 : groupement Priority ----
|
||||
[Fact]
|
||||
public async Task CountGrouped_Priority_ReturnsGroups()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
await repo.InsertBatchAsync(new[]
|
||||
{
|
||||
Ev("A", priority: "P0"),
|
||||
Ev("B", priority: "P0"),
|
||||
Ev("C", priority: "P1")
|
||||
});
|
||||
|
||||
IReadOnlyList<DiagnosticGroupCount> result = await repo.CountGroupedAsync(
|
||||
DiagnosticGroupDimension.Priority, All());
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, g => g.Key == "P0" && g.Count == 2);
|
||||
Assert.Contains(result, g => g.Key == "P1" && g.Count == 1);
|
||||
}
|
||||
|
||||
// ---------------------------------------------- T4 : durée moyenne ----
|
||||
[Fact]
|
||||
public async Task GetDurationStats_Average_IsCorrect()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
await repo.InsertBatchAsync(new[]
|
||||
{
|
||||
Ev("A", durationMs: 100),
|
||||
Ev("A", durationMs: 200)
|
||||
});
|
||||
|
||||
DiagnosticDurationStats? stats = await repo.GetDurationStatsAsync(All());
|
||||
|
||||
Assert.NotNull(stats);
|
||||
Assert.Equal(2, stats.Count);
|
||||
Assert.Equal(150, stats.AverageMs);
|
||||
}
|
||||
|
||||
// ---------------------------------------------- T5 : durée max ----
|
||||
[Fact]
|
||||
public async Task GetDurationStats_Max_IsCorrect()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
await repo.InsertBatchAsync(new[]
|
||||
{
|
||||
Ev("A", durationMs: 100),
|
||||
Ev("A", durationMs: 350)
|
||||
});
|
||||
|
||||
DiagnosticDurationStats? stats = await repo.GetDurationStatsAsync(All());
|
||||
|
||||
Assert.NotNull(stats);
|
||||
Assert.Equal(350, stats.MaxMs);
|
||||
Assert.Equal(100, stats.MinMs);
|
||||
}
|
||||
|
||||
// ---------------------------------------------- T6 : durée NULL ignorée ----
|
||||
[Fact]
|
||||
public async Task GetDurationStats_NullDurations_AreIgnored()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
await repo.InsertBatchAsync(new[]
|
||||
{
|
||||
Ev("A", durationMs: 100),
|
||||
Ev("B", durationMs: null), // sans durée
|
||||
Ev("C", durationMs: 200)
|
||||
});
|
||||
|
||||
DiagnosticDurationStats? stats = await repo.GetDurationStatsAsync(All());
|
||||
|
||||
Assert.NotNull(stats);
|
||||
Assert.Equal(2, stats.Count);
|
||||
Assert.Equal(150, stats.AverageMs);
|
||||
Assert.Equal(100, stats.MinMs);
|
||||
Assert.Equal(200, stats.MaxMs);
|
||||
}
|
||||
|
||||
// ---------------------------------------------- T7 : aucune durée → null ----
|
||||
[Fact]
|
||||
public async Task GetDurationStats_NoDurations_ReturnsNull()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
await repo.InsertBatchAsync(new[]
|
||||
{
|
||||
Ev("A", durationMs: null),
|
||||
Ev("B", durationMs: null)
|
||||
});
|
||||
|
||||
DiagnosticDurationStats? stats = await repo.GetDurationStatsAsync(All());
|
||||
|
||||
Assert.Null(stats);
|
||||
}
|
||||
|
||||
// ---------------------------------------------- T8 : période appliquée ----
|
||||
[Fact]
|
||||
public async Task GetDurationStats_RespectsPeriod()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
await repo.InsertBatchAsync(new[]
|
||||
{
|
||||
Ev("A", From.AddMinutes(-5), durationMs: 999), // avant → exclu
|
||||
Ev("A", From.AddMinutes(5), durationMs: 100), // dans
|
||||
Ev("A", To.AddMinutes(5), durationMs: 999) // après → exclu
|
||||
});
|
||||
|
||||
DiagnosticDurationStats? stats = await repo.GetDurationStatsAsync(All());
|
||||
|
||||
Assert.NotNull(stats);
|
||||
Assert.Equal(1, stats.Count);
|
||||
Assert.Equal(100, stats.AverageMs);
|
||||
}
|
||||
|
||||
// ---------------------------------------------- T9 : filtre EventCode ----
|
||||
[Fact]
|
||||
public async Task GetDurationStats_RespectsEventCodeFilter()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
await repo.InsertBatchAsync(new[]
|
||||
{
|
||||
Ev("A", durationMs: 100),
|
||||
Ev("B", durationMs: 900)
|
||||
});
|
||||
|
||||
DiagnosticDurationStats? stats = await repo.GetDurationStatsAsync(
|
||||
new DiagnosticAggregateQuery { FromUtc = From, ToUtc = To, EventCode = "A" });
|
||||
|
||||
Assert.NotNull(stats);
|
||||
Assert.Equal(1, stats.Count);
|
||||
Assert.Equal(100, stats.AverageMs);
|
||||
}
|
||||
|
||||
// ---------------------------------------------- T10 : top lentes limité ----
|
||||
[Fact]
|
||||
public async Task GetTopDurationByEventCode_RespectsLimitAndSort()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
await repo.InsertBatchAsync(new[]
|
||||
{
|
||||
Ev("FAST", durationMs: 10),
|
||||
Ev("SLOW", durationMs: 500),
|
||||
Ev("MEDIUM", durationMs: 250)
|
||||
});
|
||||
|
||||
IReadOnlyList<DiagnosticDurationByEventCode> result = await repo.GetTopDurationByEventCodeAsync(
|
||||
new DiagnosticAggregateQuery { FromUtc = From, ToUtc = To, Limit = 2 });
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Equal("SLOW", result[0].EventCode); // moyenne décroissante
|
||||
Assert.Equal("MEDIUM", result[1].EventCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetTopDurationByEventCode_ExcludesNullDurations()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
await repo.InsertBatchAsync(new[]
|
||||
{
|
||||
Ev("A", durationMs: 100),
|
||||
Ev("B", durationMs: null) // sans durée → ne doit pas apparaître
|
||||
});
|
||||
|
||||
IReadOnlyList<DiagnosticDurationByEventCode> result = await repo.GetTopDurationByEventCodeAsync(All());
|
||||
|
||||
DiagnosticDurationByEventCode item = Assert.Single(result);
|
||||
Assert.Equal("A", item.EventCode);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user