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,223 @@
using Omika.Diagnostics.Referential;
using Omika.Diagnostics.Security;
using Omika.Diagnostics.Tests.Fakes;
using Omika.Diagnostics.Web.Pages;
namespace Omika.Diagnostics.Tests;
/// <summary>
/// T24-A Partie B — PageModels du référentiel (/Referentiel + /Referentiel/Admin) et
/// libellés des dropdowns Diagnostics. Les handlers n'accèdent pas à HttpContext (aucun
/// harnais Web requis) ; l'autorisation est portée par la convention Program.cs.
/// </summary>
public class DiagnosticReferencePageModelTests
{
private static DiagnosticReferenceRecord Ref(
long id,
string type,
string code,
string label,
bool enabled = true) => new()
{
Id = id,
ReferenceType = type,
Code = code,
Label = label,
Enabled = enabled,
CreatedAtUtc = DateTimeOffset.UtcNow,
UpdatedAtUtc = DateTimeOffset.UtcNow
};
private static ReferentielAdminModel NewAdminModel(
FakeDiagnosticReferenceRepository references) =>
new(new FakeDiagnosticEventRepository(), references);
// ------------------------------------------- création admin ----
[Fact]
public async Task AdminCreate_StoresNormalizedEntry()
{
var references = new FakeDiagnosticReferenceRepository();
var model = NewAdminModel(references);
model.NewType = DiagnosticReferenceTypes.ScreenCode;
model.NewCode = " facture_detail ";
model.NewLabel = " Détail de facture ";
model.NewDescription = "Fiche détaillée d'une facture.";
await model.OnPostCreateAsync();
DiagnosticReferenceRecord? created = await references.GetByTypeAndCodeAsync(
DiagnosticReferenceTypes.ScreenCode, "FACTURE_DETAIL");
Assert.NotNull(created);
Assert.Equal("FACTURE_DETAIL", created!.Code);
Assert.Equal("Détail de facture", created.Label);
Assert.Contains("FACTURE_DETAIL", model.SuccessMessage);
}
[Fact]
public async Task AdminCreate_Duplicate_ShowsError()
{
var references = new FakeDiagnosticReferenceRepository();
references.Seed(Ref(1, DiagnosticReferenceTypes.ScreenCode, "AGENDA", "Agenda"));
var model = NewAdminModel(references);
model.NewType = DiagnosticReferenceTypes.ScreenCode;
model.NewCode = "agenda";
model.NewLabel = "Agenda bis";
await model.OnPostCreateAsync();
Assert.Contains("existe déjà", model.ErrorMessage);
Assert.Single(await references.ListAsync());
}
[Fact]
public async Task AdminCreate_EmptyLabel_ShowsError()
{
var references = new FakeDiagnosticReferenceRepository();
var model = NewAdminModel(references);
model.NewType = DiagnosticReferenceTypes.ScreenCode;
model.NewCode = "X";
model.NewLabel = " ";
await model.OnPostCreateAsync();
Assert.Contains("libellé", model.ErrorMessage, StringComparison.OrdinalIgnoreCase);
Assert.Empty(await references.ListAsync());
}
// ------------------------------------------- modification / archivage ----
[Fact]
public async Task AdminUpdate_ChangesLabelDescriptionAndState()
{
var references = new FakeDiagnosticReferenceRepository();
references.Seed(Ref(1, DiagnosticReferenceTypes.ScreenCode, "AGENDA", "Agenda"));
var model = NewAdminModel(references);
await model.OnPostUpdateAsync(1, "Nouvel agenda", "Description", enabled: false, sortOrder: 5);
DiagnosticReferenceRecord? record = await references.GetByIdAsync(1);
Assert.Equal("Nouvel agenda", record!.Label);
Assert.Equal("Description", record.Description);
Assert.False(record.Enabled);
Assert.Equal(5, record.SortOrder);
}
[Fact]
public async Task AdminArchive_DisablesWithoutDelete()
{
var references = new FakeDiagnosticReferenceRepository();
references.Seed(Ref(1, DiagnosticReferenceTypes.ScreenCode, "AGENDA", "Agenda"));
var model = NewAdminModel(references);
await model.OnPostArchiveAsync(1);
Assert.False((await references.GetByIdAsync(1))!.Enabled);
Assert.Single(await references.ListAsync()); // jamais de DELETE.
Assert.Contains("archivée", model.SuccessMessage);
}
[Fact]
public async Task AdminEnable_ReactivatesArchived()
{
var references = new FakeDiagnosticReferenceRepository();
references.Seed(Ref(1, DiagnosticReferenceTypes.ScreenCode, "AGENDA", "Agenda", enabled: false));
var model = NewAdminModel(references);
await model.OnPostEnableAsync(1);
Assert.True((await references.GetByIdAsync(1))!.Enabled);
}
// ------------------------------------------- ajout rapide non référencé ----
[Fact]
public async Task AdminAddUnreferenced_CreatesReference()
{
var references = new FakeDiagnosticReferenceRepository();
DateTimeOffset last = DateTimeOffset.UtcNow.AddDays(-1);
references.SeedObserved(DiagnosticReferenceTypes.ScreenCode, "FACTURE_DETAIL", 128, last);
var model = NewAdminModel(references);
await model.OnGetAsync();
DiagnosticUnreferencedCode unreferenced = Assert.Single(model.Unreferenced);
Assert.Equal("FACTURE_DETAIL", unreferenced.Code);
await model.OnPostAddUnreferencedAsync(
DiagnosticReferenceTypes.ScreenCode, "FACTURE_DETAIL", "Détail de facture", "Fiche détaillée.");
Assert.NotNull(await references.GetByTypeAndCodeAsync(DiagnosticReferenceTypes.ScreenCode, "FACTURE_DETAIL"));
// Après ajout, le code n'est plus « non référencé ».
await model.OnGetAsync();
Assert.Empty(model.Unreferenced);
}
// ------------------------------------------- consultation /Referentiel ----
[Fact]
public async Task ReferentielModel_LoadsScreenAndFeatureReferences()
{
var references = new FakeDiagnosticReferenceRepository();
references.Seed(
Ref(1, DiagnosticReferenceTypes.ScreenCode, "AGENDA", "Agenda"),
Ref(2, DiagnosticReferenceTypes.FeatureCode, "EMAIL_SEND", "Envoi de courriel"));
var model = new ReferentielModel(new FakeDiagnosticEventRepository(), references);
await model.OnGetAsync();
Assert.True(model.PostgreSqlAvailable);
DiagnosticReferenceRecord screen = Assert.Single(model.ScreenReferences);
Assert.Equal("AGENDA", screen.Code);
DiagnosticReferenceRecord feature = Assert.Single(model.FeatureReferences);
Assert.Equal("EMAIL_SEND", feature.Code);
}
// ------------------------------------------- dropdowns Diagnostics (§33/§37) ----
[Fact]
public async Task DiagnosticsDropdown_ReferencedCode_UsesDbLabel()
{
var references = new FakeDiagnosticReferenceRepository();
references.Seed(Ref(1, DiagnosticReferenceTypes.ScreenCode, "FACTURE_DETAIL", "Détail de facture"));
var model = new DiagnosticsModel(new FakeDiagnosticEventRepository(), references);
await model.OnGetAsync();
// Référence DB → « Libellé (CODE) ».
Assert.Equal("Détail de facture (FACTURE_DETAIL)", model.ScreenCodeOptionLabel("FACTURE_DETAIL"));
}
[Fact]
public async Task DiagnosticsDropdown_UnreferencedCode_ShowsNotReferenced()
{
var references = new FakeDiagnosticReferenceRepository();
var model = new DiagnosticsModel(new FakeDiagnosticEventRepository(), references);
await model.OnGetAsync();
// Aucune référence → « CODE (non référencé) ». La valeur envoyée reste le CODE.
Assert.Equal("FACTURE_DETAIL (non référencé)", model.ScreenCodeOptionLabel("FACTURE_DETAIL"));
}
[Fact]
public async Task DiagnosticsDropdown_CatalogCodeWithoutDbEntry_FallsBackToCatalog()
{
var references = new FakeDiagnosticReferenceRepository();
var model = new DiagnosticsModel(new FakeDiagnosticEventRepository(), references);
await model.OnGetAsync();
// Code du catalogue applicatif mais sans entrée DB → repli catalogue (résilience).
Assert.Equal("AGENDA — Agenda", model.ScreenCodeOptionLabel("AGENDA"));
}
// ------------------------------------------- rôles (§40/§46) ----
[Fact]
public void Roles_SupportCanConsultButNotAdmin()
{
Assert.True(DiagnosticAuthorization.IsSupport([DiagnosticRoles.Support]));
Assert.False(DiagnosticAuthorization.IsAdmin([DiagnosticRoles.Support]));
}
[Fact]
public void Roles_AdminCanConsultAndAdmin()
{
Assert.True(DiagnosticAuthorization.IsSupport([DiagnosticRoles.Admin]));
Assert.True(DiagnosticAuthorization.IsAdmin([DiagnosticRoles.Admin]));
}
}