Premiere commit - Omika

This commit is contained in:
2026-10-23 18:46:50 +02:00
commit 8ea76c1b24
2155 changed files with 984301 additions and 0 deletions
@@ -0,0 +1,216 @@
using Omika.Diagnostics.Models;
using Omika.Diagnostics.Tests.Fakes;
using Omika.Diagnostics.Web.Pages;
namespace Omika.Diagnostics.Tests;
/// <summary>
/// Tâche T13-G — Tests PageModel multi-clients (G25-G32) : conservation des filtres
/// dans /Diagnostics, pagination, détail, libellé NULL, non-confusion du serveur
/// Worker, variation des statistiques Dashboard/Telemetry par client. PageModels
/// testés sans HttpContext (constructeur repository uniquement, cf. infra T13-F).
/// </summary>
public class DiagnosticMultiClientPageModelTests
{
private const string ClientA = "DEMO-CLIENT-A";
private const string ClientB = "DEMO-CLIENT-B";
private const string ClientC = "DEMO-CLIENT-C";
private const string ApiA = "API-A-01";
private const string WkrA = "WORKER-A-01";
private static readonly DateTimeOffset From = new(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
private static DiagnosticEventRecord Ev(
long id,
string eventCode = "TEST",
string? client = null,
string? server = null,
string? environment = null,
DateTimeOffset? ts = null,
string level = "Info",
string? component = null,
Guid? operationId = null)
{
DiagnosticEventRecord record = DiagnosticTestData.Event(Guid.NewGuid(), eventCode, ts ?? From, component: component, level: level);
record.Id = id;
record.ClientCode = client;
record.ServerInstance = server;
record.Environment = environment;
record.OperationId = operationId;
return record;
}
// --------------------------- G25 : filtre Client conservé ------------------
[Fact]
public async Task G25_DiagnosticsModel_SelectedClientIsPreserved()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev(1, client: ClientA, server: ApiA, environment: "Production"),
Ev(2, client: ClientB, server: "API-B-01", environment: "Production")
});
var model = new DiagnosticsModel(repo) { ClientCode = ClientA };
await model.OnGetAsync();
DiagnosticEventRecord record = Assert.Single(model.Items);
Assert.Equal(ClientA, record.ClientCode);
Assert.Equal(ClientA, model.ClientCode);
}
// --------------------------- G26 : pagination conserve 3 filtres -----------
[Fact]
public void G26_DiagnosticsModel_RoutePreservesAllThreeFilters()
{
var model = new DiagnosticsModel(new FakeDiagnosticEventRepository())
{
ClientCode = ClientA,
ServerInstance = WkrA,
Environment = "Production"
};
Dictionary<string, string> route = model.Route(3);
Assert.Equal("3", route["pageNumber"]);
Assert.Equal(ClientA, route["clientCode"]);
Assert.Equal(WkrA, route["serverInstance"]);
Assert.Equal("Production", route["environment"]);
}
// --------------------------- G27 : détail affiche les 3 valeurs ------------
[Fact]
public async Task G27_DiagnosticsDetail_ShowsDeploymentContext()
{
var repo = new FakeDiagnosticEventRepository();
DiagnosticEventRecord record = Ev(42, client: ClientA, server: ApiA, environment: "Production");
await repo.InsertBatchAsync(new[] { record });
var model = new DiagnosticsDetailModel(repo);
Microsoft.AspNetCore.Mvc.IActionResult result = await model.OnGetAsync(42);
Assert.IsType<Microsoft.AspNetCore.Mvc.RazorPages.PageResult>(result);
Assert.NotNull(model.Event);
Assert.Equal(ClientA, model.Event!.ClientCode);
Assert.Equal(ApiA, model.Event.ServerInstance);
Assert.Equal("Production", model.Event.Environment);
}
// --------------------------- G28 : NULL → « Non renseigné » ----------------
[Fact]
public async Task G28_NullContext_RendersNonRenseigneAndLoadsWithoutCrash()
{
var repo = new FakeDiagnosticEventRepository();
DiagnosticEventRecord record = Ev(7, client: null, server: null, environment: null);
await repo.InsertBatchAsync(new[] { record });
var model = new DiagnosticsDetailModel(repo);
Microsoft.AspNetCore.Mvc.IActionResult result = await model.OnGetAsync(7);
// Le détail se charge sans planter et l'événement NULL reste lisible.
Assert.IsType<Microsoft.AspNetCore.Mvc.RazorPages.PageResult>(result);
Assert.NotNull(model.Event);
Assert.Null(model.Event!.ClientCode);
// L'UI affichera « Non renseigné » pour chacune des trois valeurs NULL.
Assert.Equal("Non renseigné", UiDeploymentContext.Value(model.Event.ClientCode));
Assert.Equal("Non renseigné", UiDeploymentContext.Value(model.Event.ServerInstance));
Assert.Equal("Non renseigné", UiDeploymentContext.Value(model.Event.Environment));
}
// --------------------------- G29 : serveur Worker ≠ serveur API ------------
[Fact]
public async Task G29_WorkerOccurrence_ShowsWorkerServerNotApiServer()
{
var repo = new FakeDiagnosticEventRepository();
Guid operationId = Guid.NewGuid();
await repo.InsertBatchAsync(new[]
{
Ev(1, "OMK-API-HTTP-001", ClientA, ApiA, "Production", operationId: operationId, component: "Omika.Api"),
Ev(2, "OMK-WKR-SYNC-001", ClientA, WkrA, "Production", operationId: operationId, component: "Omika.Worker")
});
var model = new TracesModel(repo) { OperationId = operationId.ToString() };
await model.OnGetAsync();
Assert.Equal(2, model.Timeline.Count);
DiagnosticEventRecord worker = model.Timeline.Single(e => e.Component == "Omika.Worker");
Assert.Equal(WkrA, worker.ServerInstance);
Assert.NotEqual(ApiA, worker.ServerInstance);
}
// --------------------------- G30 : dashboard varie par client --------------
[Fact]
public async Task G30_Dashboard_StatsVaryByClient()
{
var repo = new FakeDiagnosticEventRepository();
DateTimeOffset now = DateTimeOffset.UtcNow;
await repo.InsertBatchAsync(new[]
{
Ev(1, client: ClientA, server: ApiA, environment: "Production", ts: now.AddMinutes(-30)),
Ev(2, client: ClientA, server: ApiA, environment: "Production", ts: now.AddMinutes(-20)),
Ev(3, client: ClientA, server: WkrA, environment: "Production", ts: now.AddMinutes(-10)),
Ev(4, client: ClientB, server: "API-B-01", environment: "Production", ts: now.AddMinutes(-5))
});
var aModel = new IndexModel(repo) { ClientCode = ClientA };
await aModel.OnGetAsync();
var bModel = new IndexModel(repo) { ClientCode = ClientB };
await bModel.OnGetAsync();
// Les statistiques changent réellement selon le client.
Assert.Equal(3, aModel.TotalEvents);
Assert.Equal(1, bModel.TotalEvents);
Assert.NotEqual(aModel.TotalEvents, bModel.TotalEvents);
}
// --------------------------- G31 : Telemetry filtre Client -----------------
[Fact]
public async Task G31_Telemetry_ClientFilterModifiesDataset()
{
var repo = new FakeDiagnosticEventRepository();
DateTimeOffset now = DateTimeOffset.UtcNow;
await repo.InsertBatchAsync(new[]
{
Ev(1, client: ClientA, server: ApiA, environment: "Production", ts: now.AddMinutes(-30)),
Ev(2, client: ClientA, server: WkrA, environment: "Production", ts: now.AddMinutes(-20)),
Ev(3, client: ClientB, server: "API-B-01", environment: "Production", ts: now.AddMinutes(-10)),
Ev(4, client: ClientC, server: "API-C-DEV-01", environment: "Development", ts: now.AddMinutes(-5))
});
var aModel = new TelemetryModel(repo) { ClientCode = ClientA };
await aModel.OnGetAsync();
var cModel = new TelemetryModel(repo) { ClientCode = ClientC };
await cModel.OnGetAsync();
Assert.Equal(2, aModel.TotalEvents);
Assert.Equal(1, cModel.TotalEvents);
Assert.NotEqual(aModel.TotalEvents, cModel.TotalEvents);
}
// --------------------------- G32 : Telemetry Client + Environment ----------
[Fact]
public async Task G32_Telemetry_ClientAndEnvironmentAreCombinable()
{
var repo = new FakeDiagnosticEventRepository();
DateTimeOffset now = DateTimeOffset.UtcNow;
await repo.InsertBatchAsync(new[]
{
Ev(1, client: ClientB, server: "API-B-01", environment: "Production", ts: now.AddMinutes(-30)),
Ev(2, client: ClientB, server: "API-B-01", environment: "Production", ts: now.AddMinutes(-20)),
Ev(3, client: ClientB, server: "API-B-STG-01", environment: "Staging", ts: now.AddMinutes(-10))
});
var prodModel = new TelemetryModel(repo) { ClientCode = ClientB, Environment = "Production" };
await prodModel.OnGetAsync();
var stagingModel = new TelemetryModel(repo) { ClientCode = ClientB, Environment = "Staging" };
await stagingModel.OnGetAsync();
Assert.Equal(2, prodModel.TotalEvents);
Assert.Equal(1, stagingModel.TotalEvents);
}
}