Files
Omika/tests/Omika.Diagnostics.Tests/DiagnosticSyncTests.cs
T
2026-10-23 18:46:50 +02:00

246 lines
8.4 KiB
C#

using Omika.Diagnostics.Models;
using Omika.Diagnostics.Queries;
using Omika.Diagnostics.Tests.Fakes;
namespace Omika.Diagnostics.Tests;
/// <summary>
/// Tâche 8 — Synchronisation : résumé sync et filtres par liste fermée d'EventCodes.
/// Tests sur <see cref="FakeDiagnosticEventRepository"/> ; la production exécute la même
/// sémantique via COUNT/COUNT(DISTINCT)/FILTER/AVG côté PostgreSQL.
/// </summary>
public class DiagnosticSyncTests
{
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,
DateTimeOffset? ts = null,
Guid? operationId = null,
string level = "Info",
long? durationMs = null) =>
new()
{
EventId = Guid.NewGuid(),
EventCode = eventCode,
TimestampUtc = ts ?? From.AddMinutes(1),
OperationId = operationId,
Level = level,
Priority = "P2",
DurationMs = durationMs,
Source = DiagnosticSources.Mobile,
IngestedAtUtc = DateTimeOffset.UtcNow
};
private static DiagnosticAggregateQuery All() => new() { FromUtc = From, ToUtc = To };
// ------------------------------------------------- S1 : summary multi ----
[Fact]
public async Task GetSyncSummary_MultipleEvents_ReturnsSummary()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev("OMK-MOB-SYNC-001", operationId: Guid.NewGuid()),
Ev("OMK-WKR-SYNC-001", operationId: Guid.NewGuid())
});
DiagnosticSyncSummary summary = await repo.GetSyncSummaryAsync(All());
Assert.Equal(2, summary.EventCount);
}
// ------------------------------------------------- S2 : EventCount ----
[Fact]
public async Task GetSyncSummary_EventCount_CountsOnlySync()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev("OMK-MOB-SYNC-001"),
Ev("OMK-WKR-SYNC-001"),
Ev("NOT-SYNC")
});
DiagnosticSyncSummary summary = await repo.GetSyncSummaryAsync(All());
Assert.Equal(2, summary.EventCount);
}
// ------------------------------------------------- S3 : OperationCount distinct ----
[Fact]
public async Task GetSyncSummary_OperationCount_IsDistinct()
{
Guid opA = Guid.NewGuid();
Guid opB = Guid.NewGuid();
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev("OMK-MOB-SYNC-001", operationId: opA),
Ev("OMK-MOB-SYNC-001", operationId: opA), // même opération
Ev("OMK-WKR-SYNC-001", operationId: opB)
});
DiagnosticSyncSummary summary = await repo.GetSyncSummaryAsync(All());
Assert.Equal(2, summary.OperationCount);
}
// ------------------------------------------------- S4 : OperationId null exclu ----
[Fact]
public async Task GetSyncSummary_NullOperationId_NotCounted()
{
Guid opA = Guid.NewGuid();
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev("OMK-MOB-SYNC-001", operationId: opA),
Ev("OMK-MOB-SYNC-001", operationId: null) // sans OperationId
});
DiagnosticSyncSummary summary = await repo.GetSyncSummaryAsync(All());
Assert.Equal(1, summary.OperationCount);
Assert.Equal(2, summary.EventCount); // l'événement sans OperationId reste compté
}
// ------------------------------------------------- S5 : ErrorCount casse mixte ----
[Fact]
public async Task GetSyncSummary_ErrorCount_MixedCase()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev("OMK-WKR-SYNC-002", level: "Error"),
Ev("OMK-WKR-SYNC-002", level: "error"),
Ev("OMK-WKR-SYNC-002", level: "Critical"),
Ev("OMK-WKR-SYNC-001", level: "Info")
});
DiagnosticSyncSummary summary = await repo.GetSyncSummaryAsync(All());
Assert.Equal(3, summary.ErrorCount);
}
// ------------------------------------------------- S6 : durée avg/min/max ----
[Fact]
public async Task GetSyncSummary_DurationStats_AreCorrect()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev("OMK-WKR-SYNC-001", durationMs: 100),
Ev("OMK-WKR-SYNC-001", durationMs: 300)
});
DiagnosticSyncSummary summary = await repo.GetSyncSummaryAsync(All());
Assert.Equal(2, summary.DurationCount);
Assert.Equal(200, summary.AverageMs);
Assert.Equal(100, summary.MinMs);
Assert.Equal(300, summary.MaxMs);
}
// ------------------------------------------------- S7 : durée NULL ignorée ----
[Fact]
public async Task GetSyncSummary_NullDurations_Ignored()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev("OMK-WKR-SYNC-001", durationMs: 100),
Ev("OMK-WKR-SYNC-001", durationMs: null)
});
DiagnosticSyncSummary summary = await repo.GetSyncSummaryAsync(All());
Assert.Equal(1, summary.DurationCount);
Assert.Equal(100, summary.AverageMs);
}
[Fact]
public async Task GetSyncSummary_NoDurations_NullStats()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev("OMK-MOB-SYNC-001", durationMs: null)
});
DiagnosticSyncSummary summary = await repo.GetSyncSummaryAsync(All());
Assert.Equal(0, summary.DurationCount);
Assert.Null(summary.AverageMs);
Assert.Null(summary.MinMs);
Assert.Null(summary.MaxMs);
}
// ------------------------------------------------- S8 : période ----
[Fact]
public async Task GetSyncSummary_RespectsPeriod()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev("OMK-MOB-SYNC-001", From.AddMinutes(-5)), // avant
Ev("OMK-MOB-SYNC-001", From.AddMinutes(5)), // dans
Ev("OMK-MOB-SYNC-001", To.AddMinutes(5)) // après
});
DiagnosticSyncSummary summary = await repo.GetSyncSummaryAsync(All());
Assert.Equal(1, summary.EventCount);
}
// ------------------------------------------------- S9 : non-sync exclu ----
[Fact]
public async Task GetSyncSummary_NonSyncExcluded()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev("OMK-MOB-SYNC-001"),
Ev("OMK-MOB-APP-001") // non-sync
});
DiagnosticSyncSummary summary = await repo.GetSyncSummaryAsync(All());
Assert.Equal(1, summary.EventCount);
}
// ------------------------------------------------- S10 : reconnaissance ----
[Theory]
[InlineData("OMK-MOB-SYNC-001", true)]
[InlineData("OMK-WKR-SYNC-001", true)]
[InlineData("OMK-WKR-SYNC-002", true)]
[InlineData("OMK-MOB-APP-001", false)]
[InlineData(null, false)]
public void SyncEventCodes_Recognition(string? code, bool expected)
{
Assert.Equal(expected, DiagnosticSyncEventCodes.IsSyncEventCode(code));
}
// ------------------------------------------------- S11 : distribution EventCode ----
[Fact]
public async Task CountGrouped_EventCode_SyncOnly_ReturnsOnlySyncCodes()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev("OMK-MOB-SYNC-001"),
Ev("OMK-MOB-SYNC-001"),
Ev("OMK-WKR-SYNC-001"),
Ev("OMK-MOB-APP-001") // non-sync → exclu
});
IReadOnlyList<DiagnosticGroupCount> result = await repo.CountGroupedAsync(
DiagnosticGroupDimension.EventCode,
new DiagnosticAggregateQuery { FromUtc = From, ToUtc = To, SyncOnly = true });
Assert.Equal(2, result.Count);
Assert.DoesNotContain(result, g => g.Key == "OMK-MOB-APP-001");
Assert.Contains(result, g => g.Key == "OMK-MOB-SYNC-001" && g.Count == 2);
}
}