using Omika.Diagnostics.Referential; using Omika.Diagnostics.Security; using Omika.Diagnostics.Tests.Fakes; using Omika.Diagnostics.Web.Pages; namespace Omika.Diagnostics.Tests; /// /// 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. /// 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])); } }