222 lines
9.6 KiB
C#
222 lines
9.6 KiB
C#
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Omika.Diagnostics.Models;
|
|
using Omika.Diagnostics.Tests.Fakes;
|
|
using Omika.Diagnostics.Web.Pages;
|
|
|
|
namespace Omika.Diagnostics.Tests;
|
|
|
|
/// <summary>
|
|
/// Tâche 13-F — Tests des PageModels (Diagnostics / Erreurs / Traces) : transmission
|
|
/// des filtres de contexte (F11/F17), chargement des options dynamiques (F12),
|
|
/// conservation de la sélection (F13), pagination (F14), détail (F15), libellé NULL
|
|
/// historique (F16) et non-confusion du serveur producteur (§38). Les PageModels
|
|
/// n'accèdent pas à HttpContext pour ces chemins (aucun harnais Web requis).
|
|
/// </summary>
|
|
public class DiagnosticPageModelTests
|
|
{
|
|
private static readonly DateTimeOffset From = new(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
|
|
|
private static DiagnosticEventRecord Ev(
|
|
long id,
|
|
string eventCode = "TEST",
|
|
string? clientCode = null,
|
|
string? serverInstance = null,
|
|
string? environment = null,
|
|
DateTimeOffset? ts = null,
|
|
string level = "Info",
|
|
Guid? operationId = null,
|
|
string? component = null)
|
|
{
|
|
DiagnosticEventRecord record = DiagnosticTestData.Event(Guid.NewGuid(), eventCode, ts ?? From, component: component, level: level);
|
|
record.Id = id;
|
|
record.ClientCode = clientCode;
|
|
record.ServerInstance = serverInstance;
|
|
record.Environment = environment;
|
|
record.OperationId = operationId;
|
|
return record;
|
|
}
|
|
|
|
// --------------------------- F11 : DiagnosticsModel transmet les filtres -----
|
|
[Fact]
|
|
public async Task F11_DiagnosticsModel_PassesDeploymentFiltersToQuery()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await repo.InsertBatchAsync(new[]
|
|
{
|
|
Ev(1, clientCode: "CLIENT-A", serverInstance: "API-SRV-01", environment: "Production"),
|
|
Ev(2, clientCode: "CLIENT-B", serverInstance: "WKR-SRV-01", environment: "Staging")
|
|
});
|
|
|
|
var model = new DiagnosticsModel(repo)
|
|
{
|
|
ClientCode = "CLIENT-A",
|
|
ServerInstance = "API-SRV-01",
|
|
Environment = "Production"
|
|
};
|
|
|
|
await model.OnGetAsync();
|
|
|
|
DiagnosticEventRecord record = Assert.Single(model.Items);
|
|
Assert.Equal("CLIENT-A", record.ClientCode);
|
|
Assert.Equal("API-SRV-01", record.ServerInstance);
|
|
Assert.Equal("Production", record.Environment);
|
|
}
|
|
|
|
// --------------------------- F12 : options dynamiques chargées ---------------
|
|
[Fact]
|
|
public async Task F12_DiagnosticsModel_LoadsDeploymentFilterOptions()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
// Les options de filtre sont bornées par une fenêtre glissante (60 jours) :
|
|
// les événements doivent être RÉCENTS pour être observés (cf. OptionsWindowDays).
|
|
await repo.InsertBatchAsync(new[]
|
|
{
|
|
Ev(1, clientCode: "CLIENT-A", serverInstance: "API-SRV-01", environment: "Production", ts: DateTimeOffset.UtcNow.AddMinutes(-5)),
|
|
Ev(2, clientCode: "CLIENT-B", serverInstance: "WKR-SRV-01", environment: "Staging", ts: DateTimeOffset.UtcNow.AddMinutes(-4))
|
|
});
|
|
|
|
var model = new DiagnosticsModel(repo);
|
|
await model.OnGetAsync();
|
|
|
|
Assert.Contains("CLIENT-A", model.ClientOptions);
|
|
Assert.Contains("CLIENT-B", model.ClientOptions);
|
|
Assert.Contains("API-SRV-01", model.ServerOptions);
|
|
Assert.Contains("WKR-SRV-01", model.ServerOptions);
|
|
Assert.Contains("Production", model.EnvironmentOptions);
|
|
Assert.Contains("Staging", model.EnvironmentOptions);
|
|
}
|
|
|
|
// --------------------------- F13 : sélection conservée -----------------------
|
|
[Fact]
|
|
public async Task F13_DiagnosticsModel_SelectedValueRemainsSelected()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
await repo.InsertBatchAsync(new[] { Ev(1, clientCode: "CLIENT-A", serverInstance: "SRV-1", environment: "Production") });
|
|
|
|
var model = new DiagnosticsModel(repo) { ClientCode = "CLIENT-Z", ServerInstance = "SRV-Z", Environment = "Custom" };
|
|
await model.OnGetAsync();
|
|
|
|
// La valeur active reste la valeur du modèle (jamais réécrite)…
|
|
Assert.Equal("CLIENT-Z", model.ClientCode);
|
|
Assert.Equal("SRV-Z", model.ServerInstance);
|
|
Assert.Equal("Custom", model.Environment);
|
|
|
|
// …et elle est injectée en tête des options (pagination/conservation §24).
|
|
Assert.Equal("CLIENT-Z", model.ClientOptions[0]);
|
|
Assert.Equal("SRV-Z", model.ServerOptions[0]);
|
|
Assert.Equal("Custom", model.EnvironmentOptions[0]);
|
|
Assert.True(DiagnosticsModel.IsSelected("CLIENT-Z", model.ClientCode));
|
|
}
|
|
|
|
// --------------------------- F14 : pagination conserve les filtres ----------
|
|
[Fact]
|
|
public void F14_DiagnosticsModel_RoutePreservesDeploymentFilters()
|
|
{
|
|
var model = new DiagnosticsModel(new FakeDiagnosticEventRepository())
|
|
{
|
|
ClientCode = "CLIENT-A",
|
|
ServerInstance = "WKR-SRV-01",
|
|
Environment = "Production"
|
|
};
|
|
|
|
Dictionary<string, string> route = model.Route(2);
|
|
|
|
Assert.Equal("2", route["pageNumber"]);
|
|
Assert.Equal("CLIENT-A", route["clientCode"]);
|
|
Assert.Equal("WKR-SRV-01", route["serverInstance"]);
|
|
Assert.Equal("Production", route["environment"]);
|
|
}
|
|
|
|
// --------------------------- F15 : détail affiche les trois valeurs ---------
|
|
[Fact]
|
|
public async Task F15_DiagnosticsDetail_LoadsDeploymentContext()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
DiagnosticEventRecord record = Ev(42, clientCode: "CLIENT-A", serverInstance: "WKR-SRV-01", 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("CLIENT-A", model.Event!.ClientCode);
|
|
Assert.Equal("WKR-SRV-01", model.Event.ServerInstance);
|
|
Assert.Equal("Production", model.Event.Environment);
|
|
}
|
|
|
|
// --------------------------- F16 : NULL historique → « Non renseigné » -------
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
public void F16_NullContext_RendersNonRenseigne(string? value)
|
|
{
|
|
Assert.Equal("Non renseigné", UiDeploymentContext.Value(value));
|
|
}
|
|
|
|
[Fact]
|
|
public void F16_NonNullContext_IsPreserved()
|
|
{
|
|
Assert.Equal("CLIENT-A", UiDeploymentContext.Value("CLIENT-A"));
|
|
}
|
|
|
|
// --------------------------- F17/F18 : ErreursModel transmet + pré-agrégation -
|
|
[Fact]
|
|
public async Task F17_F18_ErreursModel_AppliesFiltersBeforeAggregation()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
DateTimeOffset now = DateTimeOffset.UtcNow;
|
|
await repo.InsertBatchAsync(new[]
|
|
{
|
|
Ev(1, "ERR", "CLIENT-A", "SRV-1", "Production", now.AddMinutes(-5), level: "Error", component: "Omika.Api"),
|
|
Ev(2, "ERR", "CLIENT-A", "SRV-1", "Production", now.AddMinutes(-4), level: "Error", component: "Omika.Api"),
|
|
Ev(3, "ERR", "CLIENT-B", "SRV-1", "Staging", now.AddMinutes(-3), level: "Error", component: "Omika.Api"),
|
|
Ev(4, "ERR", "CLIENT-A", "SRV-1", "Staging", now.AddMinutes(-2), level: "Error", component: "Omika.Api")
|
|
});
|
|
|
|
// Sans filtre : un seul groupe logique, 4 occurrences.
|
|
var allModel = new ErreursModel(repo, new FakeDiagnosticTaskRepository(), NullLogger<ErreursModel>.Instance);
|
|
await allModel.OnGetAsync();
|
|
Omika.Diagnostics.Queries.DiagnosticErrorGroup allGroup = Assert.Single(allModel.Groups);
|
|
Assert.Equal(4, allGroup.OccurrenceCount);
|
|
|
|
// Avec filtre Client + Environnement : le regroupement est recalculé sur le périmètre.
|
|
var filteredModel = new ErreursModel(repo, new FakeDiagnosticTaskRepository(), NullLogger<ErreursModel>.Instance)
|
|
{
|
|
ClientCode = "CLIENT-A",
|
|
Environment = "Production"
|
|
};
|
|
await filteredModel.OnGetAsync();
|
|
Omika.Diagnostics.Queries.DiagnosticErrorGroup filteredGroup = Assert.Single(filteredModel.Groups);
|
|
Assert.Equal(2, filteredGroup.OccurrenceCount);
|
|
}
|
|
|
|
// --------------------------- §38 : Traces sans confusion serveur -------------
|
|
[Fact]
|
|
public async Task Section38_Traces_KeepProducerServerInstanceDistinct()
|
|
{
|
|
var repo = new FakeDiagnosticEventRepository();
|
|
Guid operationId = Guid.NewGuid();
|
|
await repo.InsertBatchAsync(new[]
|
|
{
|
|
Ev(1, "OMK-API-HTTP-001", "CLIENT-A", "API-SRV-01", "Production", operationId: operationId, component: "Omika.Api"),
|
|
Ev(2, "OMK-WKR-SYNC-001", "CLIENT-A", "WKR-SRV-01", "Production", operationId: operationId, component: "Omika.Worker")
|
|
});
|
|
|
|
var model = new TracesModel(repo) { OperationId = operationId.ToString() };
|
|
await model.OnGetAsync();
|
|
|
|
Assert.Equal(2, model.Timeline.Count);
|
|
|
|
DiagnosticEventRecord api = model.Timeline.Single(e => e.Component == "Omika.Api");
|
|
DiagnosticEventRecord worker = model.Timeline.Single(e => e.Component == "Omika.Worker");
|
|
|
|
// Le serveur affiché est le serveur PRODUCTEUR de chaque événement, jamais mélangé.
|
|
Assert.Equal("API-SRV-01", api.ServerInstance);
|
|
Assert.Equal("WKR-SRV-01", worker.ServerInstance);
|
|
Assert.Equal("CLIENT-A", worker.ClientCode);
|
|
Assert.Equal("Production", worker.Environment);
|
|
}
|
|
}
|