Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
using System.Text.Json;
|
||||
using Omika.Core.Observability;
|
||||
using Omika.Core.Observability.Diagnostic;
|
||||
|
||||
namespace Omika.Tests.Observability.Diagnostic;
|
||||
|
||||
public class LocalFileTelemetryExporterTests : IDisposable
|
||||
{
|
||||
private readonly string _dir = DiagnosticTestHarness.CreateTempDirectory();
|
||||
|
||||
[Fact]
|
||||
public async Task Export_WritesValidJsonLine()
|
||||
{
|
||||
var exporter = new LocalFileTelemetryExporter(new LocalDiagnosticOptions { Directory = _dir });
|
||||
|
||||
await exporter.ExportAsync([Event()]);
|
||||
await exporter.DisposeAsync();
|
||||
|
||||
string file = Assert.Single(Directory.EnumerateFiles(_dir, "*.jsonl"));
|
||||
string[] lines = File.ReadAllLines(file);
|
||||
Assert.Single(lines);
|
||||
|
||||
using JsonDocument doc = JsonDocument.Parse(lines[0]);
|
||||
Assert.Equal("OMK-MOB-APP-001", doc.RootElement.GetProperty("eventCode").GetString());
|
||||
Assert.Equal("INFO", doc.RootElement.GetProperty("level").GetString());
|
||||
Assert.Equal("P3", doc.RootElement.GetProperty("priority").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Export_PreservesFrenchAccents()
|
||||
{
|
||||
var exporter = new LocalFileTelemetryExporter(new LocalDiagnosticOptions { Directory = _dir });
|
||||
var e = new TelemetryEvent
|
||||
{
|
||||
EventCode = TelemetryEventCodes.MobileAppStarted,
|
||||
Component = "échec-créé-démarré-télémétrie"
|
||||
};
|
||||
|
||||
await exporter.ExportAsync([e]);
|
||||
await exporter.DisposeAsync();
|
||||
|
||||
string content = File.ReadAllText(Assert.Single(Directory.EnumerateFiles(_dir, "*.jsonl")));
|
||||
Assert.Contains("échec", content);
|
||||
Assert.Contains("créé", content);
|
||||
Assert.Contains("démarré", content);
|
||||
Assert.Contains("télémétrie", content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Export_SerializesUsageAndDeviceDimensions()
|
||||
{
|
||||
// T17-B/T18 : le fichier local doit porter appBuild, dimensions d'écran et
|
||||
// usage fonctionnel (screenCode/featureCode), alignés sur le DTO transport.
|
||||
var exporter = new LocalFileTelemetryExporter(new LocalDiagnosticOptions { Directory = _dir });
|
||||
var e = new TelemetryEvent
|
||||
{
|
||||
EventCode = TelemetryEventCodes.MobileScreenView,
|
||||
Component = "Navigation",
|
||||
AppBuild = "1",
|
||||
ScreenWidth = 1220,
|
||||
ScreenHeight = 2712,
|
||||
LogicalWidth = 375,
|
||||
LogicalHeight = 834,
|
||||
DisplayScale = 3.25,
|
||||
ScreenCode = DiagnosticScreenCodes.SiteDetail,
|
||||
FeatureCode = DiagnosticFeatureCodes.ReportGenerate
|
||||
};
|
||||
|
||||
await exporter.ExportAsync([e]);
|
||||
await exporter.DisposeAsync();
|
||||
|
||||
using JsonDocument doc = JsonDocument.Parse(
|
||||
File.ReadAllLines(Assert.Single(Directory.EnumerateFiles(_dir, "*.jsonl")))[0]);
|
||||
Assert.Equal("1", doc.RootElement.GetProperty("appBuild").GetString());
|
||||
Assert.Equal(1220, doc.RootElement.GetProperty("screenWidth").GetInt32());
|
||||
Assert.Equal(2712, doc.RootElement.GetProperty("screenHeight").GetInt32());
|
||||
Assert.Equal(DiagnosticScreenCodes.SiteDetail, doc.RootElement.GetProperty("screenCode").GetString());
|
||||
Assert.Equal(DiagnosticFeatureCodes.ReportGenerate, doc.RootElement.GetProperty("featureCode").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Export_SerializesExceptionDetail()
|
||||
{
|
||||
// Phase 6 : le fichier local (joint à l'email support) porte le détail
|
||||
// d'exception (message/type/stack trace), aligné sur le DTO transport.
|
||||
var exporter = new LocalFileTelemetryExporter(new LocalDiagnosticOptions { Directory = _dir });
|
||||
var e = new DiagnosticEvent
|
||||
{
|
||||
EventCode = TelemetryEventCodes.MobileHandledException,
|
||||
Message = "Échec d'écriture",
|
||||
ExceptionType = "System.IO.IOException",
|
||||
StackTrace = "at Omika.App.Services.SyncEngine.Run()"
|
||||
};
|
||||
|
||||
await exporter.ExportAsync([e]);
|
||||
await exporter.DisposeAsync();
|
||||
|
||||
using JsonDocument doc = JsonDocument.Parse(
|
||||
File.ReadAllLines(Assert.Single(Directory.EnumerateFiles(_dir, "*.jsonl")))[0]);
|
||||
Assert.Equal("Échec d'écriture", doc.RootElement.GetProperty("message").GetString());
|
||||
Assert.Equal("System.IO.IOException", doc.RootElement.GetProperty("exceptionType").GetString());
|
||||
Assert.Equal("at Omika.App.Services.SyncEngine.Run()", doc.RootElement.GetProperty("stackTrace").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ActiveExporter_AllowsConcurrentRead_CompleteJsonLines()
|
||||
{
|
||||
var exporter = new LocalFileTelemetryExporter(new LocalDiagnosticOptions { Directory = _dir });
|
||||
|
||||
// L'exporteur reste ACTIF (fichier ouvert en écriture, FileShare.Read).
|
||||
await exporter.ExportAsync([Event(), Event()]);
|
||||
string file = Assert.Single(Directory.EnumerateFiles(_dir, "*.jsonl"));
|
||||
|
||||
// Lecture concurrente : FileShare.ReadWrite (le writer autorise la lecture).
|
||||
// File.ReadAllText (FileShare.Read) échouerait ici avec « used by another process ».
|
||||
using var stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var reader = new StreamReader(stream);
|
||||
string content = reader.ReadToEnd();
|
||||
|
||||
// Le JSONL lu est exploitable : deux lignes JSON complètes, aucune demi-ligne.
|
||||
string[] lines = content.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
Assert.Equal(2, lines.Length);
|
||||
foreach (string line in lines)
|
||||
{
|
||||
using JsonDocument doc = JsonDocument.Parse(line);
|
||||
Assert.Equal("OMK-MOB-APP-001", doc.RootElement.GetProperty("eventCode").GetString());
|
||||
}
|
||||
|
||||
await exporter.DisposeAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Rotation_WhenSizeExceeded()
|
||||
{
|
||||
var exporter = new LocalFileTelemetryExporter(new LocalDiagnosticOptions
|
||||
{
|
||||
Directory = _dir,
|
||||
MaxFileSizeBytes = 300,
|
||||
MaxFiles = 20
|
||||
});
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
await exporter.ExportAsync([Event()]);
|
||||
}
|
||||
|
||||
await exporter.DisposeAsync();
|
||||
|
||||
Assert.True(Directory.EnumerateFiles(_dir, "*.jsonl").Count() >= 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MaxFiles_IsEnforced()
|
||||
{
|
||||
var exporter = new LocalFileTelemetryExporter(new LocalDiagnosticOptions
|
||||
{
|
||||
Directory = _dir,
|
||||
MaxFileSizeBytes = 100,
|
||||
MaxFiles = 3
|
||||
});
|
||||
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
await exporter.ExportAsync([Event()]);
|
||||
}
|
||||
|
||||
await exporter.DisposeAsync();
|
||||
|
||||
Assert.True(Directory.EnumerateFiles(_dir, "*.jsonl").Count() <= 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InaccessibleDirectory_DoesNotThrow()
|
||||
{
|
||||
// Un FICHIER existant utilisé comme dossier → Directory.CreateDirectory échoue.
|
||||
string fileAsDirectory = Path.Combine(_dir, "not-a-dir");
|
||||
File.WriteAllText(fileAsDirectory, "x");
|
||||
|
||||
var exporter = new LocalFileTelemetryExporter(new LocalDiagnosticOptions { Directory = fileAsDirectory });
|
||||
|
||||
await exporter.ExportAsync([Event()]);
|
||||
await exporter.DisposeAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EmptyDirectory_NoFileCreated()
|
||||
{
|
||||
var exporter = new LocalFileTelemetryExporter(new LocalDiagnosticOptions { Directory = string.Empty });
|
||||
|
||||
await exporter.ExportAsync([Event()]);
|
||||
await exporter.DisposeAsync();
|
||||
|
||||
Assert.Empty(Directory.EnumerateFiles(_dir, "*.jsonl"));
|
||||
}
|
||||
|
||||
private static OmikaEvent Event() => new TelemetryEvent { EventCode = TelemetryEventCodes.MobileAppStarted };
|
||||
|
||||
public void Dispose() => DiagnosticTestHarness.DeleteDirectory(_dir);
|
||||
}
|
||||
Reference in New Issue
Block a user