using Omika.Core.Compatibility; using Omika.Core.Domain.Entities.Interventions; using Omika.Data.Mapping.Common; using OmikaModels = Omika.Data.HfSql.Models.Omika; using OmikaMappers = Omika.Data.Mapping.Omika; namespace Omika.Tests.Mapping; /// /// Tests unitaires du mapper Omika → canonique des interventions. /// Aucune base réelle, aucun HFSQL : uniquement des objets en mémoire. /// /// Le mapper ne renseigne pas SourceReference sur l'entité canonique /// (la référence source est construite en interne mais non affectée) ; ces /// tests se concentrent donc sur les propriétés canoniques réellement mappées. /// public class OmikaInterventionMapperTests { // ---- 1. Intervention Omika minimale ------------------------------------------- [Fact] public void InterventionOmika_Minimal_MapsWithoutExceptionAndKeepsNoFabricatedValue() { var row = new OmikaModels.InterventionRow { INPKID = 1 }; CanonicalMappingResult result = OmikaMappers.OmikaInterventionMapper.Map(row); Assert.Equal(DatabaseSchemaVersion.Omika, result.SourceVersion); Assert.NotNull(result.Value); Assert.Null(result.Value.Titre); Assert.Null(result.Value.Reference); Assert.Null(result.Value.Type); Assert.Null(result.Value.Etat); Assert.False(result.Value.EstArchive); Assert.Empty(result.Warnings); } // ---- 2. Intervention Omika complète ------------------------------------------- [Fact] public void InterventionOmika_Full_MapsAllCanonicalProperties() { var row = new OmikaModels.InterventionRow { INPKID = 42, INTitre = "Réparation chaudière", INRefInterne = "INT-042", INDescription = "Remplacement de la chaudière", INCleType = "TYP-MAINT", INCleEtat = "STA-ENCOURS", INDateIntervention = new DateOnly(2026, 3, 15), Archive = true, INIDImport = "IMP-O-42", GUID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }; var referenceRows = new Dictionary { ["TYP-MAINT"] = new() { PAPKID = 2, PACLE = "TYP-MAINT", PALibelle = "Maintenance" }, ["STA-ENCOURS"] = new() { PAPKID = 3, PACLE = "STA-ENCOURS", PALibelle = "En cours" } }; CanonicalMappingResult result = OmikaMappers.OmikaInterventionMapper.Map(row, referenceRows); Intervention intervention = result.Value; Assert.Equal("Réparation chaudière", intervention.Titre); Assert.Equal("INT-042", intervention.Reference); Assert.Equal("Remplacement de la chaudière", intervention.Description); Assert.Equal("Maintenance", intervention.Type!.Label); Assert.Equal("TYP-MAINT", intervention.Type!.Key); Assert.Equal("En cours", intervention.Etat!.Label); Assert.Equal("STA-ENCOURS", intervention.Etat!.Key); Assert.Equal(new DateTime(2026, 3, 15), intervention.DatePrevueDebut); Assert.True(intervention.EstArchive); } // ---- 3. GUID ------------------------------------------------------------------ [Fact] public void InterventionOmika_InvalidGuid_ProducesWarning() { var row = new OmikaModels.InterventionRow { INPKID = 42, GUID = "not-a-valid-guid" }; CanonicalMappingResult result = OmikaMappers.OmikaInterventionMapper.Map(row); Assert.Contains(result.Warnings, w => w.Contains(MappingWarningCodes.InvalidPhysicalGuid, StringComparison.Ordinal)); } [Fact] public void InterventionOmika_EmptyGuid_NoWarning() { var row = new OmikaModels.InterventionRow { INPKID = 42, GUID = "" }; CanonicalMappingResult result = OmikaMappers.OmikaInterventionMapper.Map(row); Assert.Empty(result.Warnings); } // ---- 4. Type et état (clés métier) -------------------------------------------- [Fact] public void InterventionOmika_Type_IsResolvedFromCleType() { var row = new OmikaModels.InterventionRow { INPKID = 5, INCleType = "TYP-URG" }; var referenceRows = new Dictionary { ["TYP-URG"] = new() { PAPKID = 7, PACLE = "TYP-URG", PALibelle = "Urgence" } }; CanonicalMappingResult result = OmikaMappers.OmikaInterventionMapper.Map(row, referenceRows); Assert.NotNull(result.Value.Type); Assert.Equal("Urgence", result.Value.Type!.Label); Assert.Equal("TYP-URG", result.Value.Type!.Key); Assert.Empty(result.Warnings); } [Fact] public void InterventionOmika_UnresolvedType_KeepsKeyAndWarns() { var row = new OmikaModels.InterventionRow { INPKID = 6, INCleType = "TYP-INCONNU" }; CanonicalMappingResult result = OmikaMappers.OmikaInterventionMapper.Map(row); Assert.NotNull(result.Value.Type); Assert.Equal("TYP-INCONNU", result.Value.Type!.Key); Assert.Equal(string.Empty, result.Value.Type!.Label); Assert.Contains(result.Warnings, w => w.Contains("IN_CleType", StringComparison.Ordinal)); } [Fact] public void InterventionOmika_NoTypeKey_ProducesNoReference() { var row = new OmikaModels.InterventionRow { INPKID = 8 }; CanonicalMappingResult result = OmikaMappers.OmikaInterventionMapper.Map(row); Assert.Null(result.Value.Type); Assert.Empty(result.Warnings); } [Fact] public void InterventionOmika_UnresolvedEtat_KeepsKeyAndWarns() { var row = new OmikaModels.InterventionRow { INPKID = 9, INCleEtat = "STA-INCONNU" }; CanonicalMappingResult result = OmikaMappers.OmikaInterventionMapper.Map(row); Assert.NotNull(result.Value.Etat); Assert.Equal("STA-INCONNU", result.Value.Etat!.Key); Assert.Contains(result.Warnings, w => w.Contains("IN_CleEtat", StringComparison.Ordinal)); } // ---- 5. Champs ignorés -------------------------------------------------------- [Fact] public void InterventionOmika_FieldsWithoutCanonicalEquivalent_AreReportedAsIgnored() { var row = new OmikaModels.InterventionRow { INPKID = 15, INIDClient = 1, INIDLieu = 2, INIDDossier = 3, INIDSuiviPar = 4, INIDDemandeur = 5, INIDSociete = 6, INAttestationTVAPresente = true, INNoteInterne = "note", INRefExterne = "EXT-1", INDureePrevue = 2.5f, INNumDevis = "DEV-1", INDateDevis = new DateOnly(2026, 1, 1), INNumFacture = "FAC-1", INDateFacture = new DateOnly(2026, 2, 1), INNumIntervention = "N-1", ModifDate = new DateTime(2024, 1, 1), CreationDate = new DateTime(2023, 1, 1), DateArchivage = new DateTime(2024, 6, 1) }; CanonicalMappingResult result = OmikaMappers.OmikaInterventionMapper.Map(row); Assert.Contains(result.IgnoredFields, f => f == "IN_IDClient"); Assert.Contains(result.IgnoredFields, f => f == "IN_IDLieu"); Assert.Contains(result.IgnoredFields, f => f == "IN_IDDossier"); Assert.Contains(result.IgnoredFields, f => f == "IN_IDSuiviPar"); Assert.Contains(result.IgnoredFields, f => f == "IN_IDDemandeur"); Assert.Contains(result.IgnoredFields, f => f == "IN_IDSociete"); Assert.Contains(result.IgnoredFields, f => f == "IN_AttestationTVAPresente"); Assert.Contains(result.IgnoredFields, f => f == "IN_NoteInterne"); Assert.Contains(result.IgnoredFields, f => f == "IN_DureePrevue"); Assert.Contains(result.IgnoredFields, f => f == "IN_DateDevis"); Assert.Contains(result.IgnoredFields, f => f == "IN_DateFacture"); Assert.Equal(new DateTime(2024, 1, 1), result.Value.DateModification); Assert.Contains(result.IgnoredFields, f => f == "CreationDate"); Assert.Contains(result.IgnoredFields, f => f == "DateArchivage"); // Champs désormais dotés d'un équivalent canonique : ils ne sont plus // ignorés, ils sont mappés dans l'entité Intervention. Assert.Equal("EXT-1", result.Value.ReferenceExterne); Assert.Equal("DEV-1", result.Value.NumeroDevis); Assert.Equal("N-1", result.Value.NumeroIntervention); Assert.Equal("FAC-1", result.Value.NumeroFacture); } // ---- 6. Aucune exception sur les chaînes vides -------------------------------- [Fact] public void InterventionOmika_EmptyOrNullStrings_NeverThrow() { var row = new OmikaModels.InterventionRow { INTitre = "", INRefInterne = " ", INDescription = "", INNoteInterne = " ", INRefExterne = "", INIDImport = " " }; CanonicalMappingResult result = OmikaMappers.OmikaInterventionMapper.Map(row); Assert.NotNull(result.Value); Assert.Null(result.Value.Titre); Assert.Null(result.Value.Reference); Assert.Null(result.Value.Description); Assert.Empty(result.Warnings); } [Fact] public void InterventionOmika_DefaultRow_NeverThrows() { var row = new OmikaModels.InterventionRow(); CanonicalMappingResult result = OmikaMappers.OmikaInterventionMapper.Map(row); Assert.NotNull(result.Value); Assert.False(result.Value.EstArchive); } // ---- 7. Absence de noms physiques HFSQL dans le domaine canonique ------------- [Fact] public void InterventionOmika_CanonicalDomain_DoesNotExposeHfSqlNames() { var row = new OmikaModels.InterventionRow { INPKID = 40, INTitre = "Titre", INRefInterne = "INT-040", INDescription = "desc", INCleType = "TYP-MAINT", INCleEtat = "STA-ENCOURS", INDateIntervention = new DateOnly(2026, 1, 1), Archive = false, INIDImport = "IMP-40", INIDClient = 1, INIDLieu = 2, INIDDossier = 3, INIDSuiviPar = 4, INIDDemandeur = 5, INIDSociete = 6, INAttestationTVAPresente = true, INNoteInterne = "note", INRefExterne = "EXT", INDureePrevue = 1f, INNumDevis = "DEV", INNumFacture = "FAC", INNumIntervention = "N", GUID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }; var referenceRows = new Dictionary { ["TYP-MAINT"] = new() { PAPKID = 2, PACLE = "TYP-MAINT", PALibelle = "Maintenance" }, ["STA-ENCOURS"] = new() { PAPKID = 3, PACLE = "STA-ENCOURS", PALibelle = "En cours" } }; CanonicalMappingResult result = OmikaMappers.OmikaInterventionMapper.Map(row, referenceRows); Intervention intervention = result.Value; string[] forbidden = [ "IN_", "PA_", "PKID", "HfSql", "HFSQL" ]; AssertHasNoPhysicalNames(intervention, forbidden); } // ---- Helper ------------------------------------------------------------------- private static void AssertHasNoPhysicalNames(object value, string[] forbidden) { foreach (var property in value.GetType().GetProperties()) { Assert.DoesNotContain(forbidden, f => property.Name.Contains(f, StringComparison.Ordinal)); } } }