157 lines
6.0 KiB
C#
157 lines
6.0 KiB
C#
using Omika.Diagnostics.Models;
|
|
using Omika.Diagnostics.Queries;
|
|
using Omika.Diagnostics.Tests.Fakes;
|
|
|
|
namespace Omika.Diagnostics.Tests;
|
|
|
|
/// <summary>
|
|
/// Tâche T14-C — Normalisation des dimensions d'agrégation (§9/§10) : les variantes de
|
|
/// casse/espaces de <c>component</c> et <c>error_code</c> ne doivent plus produire de
|
|
/// dimensions distinctes dans les statistiques. Aucune donnée persistée n'est modifiée :
|
|
/// seule la clé d'agrégation est normalisée (déterministe, côté requête).
|
|
/// </summary>
|
|
public class DiagnosticNormalizationTests
|
|
{
|
|
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? component = null, string? errorCode = null)
|
|
{
|
|
return new DiagnosticEventRecord
|
|
{
|
|
EventId = Guid.NewGuid(),
|
|
EventCode = "TEST",
|
|
TimestampUtc = From.AddMinutes(1),
|
|
Component = component,
|
|
ErrorCode = errorCode,
|
|
Level = "Error",
|
|
Priority = "P1",
|
|
Source = DiagnosticSources.Mobile,
|
|
IngestedAtUtc = DateTimeOffset.UtcNow
|
|
};
|
|
}
|
|
|
|
// -------------------------------------------------- T14-C Test 8 : Component --
|
|
[Fact]
|
|
public async Task Component_NormalizesCaseAndWhitespace()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await repo.InsertBatchAsync(new[]
|
|
{
|
|
Ev(component: "Omika.Api"),
|
|
Ev(component: "OMIKA.API"),
|
|
Ev(component: "omika.api"),
|
|
Ev(component: " Omika.Api "),
|
|
Ev(component: "Omika.Worker")
|
|
});
|
|
|
|
IReadOnlyList<DiagnosticGroupCount> result = await repo.CountGroupedAsync(
|
|
DiagnosticGroupDimension.Component,
|
|
new DiagnosticAggregateQuery { FromUtc = From, ToUtc = To });
|
|
|
|
// 4 variantes du même composant + 1 autre = 2 dimensions logiques.
|
|
Assert.Equal(2, result.Count);
|
|
Assert.Contains(result, g => g.Key == "Omika.Api" && g.Count == 4);
|
|
Assert.Contains(result, g => g.Key == "Omika.Worker" && g.Count == 1);
|
|
}
|
|
|
|
// -------------------------------------------------- T14-C Test 9 : ErrorCode --
|
|
[Fact]
|
|
public async Task ErrorCode_NormalizesCaseAndWhitespace()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await repo.InsertBatchAsync(new[]
|
|
{
|
|
Ev(errorCode: "timeout"),
|
|
Ev(errorCode: "TIMEOUT"),
|
|
Ev(errorCode: "Timeout"),
|
|
Ev(errorCode: " timeout "),
|
|
Ev(errorCode: "CRASH")
|
|
});
|
|
|
|
IReadOnlyList<DiagnosticGroupCount> result = await repo.CountGroupedAsync(
|
|
DiagnosticGroupDimension.ErrorCode,
|
|
new DiagnosticAggregateQuery { FromUtc = From, ToUtc = To });
|
|
|
|
// 4 variantes du même code + 1 autre = 2 codes logiques.
|
|
Assert.Equal(2, result.Count);
|
|
Assert.Contains(result, g => g.Key == "TIMEOUT" && g.Count == 4);
|
|
Assert.Contains(result, g => g.Key == "CRASH" && g.Count == 1);
|
|
}
|
|
|
|
// ------------------------- Null/vide restent un groupe distinct (compatibilité) --
|
|
[Fact]
|
|
public async Task ErrorCode_NullAndEmpty_RemainGroupedAsNull()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await repo.InsertBatchAsync(new[]
|
|
{
|
|
Ev(errorCode: null),
|
|
Ev(errorCode: " "),
|
|
Ev(errorCode: "TIMEOUT")
|
|
});
|
|
|
|
IReadOnlyList<DiagnosticGroupCount> result = await repo.CountGroupedAsync(
|
|
DiagnosticGroupDimension.ErrorCode,
|
|
new DiagnosticAggregateQuery { FromUtc = From, ToUtc = To });
|
|
|
|
Assert.Equal(2, result.Count);
|
|
Assert.Contains(result, g => g.Key is null && g.Count == 2);
|
|
Assert.Contains(result, g => g.Key == "TIMEOUT" && g.Count == 1);
|
|
}
|
|
|
|
// ------------------------- T20 §7 : « Codes d'erreur principaux » ----
|
|
[Fact]
|
|
public async Task ErrorCode_WithHasErrorCode_ExcludesNullAndBlank()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await repo.InsertBatchAsync(new[]
|
|
{
|
|
Ev(errorCode: null),
|
|
Ev(errorCode: " "),
|
|
Ev(errorCode: ""),
|
|
Ev(errorCode: "TIMEOUT"),
|
|
Ev(errorCode: "CRASH"),
|
|
});
|
|
|
|
IReadOnlyList<DiagnosticGroupCount> result = await repo.CountGroupedAsync(
|
|
DiagnosticGroupDimension.ErrorCode,
|
|
new DiagnosticAggregateQuery { FromUtc = From, ToUtc = To, HasErrorCode = true });
|
|
|
|
// Seuls les ErrorCode réels comptent : jamais de groupe « Non renseigné ».
|
|
Assert.Equal(2, result.Count);
|
|
Assert.DoesNotContain(result, g => g.Key is null);
|
|
Assert.Contains(result, g => g.Key == "TIMEOUT" && g.Count == 1);
|
|
Assert.Contains(result, g => g.Key == "CRASH" && g.Count == 1);
|
|
}
|
|
|
|
// --------------------- Équivalent C# documenté (unité, sans repository) --------
|
|
[Theory]
|
|
[InlineData("Omika.Api", "Omika.Api")]
|
|
[InlineData("OMIKA.API", "Omika.Api")]
|
|
[InlineData("omika.api", "Omika.Api")]
|
|
[InlineData(" omika.api ", "Omika.Api")]
|
|
[InlineData("Omika.Diagnostics.Worker", "Omika.Diagnostics.Worker")]
|
|
[InlineData(null, null)]
|
|
[InlineData("", null)]
|
|
[InlineData(" ", null)]
|
|
public void ComponentHelper_NormalizesDeterministically(string? input, string? expected)
|
|
{
|
|
Assert.Equal(expected, DiagnosticDimensionNormalization.Component(input));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("timeout", "TIMEOUT")]
|
|
[InlineData("TIMEOUT", "TIMEOUT")]
|
|
[InlineData("Timeout", "TIMEOUT")]
|
|
[InlineData(" timeout ", "TIMEOUT")]
|
|
[InlineData("UnhandledException", "UNHANDLEDEXCEPTION")]
|
|
[InlineData(null, null)]
|
|
[InlineData("", null)]
|
|
[InlineData(" ", null)]
|
|
public void ErrorCodeHelper_NormalizesDeterministically(string? input, string? expected)
|
|
{
|
|
Assert.Equal(expected, DiagnosticDimensionNormalization.ErrorCode(input));
|
|
}
|
|
}
|