using Omika.Diagnostics.Models;
using Omika.Diagnostics.Queries;
using Omika.Diagnostics.Tests.Fakes;
namespace Omika.Diagnostics.Tests;
///
/// Tâche 4 — Dashboard : compteurs agrégés (CountAsync,
/// CountErrorGroupsAsync) et prédicat ErrorsOnly (niveaux d'erreur réels).
/// Les tests s'appuient sur le (in-memory) ;
/// l'implémentation PostgreSQL exécute les mêmes contrats via count(*) et
/// LOWER(level) IN ('error','critical').
///
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 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 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);
}
}