Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
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.
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user