215 lines
7.7 KiB
C#
215 lines
7.7 KiB
C#
using Omika.Diagnostics.Models;
|
|
using Omika.Diagnostics.Queries;
|
|
using Omika.Diagnostics.Tests.Fakes;
|
|
|
|
namespace Omika.Diagnostics.Tests;
|
|
|
|
/// <summary>
|
|
/// Tâche 4 — Dashboard : compteurs agrégés (<c>CountAsync</c>,
|
|
/// <c>CountErrorGroupsAsync</c>) et prédicat <c>ErrorsOnly</c> (niveaux d'erreur réels).
|
|
/// Les tests s'appuient sur le <see cref="FakeDiagnosticEventRepository"/> (in-memory) ;
|
|
/// l'implémentation PostgreSQL exécute les mêmes contrats via <c>count(*)</c> et
|
|
/// <c>LOWER(level) IN ('error','critical')</c>.
|
|
/// </summary>
|
|
public class DiagnosticDashboardTests
|
|
{
|
|
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? errorCode = null,
|
|
string? exceptionType = null,
|
|
string level = "Info",
|
|
string priority = "P2") =>
|
|
new()
|
|
{
|
|
EventId = Guid.NewGuid(),
|
|
EventCode = eventCode,
|
|
TimestampUtc = ts ?? From.AddMinutes(1),
|
|
Component = component,
|
|
ErrorCode = errorCode,
|
|
ExceptionType = exceptionType,
|
|
Level = level,
|
|
Priority = priority,
|
|
Source = DiagnosticSources.Mobile,
|
|
IngestedAtUtc = DateTimeOffset.UtcNow
|
|
};
|
|
|
|
private static DiagnosticAggregateQuery All() => new() { FromUtc = From, ToUtc = To };
|
|
|
|
// --------------------------------------------------- CountAsync : total ----
|
|
[Fact]
|
|
public async Task CountAsync_Total_ReturnsCorrectTotal()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await repo.InsertBatchAsync(new[]
|
|
{
|
|
Ev("A", From.AddMinutes(1)),
|
|
Ev("B", From.AddMinutes(2)),
|
|
Ev("C", From.AddMinutes(3))
|
|
});
|
|
|
|
long count = await repo.CountAsync(All());
|
|
|
|
Assert.Equal(3, count);
|
|
}
|
|
|
|
// --------------------------------------------------- CountAsync : période ----
|
|
[Fact]
|
|
public async Task CountAsync_RespectsPeriod()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await repo.InsertBatchAsync(new[]
|
|
{
|
|
Ev("A", From.AddMinutes(-5)), // avant
|
|
Ev("B", From.AddMinutes(5)), // dans
|
|
Ev("C", To.AddMinutes(5)) // après (borne exclusive)
|
|
});
|
|
|
|
long count = await repo.CountAsync(All());
|
|
|
|
Assert.Equal(1, count);
|
|
}
|
|
|
|
// --------------------------------------------------- CountAsync : zéro ----
|
|
[Fact]
|
|
public async Task CountAsync_NoData_ReturnsZero()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
|
|
long count = await repo.CountAsync(All());
|
|
|
|
Assert.Equal(0, count);
|
|
}
|
|
|
|
// ---------------------------------------------- CountAsync : ErrorsOnly ----
|
|
[Fact]
|
|
public async Task CountAsync_ErrorsOnly_CountsErrorLevelsCaseInsensitively()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await repo.InsertBatchAsync(new[]
|
|
{
|
|
Ev("A", From.AddMinutes(1), level: "Error"),
|
|
Ev("B", From.AddMinutes(2), level: "error"), // minuscule (ServerDiagnosticMiddleware)
|
|
Ev("C", From.AddMinutes(3), level: "Critical"),
|
|
Ev("D", From.AddMinutes(4), level: "Info"),
|
|
Ev("E", From.AddMinutes(5), level: "Warning")
|
|
});
|
|
|
|
long count = await repo.CountAsync(new DiagnosticAggregateQuery { FromUtc = From, ToUtc = To, ErrorsOnly = true });
|
|
|
|
Assert.Equal(3, count);
|
|
}
|
|
|
|
// --------------------------------------------- CountAsync : filtre priority ----
|
|
[Fact]
|
|
public async Task CountAsync_RespectsPriorityFilter()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await repo.InsertBatchAsync(new[]
|
|
{
|
|
Ev("A", From.AddMinutes(1), priority: "P0"),
|
|
Ev("B", From.AddMinutes(2), priority: "P0"),
|
|
Ev("C", From.AddMinutes(3), priority: "P1")
|
|
});
|
|
|
|
long count = await repo.CountAsync(new DiagnosticAggregateQuery { FromUtc = From, ToUtc = To, Priority = "P0" });
|
|
|
|
Assert.Equal(2, count);
|
|
}
|
|
|
|
// ------------------------------------------ CountErrorGroupsAsync : distinct ----
|
|
[Fact]
|
|
public async Task CountErrorGroups_DistinctGroups_AreCounted()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await repo.InsertBatchAsync(new[]
|
|
{
|
|
Ev("A", From.AddMinutes(1), exceptionType: "System.TimeoutException"),
|
|
Ev("A", From.AddMinutes(2), exceptionType: "System.TimeoutException"),
|
|
Ev("A", From.AddMinutes(3), exceptionType: "System.NullReferenceException")
|
|
});
|
|
|
|
long count = await repo.CountErrorGroupsAsync(
|
|
new DiagnosticErrorGroupQuery { FromUtc = From, ToUtc = To });
|
|
|
|
Assert.Equal(2, count); // 2 exception_type distincts pour le même event_code
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CountErrorGroups_EquivalentErrors_SingleGroup()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await repo.InsertBatchAsync(new[]
|
|
{
|
|
Ev("ERR", From.AddMinutes(1), exceptionType: "System.TimeoutException"),
|
|
Ev("ERR", From.AddMinutes(2), exceptionType: "system.timeoutexception") // casse ≠ → même groupe
|
|
});
|
|
|
|
long count = await repo.CountErrorGroupsAsync(
|
|
new DiagnosticErrorGroupQuery { FromUtc = From, ToUtc = To });
|
|
|
|
Assert.Equal(1, count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CountErrorGroups_ErrorsOnly_ExcludesNonErrors()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await repo.InsertBatchAsync(new[]
|
|
{
|
|
Ev("ERR", From.AddMinutes(1), exceptionType: "System.Exception", level: "Error"),
|
|
Ev("INFO", From.AddMinutes(2), exceptionType: null, level: "Info") // non-erreur → exclu
|
|
});
|
|
|
|
long count = await repo.CountErrorGroupsAsync(
|
|
new DiagnosticErrorGroupQuery { FromUtc = From, ToUtc = To, ErrorsOnly = true });
|
|
|
|
Assert.Equal(1, count);
|
|
}
|
|
|
|
// ------------------------------------- série temporelle : ErrorsOnly ----
|
|
[Fact]
|
|
public async Task CountByTimeBucket_ErrorsOnly_OnlyErrorLevels()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
var t = new DateTimeOffset(2025, 1, 1, 10, 0, 0, TimeSpan.Zero);
|
|
await repo.InsertBatchAsync(new[]
|
|
{
|
|
Ev("A", t, level: "Error"),
|
|
Ev("B", t.AddMinutes(5), level: "Info"),
|
|
Ev("C", t.AddMinutes(10), level: "critical")
|
|
});
|
|
|
|
IReadOnlyList<DiagnosticBucketCount> result = await repo.CountByTimeBucketAsync(
|
|
DiagnosticTimeBucket.Hour,
|
|
new DiagnosticAggregateQuery { FromUtc = From, ToUtc = To, ErrorsOnly = true });
|
|
|
|
DiagnosticBucketCount bucket = Assert.Single(result);
|
|
Assert.Equal(2, bucket.Count); // Error + critical ; Info exclu
|
|
}
|
|
|
|
// ------------------------------------- groupé : ErrorsOnly ----
|
|
[Fact]
|
|
public async Task CountGrouped_ErrorsOnly_OnlyErrorLevels()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await repo.InsertBatchAsync(new[]
|
|
{
|
|
Ev("ERR", From.AddMinutes(1), level: "Error"),
|
|
Ev("ERR", From.AddMinutes(2), level: "Info")
|
|
});
|
|
|
|
IReadOnlyList<DiagnosticGroupCount> result = await repo.CountGroupedAsync(
|
|
DiagnosticGroupDimension.EventCode,
|
|
new DiagnosticAggregateQuery { FromUtc = From, ToUtc = To, ErrorsOnly = true });
|
|
|
|
DiagnosticGroupCount group = Assert.Single(result);
|
|
Assert.Equal("ERR", group.Key);
|
|
Assert.Equal(1, group.Count);
|
|
}
|
|
}
|