Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
using Omika.Core.Observability;
|
||||
using Omika.Diagnostics.Models;
|
||||
using Omika.Diagnostics.Queries;
|
||||
using Omika.Diagnostics.Tests.Fakes;
|
||||
|
||||
namespace Omika.Diagnostics.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// T18 : statistiques d'USAGE (événementiel) — vues/écrans, appareils distincts,
|
||||
/// sessions distinctes, fonctionnalités. Unité = événements ScreenView/FeatureUse,
|
||||
/// jamais la dernière observation d'un appareil (§54).
|
||||
/// </summary>
|
||||
public class DiagnosticUsageTests
|
||||
{
|
||||
private static readonly string ScreenView = TelemetryEventCodes.MobileScreenView.Value!;
|
||||
private static readonly string FeatureUse = TelemetryEventCodes.MobileFeatureUse.Value!;
|
||||
|
||||
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);
|
||||
|
||||
[Fact]
|
||||
public async Task GetScreenUsage_CountsViewsDistinctDevicesDistinctSessions()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
Guid s1 = Guid.NewGuid();
|
||||
Guid s2 = Guid.NewGuid();
|
||||
Guid s3 = Guid.NewGuid();
|
||||
|
||||
await repo.InsertBatchAsync(
|
||||
[
|
||||
Screen("AGENDA", "D-A", s1, 1),
|
||||
Screen("AGENDA", "D-A", s1, 2),
|
||||
Screen("AGENDA", "D-A", s2, 3),
|
||||
Screen("AGENDA", "D-B", s1, 4),
|
||||
Screen("AGENDA", "D-B", s3, 5),
|
||||
Screen("AGENDA", "D-B", s3, 6),
|
||||
Screen("AGENDA", "D-B", s3, 7),
|
||||
Screen("AGENDA", "D-B", s3, 8),
|
||||
Screen("AGENDA", "D-B", s3, 9),
|
||||
Screen("AGENDA", "D-B", s3, 10)
|
||||
]);
|
||||
|
||||
IReadOnlyList<ScreenUsageRecord> result = await repo.GetScreenUsageAsync(Query());
|
||||
|
||||
ScreenUsageRecord item = Assert.Single(result);
|
||||
Assert.Equal("AGENDA", item.ScreenCode);
|
||||
Assert.Equal(10, item.Views);
|
||||
Assert.Equal(2, item.DistinctDevices);
|
||||
Assert.Equal(3, item.DistinctSessions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetScreenUsage_DeviceKeyNull_IgnoredInDistinctDevices()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
Guid session = Guid.NewGuid();
|
||||
|
||||
var events = new List<DiagnosticEventRecord>();
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
events.Add(Screen("AGENDA", null, session, 1 + i));
|
||||
}
|
||||
|
||||
events.Add(Screen("AGENDA", "D-A", session, 10));
|
||||
events.Add(Screen("AGENDA", "D-A", session, 11));
|
||||
events.Add(Screen("AGENDA", "D-A", session, 12));
|
||||
events.Add(Screen("AGENDA", "D-A", session, 13));
|
||||
events.Add(Screen("AGENDA", "D-A", session, 14));
|
||||
|
||||
await repo.InsertBatchAsync(events);
|
||||
|
||||
ScreenUsageRecord item = Assert.Single(await repo.GetScreenUsageAsync(Query()));
|
||||
Assert.Equal(10, item.Views);
|
||||
Assert.Equal(1, item.DistinctDevices);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetScreenUsage_SessionNull_IgnoredInDistinctSessions()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
Guid session = Guid.NewGuid();
|
||||
|
||||
await repo.InsertBatchAsync(
|
||||
[
|
||||
Screen("AGENDA", "D-A", null, 1),
|
||||
Screen("AGENDA", "D-A", null, 2),
|
||||
Screen("AGENDA", "D-A", session, 3)
|
||||
]);
|
||||
|
||||
ScreenUsageRecord item = Assert.Single(await repo.GetScreenUsageAsync(Query()));
|
||||
Assert.Equal(3, item.Views);
|
||||
Assert.Equal(1, item.DistinctSessions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetScreenUsage_NormalizesCaseAndTrims()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
await repo.InsertBatchAsync(
|
||||
[
|
||||
Screen("AGENDA", "D-A", Guid.NewGuid(), 1),
|
||||
Screen("agenda", "D-A", Guid.NewGuid(), 2),
|
||||
Screen(" AGENDA ", "D-A", Guid.NewGuid(), 3),
|
||||
Screen("SITE_DETAIL", "D-A", Guid.NewGuid(), 4)
|
||||
]);
|
||||
|
||||
IReadOnlyList<ScreenUsageRecord> result = await repo.GetScreenUsageAsync(Query());
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Equal("AGENDA", result[0].ScreenCode);
|
||||
Assert.Equal(3, result[0].Views);
|
||||
Assert.Equal("SITE_DETAIL", result[1].ScreenCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetScreenUsage_ExcludesNullOrEmptyScreenCode()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
await repo.InsertBatchAsync(
|
||||
[
|
||||
Screen(null, "D-A", Guid.NewGuid(), 1),
|
||||
Screen("", "D-A", Guid.NewGuid(), 2),
|
||||
Screen("AGENDA", "D-A", Guid.NewGuid(), 3)
|
||||
]);
|
||||
|
||||
ScreenUsageRecord item = Assert.Single(await repo.GetScreenUsageAsync(Query()));
|
||||
Assert.Equal("AGENDA", item.ScreenCode);
|
||||
Assert.Equal(1, item.Views);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetScreenUsage_AppliesFilters()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
await repo.InsertBatchAsync(
|
||||
[
|
||||
Screen("AGENDA", "D-A", Guid.NewGuid(), 1, client: "CLIENT-A", license: "LIC-1", platform: "Android", os: "Android", version: "1.4.0"),
|
||||
Screen("AGENDA", "D-B", Guid.NewGuid(), 2, client: "CLIENT-B", license: "LIC-2", platform: "iOS", os: "iOS", version: "1.4.1"),
|
||||
Screen("SITE_DETAIL", "D-A", Guid.NewGuid(), 3, client: "CLIENT-A", license: "LIC-1", platform: "Android", os: "Android", version: "1.4.0")
|
||||
]);
|
||||
|
||||
ScreenUsageRecord byClient = Assert.Single(await repo.GetScreenUsageAsync(Query(clientCode: "CLIENT-B")));
|
||||
Assert.Equal("AGENDA", byClient.ScreenCode);
|
||||
|
||||
// LIC-1 est observée sur AGENDA ET SITE_DETAIL (même DeviceKey, deux écrans).
|
||||
IReadOnlyList<ScreenUsageRecord> byLicense = await repo.GetScreenUsageAsync(Query(licenseNumber: "LIC-1"));
|
||||
Assert.Equal(2, byLicense.Count);
|
||||
Assert.Contains(byLicense, s => s.ScreenCode == "AGENDA" && s.Views == 1);
|
||||
Assert.Contains(byLicense, s => s.ScreenCode == "SITE_DETAIL" && s.Views == 1);
|
||||
|
||||
ScreenUsageRecord byPlatform = Assert.Single(await repo.GetScreenUsageAsync(Query(platform: "iOS")));
|
||||
Assert.Equal("AGENDA", byPlatform.ScreenCode);
|
||||
|
||||
ScreenUsageRecord byScreen = Assert.Single(await repo.GetScreenUsageAsync(Query(screenCode: "SITE_DETAIL")));
|
||||
Assert.Equal("SITE_DETAIL", byScreen.ScreenCode);
|
||||
Assert.Equal(1, byScreen.Views);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetScreenUsage_LicenseReassigned_CountsBothDevicesWithoutMerging()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
await repo.InsertBatchAsync(
|
||||
[
|
||||
Screen("AGENDA", "D-A", Guid.NewGuid(), 1, license: "LIC-REASSIGNED"),
|
||||
Screen("AGENDA", "D-B", Guid.NewGuid(), 2, license: "LIC-REASSIGNED")
|
||||
]);
|
||||
|
||||
IReadOnlyList<ScreenUsageRecord> result = await repo.GetScreenUsageAsync(Query(licenseNumber: "LIC-REASSIGNED"));
|
||||
|
||||
ScreenUsageRecord item = Assert.Single(result);
|
||||
Assert.Equal("AGENDA", item.ScreenCode);
|
||||
Assert.Equal(2, item.Views);
|
||||
Assert.Equal(2, item.DistinctDevices); // jamais fusionné en 1 appareil
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetFeatureUsage_CountsUsesAndDistinctDevices()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
await repo.InsertBatchAsync(
|
||||
[
|
||||
Feature("EMAIL_SEND", "D-A", 1),
|
||||
Feature("EMAIL_SEND", "D-A", 2),
|
||||
Feature("EMAIL_SEND", "D-B", 3),
|
||||
Feature("REPORT_GENERATE", "D-A", 4)
|
||||
]);
|
||||
|
||||
IReadOnlyList<FeatureUsageRecord> result = await repo.GetFeatureUsageAsync(Query());
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Equal("EMAIL_SEND", result[0].FeatureCode);
|
||||
Assert.Equal(3, result[0].Uses);
|
||||
Assert.Equal(2, result[0].DistinctDevices);
|
||||
Assert.Equal("REPORT_GENERATE", result[1].FeatureCode);
|
||||
Assert.Equal(1, result[1].Uses);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetFeatureUsage_FiltersByFeatureCode()
|
||||
{
|
||||
var repo = new FakeDiagnosticEventRepository();
|
||||
await repo.InsertBatchAsync(
|
||||
[
|
||||
Feature("EMAIL_SEND", "D-A", 1),
|
||||
Feature("REPORT_GENERATE", "D-A", 2)
|
||||
]);
|
||||
|
||||
FeatureUsageRecord item = Assert.Single(await repo.GetFeatureUsageAsync(Query(featureCode: "REPORT_GENERATE")));
|
||||
Assert.Equal("REPORT_GENERATE", item.FeatureCode);
|
||||
Assert.Equal(1, item.Uses);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UsageTelemetryQuery_Validate_RequiresPeriod()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new UsageTelemetryQuery().Validate());
|
||||
Assert.Throws<ArgumentException>(() => new UsageTelemetryQuery { FromUtc = To, ToUtc = From }.Validate());
|
||||
}
|
||||
|
||||
private static DiagnosticEventRecord Screen(
|
||||
string? screenCode,
|
||||
string? deviceKey,
|
||||
Guid? sessionId,
|
||||
long id,
|
||||
string? client = null,
|
||||
string? license = null,
|
||||
string? platform = null,
|
||||
string? os = null,
|
||||
string? version = null) =>
|
||||
new()
|
||||
{
|
||||
Id = id,
|
||||
EventId = Guid.NewGuid(),
|
||||
EventCode = ScreenView,
|
||||
TimestampUtc = From.AddMinutes(id),
|
||||
ScreenCode = screenCode,
|
||||
DeviceKey = deviceKey,
|
||||
SessionId = sessionId,
|
||||
ClientCode = client,
|
||||
LicenseNumber = license,
|
||||
Platform = platform,
|
||||
OperatingSystem = os,
|
||||
AppVersion = version,
|
||||
Source = "mobile",
|
||||
IngestedAtUtc = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
private static DiagnosticEventRecord Feature(string featureCode, string deviceKey, long id) =>
|
||||
new()
|
||||
{
|
||||
Id = id,
|
||||
EventId = Guid.NewGuid(),
|
||||
EventCode = FeatureUse,
|
||||
TimestampUtc = From.AddMinutes(id),
|
||||
FeatureCode = featureCode,
|
||||
DeviceKey = deviceKey,
|
||||
Source = "mobile",
|
||||
IngestedAtUtc = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
private static UsageTelemetryQuery Query(
|
||||
string? clientCode = null,
|
||||
string? licenseNumber = null,
|
||||
string? platform = null,
|
||||
string? screenCode = null,
|
||||
string? featureCode = null) =>
|
||||
new()
|
||||
{
|
||||
FromUtc = From,
|
||||
ToUtc = To,
|
||||
ClientCode = clientCode,
|
||||
LicenseNumber = licenseNumber,
|
||||
Platform = platform,
|
||||
ScreenCode = screenCode,
|
||||
FeatureCode = featureCode,
|
||||
Limit = 50
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user