using Omika.Core.Compatibility; using Omika.Core.Domain.Entities.Interventions; using Omika.Core.Domain.ValueObjects; using Omika.Data.Mapping.Common; using BatikModels = Omika.Data.HfSql.Models.Batik; using BatikMappers = Omika.Data.Mapping.Batik; namespace Omika.Tests.Mapping; /// /// Tests unitaires du mapper Batik → canonique des interventions. /// Aucune base réelle, aucun HFSQL : uniquement des objets en mémoire. /// public class BatikInterventionMapperTests { // ---- 1. Intervention Batik minimale ------------------------------------------- [Fact] public void InterventionBatik_Minimal_MapsWithoutExceptionAndKeepsNoFabricatedValue() { var row = new BatikModels.InterventionRow { INPKID = 1 }; CanonicalMappingResult result = BatikMappers.BatikInterventionMapper.Map(row); Assert.Equal(DatabaseSchemaVersion.Batik, 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 Batik complète ------------------------------------------- [Fact] public void InterventionBatik_Full_MapsAllCanonicalProperties() { var row = new BatikModels.InterventionRow { INPKID = 42, INTitre = "Réparation chaudière", INRefInterne = "INT-042", INDescription = "Remplacement de la chaudière", INIDTypeIntervention = 2, INIDEtatIntervention = 3, INDateIntervention = new DateOnly(2026, 3, 15), Archive = true, INIDImport = "IMP-B-42" }; var referenceRows = new Dictionary { [2] = new() { PAPKID = 2, PACode = "T", PAValeur = "1", PALibelle = "Maintenance" }, [3] = new() { PAPKID = 3, PACode = "S", PAValeur = "1", PALibelle = "En cours" } }; CanonicalMappingResult result = BatikMappers.BatikInterventionMapper.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(2, intervention.Type!.LegacyId); Assert.Equal("En cours", intervention.Etat!.Label); Assert.Equal(3, intervention.Etat!.LegacyId); Assert.Equal(new DateTime(2026, 3, 15), intervention.DatePrevueDebut); Assert.True(intervention.EstArchive); } // ---- 4. Type et état ---------------------------------------------------------- [Fact] public void InterventionBatik_Type_IsResolvedFromLegacyId() { var row = new BatikModels.InterventionRow { INPKID = 5, INIDTypeIntervention = 7 }; var referenceRows = new Dictionary { [7] = new() { PAPKID = 7, PACode = "T", PAValeur = "2", PALibelle = "Urgence" } }; CanonicalMappingResult result = BatikMappers.BatikInterventionMapper.Map(row, referenceRows); Assert.NotNull(result.Value.Type); Assert.Equal("Urgence", result.Value.Type!.Label); Assert.Equal(7, result.Value.Type!.LegacyId); Assert.Empty(result.Warnings); } [Fact] public void InterventionBatik_UnresolvedType_KeepsLegacyIdAndWarns() { var row = new BatikModels.InterventionRow { INPKID = 6, INIDTypeIntervention = 77 }; CanonicalMappingResult result = BatikMappers.BatikInterventionMapper.Map(row); Assert.NotNull(result.Value.Type); Assert.Equal(77, result.Value.Type!.LegacyId); Assert.Equal(string.Empty, result.Value.Type!.Label); Assert.Contains(result.Warnings, w => w.Contains("IN_IDTypeIntervention", StringComparison.Ordinal) && w.Contains(MappingWarningCodes.UnresolvedReference, StringComparison.Ordinal)); } [Fact] public void InterventionBatik_ZeroType_ProducesNoReference() { var row = new BatikModels.InterventionRow { INPKID = 8, INIDTypeIntervention = 0 }; CanonicalMappingResult result = BatikMappers.BatikInterventionMapper.Map(row); Assert.Null(result.Value.Type); Assert.Empty(result.Warnings); } [Fact] public void InterventionBatik_UnresolvedEtat_KeepsLegacyIdAndWarns() { var row = new BatikModels.InterventionRow { INPKID = 9, INIDEtatIntervention = 88 }; CanonicalMappingResult result = BatikMappers.BatikInterventionMapper.Map(row); Assert.NotNull(result.Value.Etat); Assert.Equal(88, result.Value.Etat!.LegacyId); Assert.Contains(result.Warnings, w => w.Contains("IN_IDEtatIntervention", StringComparison.Ordinal) && w.Contains(MappingWarningCodes.UnresolvedReference, StringComparison.Ordinal)); } // ---- 5. Champs ignorés -------------------------------------------------------- [Fact] public void InterventionBatik_FieldsWithoutCanonicalEquivalent_AreReportedAsIgnored() { var row = new BatikModels.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 = BatikMappers.BatikInterventionMapper.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 InterventionBatik_EmptyOrNullStrings_NeverThrow() { var row = new BatikModels.InterventionRow { INTitre = "", INRefInterne = " ", INDescription = "", INNoteInterne = " ", INRefExterne = "", INIDImport = " " }; CanonicalMappingResult result = BatikMappers.BatikInterventionMapper.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 InterventionBatik_DefaultRow_NeverThrows() { var row = new BatikModels.InterventionRow(); CanonicalMappingResult result = BatikMappers.BatikInterventionMapper.Map(row); Assert.NotNull(result.Value); Assert.False(result.Value.EstArchive); } // ---- 7. Absence de noms physiques HFSQL dans le domaine canonique ------------- [Fact] public void InterventionBatik_CanonicalDomain_DoesNotExposeHfSqlNames() { var row = new BatikModels.InterventionRow { INPKID = 40, INTitre = "Titre", INRefInterne = "INT-040", INDescription = "desc", INIDTypeIntervention = 2, INIDEtatIntervention = 3, 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" }; var referenceRows = new Dictionary { [2] = new() { PAPKID = 2, PACode = "T", PAValeur = "1", PALibelle = "Maintenance" }, [3] = new() { PAPKID = 3, PACode = "S", PAValeur = "1", PALibelle = "En cours" } }; CanonicalMappingResult result = BatikMappers.BatikInterventionMapper.Map(row, referenceRows); Intervention intervention = result.Value; string[] forbidden = [ "IN_", "PA_", "PKID", "HfSql", "HFSQL", "GUID" ]; AssertHasNoPhysicalNames(intervention, forbidden); if (intervention.SourceReference is not null) { AssertHasNoPhysicalNames(intervention.SourceReference, 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)); } } }