256 lines
9.8 KiB
C#
256 lines
9.8 KiB
C#
using System.Text.Json;
|
|
using Omika.Core.Observability.External;
|
|
using Omika.Core.Observability.Identity;
|
|
using Omika.Core.Observability.Outbox;
|
|
using Omika.Diagnostics.Import;
|
|
using Omika.Diagnostics.Models;
|
|
|
|
namespace Omika.Diagnostics.Tests;
|
|
|
|
/// <summary>
|
|
/// Tâche 17-B — Mapping Worker + transport JSONL des dimensions appareil/licence :
|
|
/// <list type="bullet">
|
|
/// <item>Le mapper ne perd plus OperatingSystem/OSVersion/DeviceModel/DeviceManufacturer.</item>
|
|
/// <item>LicenseNumber/DeviceKey/DeviceKeyVersion + AppBuild + Screen*/DisplayScale sont mappés.</item>
|
|
/// <item>Le store écrit les champs serveur (licenseNumber/deviceKey/deviceKeyVersion) en JSONL
|
|
/// et continue d'épurer le DeviceId brut.</item>
|
|
/// <item>Toutes les valeurs null restent null (compatibilité anciennes lignes).</item>
|
|
/// </list>
|
|
/// Aucun PostgreSQL réel requis.
|
|
/// </summary>
|
|
public class DiagnosticDeviceLicenseMapperTests : IDisposable
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
|
|
|
private readonly string _dir = CreateTempDirectory();
|
|
|
|
public void Dispose() => DeleteDirectory(_dir);
|
|
|
|
// --------------------------- M1 : mapper conserve toutes les dimensions ------
|
|
[Fact]
|
|
public void M1_Mapper_PreservesDeviceAndLicenseDimensions()
|
|
{
|
|
RemoteDiagnosticRecord record = NewRecord(
|
|
Guid.NewGuid(),
|
|
licenseNumber: "LIC-123",
|
|
deviceKey: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
deviceKeyVersion: 1,
|
|
manufacturer: "Xiaomi",
|
|
model: "POCO Demo",
|
|
os: "Android",
|
|
osVersion: "16",
|
|
appBuild: "1410",
|
|
screenWidth: 1080, screenHeight: 2400,
|
|
logicalWidth: 360, logicalHeight: 800,
|
|
displayScale: 3.0);
|
|
|
|
DiagnosticEventRecord? mapped = DiagnosticEventRecordMapper.ToRecord(record, "mobile");
|
|
|
|
Assert.NotNull(mapped);
|
|
Assert.Equal("LIC-123", mapped!.LicenseNumber);
|
|
Assert.Equal("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", mapped.DeviceKey);
|
|
Assert.Equal(1, mapped.DeviceKeyVersion);
|
|
Assert.Equal("Xiaomi", mapped.DeviceManufacturer);
|
|
Assert.Equal("POCO Demo", mapped.DeviceModel);
|
|
Assert.Equal("Android", mapped.OperatingSystem);
|
|
Assert.Equal("16", mapped.OperatingSystemVersion);
|
|
Assert.Equal("1410", mapped.AppBuild);
|
|
Assert.Equal(1080, mapped.ScreenWidth);
|
|
Assert.Equal(2400, mapped.ScreenHeight);
|
|
Assert.Equal(360, mapped.LogicalWidth);
|
|
Assert.Equal(800, mapped.LogicalHeight);
|
|
Assert.Equal(3.0, mapped.DisplayScale);
|
|
}
|
|
|
|
// --------------------------- M2 : toutes valeurs null → null ----------------
|
|
[Fact]
|
|
public void M2_Mapper_AllNull_LeavesAllDimensionsNull()
|
|
{
|
|
RemoteDiagnosticRecord record = NewRecord(Guid.NewGuid());
|
|
|
|
DiagnosticEventRecord? mapped = DiagnosticEventRecordMapper.ToRecord(record, "mobile");
|
|
|
|
Assert.NotNull(mapped);
|
|
Assert.Null(mapped!.LicenseNumber);
|
|
Assert.Null(mapped.DeviceKey);
|
|
Assert.Null(mapped.DeviceKeyVersion);
|
|
Assert.Null(mapped.DeviceManufacturer);
|
|
Assert.Null(mapped.DeviceModel);
|
|
Assert.Null(mapped.OperatingSystem);
|
|
Assert.Null(mapped.OperatingSystemVersion);
|
|
Assert.Null(mapped.AppBuild);
|
|
Assert.Null(mapped.ScreenWidth);
|
|
Assert.Null(mapped.ScreenHeight);
|
|
Assert.Null(mapped.LogicalWidth);
|
|
Assert.Null(mapped.LogicalHeight);
|
|
Assert.Null(mapped.DisplayScale);
|
|
}
|
|
|
|
// --------------------------- M3 : JSONL conserve les champs serveur ---------
|
|
[Fact]
|
|
public async Task M3_Store_WritesServerIdentityFields_AndPreservesOnReadBack()
|
|
{
|
|
var deviceKey = new DiagnosticDeviceKey("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", 1);
|
|
DiagnosticEventDto evt = NewEvent(Guid.NewGuid(), deviceId: "device-raw-secret");
|
|
|
|
string content = await WriteAsync(evt, deviceKey, licenseNumber: "LIC-456");
|
|
|
|
using JsonDocument doc = JsonDocument.Parse(content);
|
|
Assert.Equal("LIC-456", doc.RootElement.GetProperty("licenseNumber").GetString());
|
|
Assert.Equal(deviceKey.Value, doc.RootElement.GetProperty("deviceKey").GetString());
|
|
Assert.Equal(1, doc.RootElement.GetProperty("deviceKeyVersion").GetInt32());
|
|
|
|
RemoteDiagnosticRecord? record = JsonSerializer.Deserialize<RemoteDiagnosticRecord>(content, JsonOptions);
|
|
Assert.NotNull(record);
|
|
DiagnosticEventRecord? mapped = DiagnosticEventRecordMapper.ToRecord(record, "mobile");
|
|
Assert.NotNull(mapped);
|
|
Assert.Equal("LIC-456", mapped!.LicenseNumber);
|
|
Assert.Equal(deviceKey.Value, mapped.DeviceKey);
|
|
Assert.Equal(1, mapped.DeviceKeyVersion);
|
|
}
|
|
|
|
// --------------------------- M4 : DeviceId brut jamais persisté ---------------
|
|
[Fact]
|
|
public async Task M4_Store_NeverWritesRawDeviceId()
|
|
{
|
|
DiagnosticEventDto evt = NewEvent(Guid.NewGuid(), deviceId: "device-raw-secret");
|
|
|
|
string content = await WriteAsync(evt, DiagnosticDeviceKey.Empty, licenseNumber: null);
|
|
|
|
Assert.DoesNotContain("\"deviceId\"", content, StringComparison.OrdinalIgnoreCase);
|
|
Assert.DoesNotContain("device-raw-secret", content, StringComparison.Ordinal);
|
|
}
|
|
|
|
// --------------------------- M5 : absence serveur → champs omis --------------
|
|
[Fact]
|
|
public async Task M5_Store_AbsentIdentity_OmitsServerFields()
|
|
{
|
|
string content = await WriteAsync(NewEvent(Guid.NewGuid()), DiagnosticDeviceKey.Empty, licenseNumber: null);
|
|
|
|
using JsonDocument doc = JsonDocument.Parse(content);
|
|
Assert.False(doc.RootElement.TryGetProperty("licenseNumber", out _));
|
|
Assert.False(doc.RootElement.TryGetProperty("deviceKey", out _));
|
|
Assert.False(doc.RootElement.TryGetProperty("deviceKeyVersion", out _));
|
|
}
|
|
|
|
// --------------------------- M6 : dimensions écran null → transport null -----
|
|
[Fact]
|
|
public void M6_Mapper_NullScreenDimensions_RemainNull()
|
|
{
|
|
RemoteDiagnosticRecord record = NewRecord(Guid.NewGuid(), screenWidth: null, displayScale: null);
|
|
|
|
DiagnosticEventRecord? mapped = DiagnosticEventRecordMapper.ToRecord(record, "mobile");
|
|
|
|
Assert.NotNull(mapped);
|
|
Assert.Null(mapped!.ScreenWidth);
|
|
Assert.Null(mapped.DisplayScale);
|
|
}
|
|
|
|
// --------------------------- Helpers ------------------------------------------
|
|
private static RemoteDiagnosticRecord NewRecord(
|
|
Guid eventId,
|
|
string? licenseNumber = null,
|
|
string? deviceKey = null,
|
|
int? deviceKeyVersion = null,
|
|
string? manufacturer = null,
|
|
string? model = null,
|
|
string? os = null,
|
|
string? osVersion = null,
|
|
string? appBuild = null,
|
|
int? screenWidth = null,
|
|
int? screenHeight = null,
|
|
int? logicalWidth = null,
|
|
int? logicalHeight = null,
|
|
double? displayScale = null)
|
|
{
|
|
DateTimeOffset ts = DateTimeOffset.UtcNow;
|
|
return new RemoteDiagnosticRecord
|
|
{
|
|
ReceivedAtUtc = ts,
|
|
BatchId = Guid.NewGuid(),
|
|
IngestionTraceId = Guid.NewGuid().ToString("D"),
|
|
LicenseNumber = licenseNumber,
|
|
DeviceKey = deviceKey,
|
|
DeviceKeyVersion = deviceKeyVersion,
|
|
Event = new DiagnosticEventDto
|
|
{
|
|
EventId = eventId,
|
|
EventCode = "OMK-MOB-APP-001",
|
|
TimestampUtc = ts,
|
|
Level = "Info",
|
|
Priority = "P3",
|
|
DeviceManufacturer = manufacturer,
|
|
DeviceModel = model,
|
|
OperatingSystem = os,
|
|
OperatingSystemVersion = osVersion,
|
|
AppBuild = appBuild,
|
|
ScreenWidth = screenWidth,
|
|
ScreenHeight = screenHeight,
|
|
LogicalWidth = logicalWidth,
|
|
LogicalHeight = logicalHeight,
|
|
DisplayScale = displayScale
|
|
}
|
|
};
|
|
}
|
|
|
|
private static DiagnosticEventDto NewEvent(Guid eventId, string? deviceId = null) => new()
|
|
{
|
|
EventId = eventId,
|
|
EventCode = "OMK-MOB-APP-001",
|
|
TimestampUtc = DateTimeOffset.UtcNow,
|
|
Level = "Info",
|
|
Priority = "P3",
|
|
DeviceId = deviceId
|
|
};
|
|
|
|
private async Task<string> WriteAsync(
|
|
DiagnosticEventDto evt,
|
|
DiagnosticDeviceKey deviceKey,
|
|
string? licenseNumber)
|
|
{
|
|
var store = new RemoteDiagnosticIngestionStore(new RemoteDiagnosticOptions { Directory = _dir });
|
|
var batch = new DiagnosticBatchDto { Events = [evt] };
|
|
|
|
await store.StoreAsync(
|
|
batch,
|
|
DiagnosticActorKey.Empty,
|
|
"trace-id",
|
|
DateTimeOffset.UtcNow,
|
|
CancellationToken.None,
|
|
deploymentContext: null,
|
|
deviceKey: deviceKey,
|
|
licenseNumber: licenseNumber);
|
|
|
|
return ReadAllTextShared(Assert.Single(Directory.EnumerateFiles(_dir, "*.jsonl")));
|
|
}
|
|
|
|
private static string CreateTempDirectory()
|
|
{
|
|
string dir = Path.Combine(Path.GetTempPath(), "omika-device-license-tests", Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(dir);
|
|
return dir;
|
|
}
|
|
|
|
private static string ReadAllTextShared(string path)
|
|
{
|
|
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
|
using var reader = new StreamReader(stream);
|
|
return reader.ReadToEnd();
|
|
}
|
|
|
|
private static void DeleteDirectory(string dir)
|
|
{
|
|
try
|
|
{
|
|
if (Directory.Exists(dir))
|
|
{
|
|
Directory.Delete(dir, recursive: true);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Best-effort.
|
|
}
|
|
}
|
|
}
|