using Omika.Core.Compatibility; using Omika.Core.Domain.Entities.Clients; 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 contacts (table Personne). /// Aucune base réelle, aucun HFSQL : uniquement des objets en mémoire. /// public class BatikContactMapperTests { // ---- 1. Contact Batik minimal ------------------------------------------------- [Fact] public void ContactBatik_Minimal_MapsWithoutExceptionAndKeepsNoFabricatedValue() { var row = new BatikModels.PersonneRow { PEPKID = 1 }; CanonicalMappingResult result = BatikMappers.BatikContactMapper.Map(row); Assert.Equal(DatabaseSchemaVersion.Batik, result.SourceVersion); Assert.NotNull(result.Value); Assert.Null(result.Value.Nom); Assert.Null(result.Value.Prenom); Assert.Null(result.Value.Civilite); Assert.Empty(result.Value.Roles); Assert.Null(result.Value.Coordonnees); Assert.False(result.Value.EstArchive); Assert.NotNull(result.Value.SourceReference); Assert.Equal(1, result.Value.SourceReference!.NumericId); Assert.Equal(DatabaseSchemaVersion.Batik, result.Value.SourceReference!.Source); Assert.Null(result.Value.SourceReference!.EntityGuid); Assert.Empty(result.Warnings); } // ---- 2. Contact Batik complet ------------------------------------------------- [Fact] public void ContactBatik_Full_MapsAllCanonicalProperties() { var row = new BatikModels.PersonneRow { PEPKID = 42, PEIDCivilite = 2, PENom = "Dupont", PEPrenom = "Jean", PEIDRole = 3, PETelFixe = "0472000000", PETelMobile = "0600000000", PEEmail = "jean.dupont@ville-lyon.fr", PEFax = "0472000001", PEIDClient = 10, PEIDAdressePrincipale = 20, PENote = "Contact principal", PEActif = true, PEUsageInterne = false, PEInfoSupp = "Disponible en journée", Archive = true, PEIDImport = "IMP-B-42" }; var referenceRows = new Dictionary { [2] = new() { PAPKID = 2, PACode = "C", PAValeur = "1", PALibelle = "M." }, [3] = new() { PAPKID = 3, PACode = "R", PAValeur = "1", PALibelle = "Contact principal" } }; CanonicalMappingResult result = BatikMappers.BatikContactMapper.Map(row, referenceRows); Contact contact = result.Value; Assert.Equal("M.", contact.Civilite!.Label); Assert.Equal(2, contact.Civilite!.LegacyId); Assert.Equal("Dupont", contact.Nom); Assert.Equal("Jean", contact.Prenom); Assert.Equal("Jean Dupont", contact.NomAffiche); Assert.Single(contact.Roles); Assert.Equal("Contact principal", contact.Roles[0].Label); Assert.Equal(3, contact.Roles[0].LegacyId); Assert.Equal("jean.dupont@ville-lyon.fr", contact.Coordonnees!.Email); Assert.Equal("0472000000", contact.Coordonnees!.Phone); Assert.Equal("0600000000", contact.Coordonnees!.Mobile); Assert.Equal("0472000001", contact.Coordonnees!.Fax); Assert.True(contact.Actif); Assert.True(contact.EstArchive); Assert.Equal("Disponible en journée", contact.InformationsSupplementaires); Assert.Equal(42, contact.SourceReference!.NumericId); Assert.Equal("IMP-B-42", contact.SourceReference!.ImportId); Assert.Equal(DatabaseSchemaVersion.Batik, contact.SourceReference!.Source); } // ---- 3. Coordonnées nulles ---------------------------------------------------- [Fact] public void ContactBatik_EmptyCoordinates_ProducesNullCoordonnees() { var row = new BatikModels.PersonneRow { PEPKID = 5, PENom = "Martin", PEEmail = " ", PETelFixe = "", PETelMobile = null, PEFax = "\t" }; CanonicalMappingResult result = BatikMappers.BatikContactMapper.Map(row); Assert.Null(result.Value.Coordonnees); Assert.Null(result.Value.Prenom); Assert.Equal("Martin", result.Value.Nom); } [Fact] public void ContactBatik_PartialCoordinates_KeepsOnlyProvidedValues() { var row = new BatikModels.PersonneRow { PEPKID = 6, PEEmail = "m@exemple.fr", PETelFixe = null, PETelMobile = "0600000000", PEFax = "" }; CanonicalMappingResult result = BatikMappers.BatikContactMapper.Map(row); Assert.NotNull(result.Value.Coordonnees); Assert.Equal("m@exemple.fr", result.Value.Coordonnees!.Email); Assert.Null(result.Value.Coordonnees!.Phone); Assert.Equal("0600000000", result.Value.Coordonnees!.Mobile); Assert.Null(result.Value.Coordonnees!.Fax); } // ---- 4. Conservation de l'identifiant source ---------------------------------- [Fact] public void ContactBatik_SourceIdentifier_IsPreservedWithoutFabricatedGuid() { var row = new BatikModels.PersonneRow { PEPKID = 1234, PEIDImport = "IMP-B" }; CanonicalMappingResult result = BatikMappers.BatikContactMapper.Map(row); Assert.NotNull(result.Value.SourceReference); Assert.Equal(DatabaseSchemaVersion.Batik, result.Value.SourceReference!.Source); Assert.Equal(1234, result.Value.SourceReference!.NumericId); Assert.Equal("IMP-B", result.Value.SourceReference!.ImportId); Assert.Equal("1234", result.Value.SourceReference!.PublicIdentifier); Assert.Null(result.Value.SourceReference!.EntityGuid); Assert.Null(result.Value.SourceReference!.PhysicalGuid); } // ---- 5. Aucune exception sur les chaînes vides ---------------------------------- [Fact] public void ContactBatik_EmptyOrNullStrings_NeverThrow() { var row = new BatikModels.PersonneRow { PENom = "", PEPrenom = " ", PEEmail = "", PETelFixe = null, PETelMobile = " ", PEFax = "", PENote = "", PEInfoSupp = " ", PEIDImport = "" }; CanonicalMappingResult result = BatikMappers.BatikContactMapper.Map(row); Assert.NotNull(result.Value); Assert.Null(result.Value.Nom); Assert.Null(result.Value.Prenom); Assert.Null(result.Value.Coordonnees); Assert.Null(result.Value.InformationsSupplementaires); Assert.Empty(result.Warnings); } [Fact] public void ContactBatik_DefaultRow_NeverThrows() { var row = new BatikModels.PersonneRow(); CanonicalMappingResult result = BatikMappers.BatikContactMapper.Map(row); Assert.NotNull(result.Value); Assert.Null(result.Value.SourceReference!.NumericId); Assert.False(result.Value.Actif); Assert.False(result.Value.EstArchive); } // ---- 6. Absence de noms physiques HFSQL dans le domaine canonique -------------- [Fact] public void ContactBatik_CanonicalDomain_DoesNotExposeHfSqlNames() { var row = new BatikModels.PersonneRow { PEPKID = 40, PEIDCivilite = 2, PENom = "Dupont", PEPrenom = "Jean", PEIDRole = 3, PEEmail = "j@exemple.fr", PETelFixe = "01", PETelMobile = "06", PEFax = "02", PEIDClient = 10, PEIDAdressePrincipale = 20, PENote = "note", PEActif = true, PEUsageInterne = true, PEInfoSupp = "info", Archive = false, PEIDImport = "IMP-40" }; var referenceRows = new Dictionary { [2] = new() { PAPKID = 2, PACode = "C", PAValeur = "1", PALibelle = "M." }, [3] = new() { PAPKID = 3, PACode = "R", PAValeur = "1", PALibelle = "Rôle" } }; CanonicalMappingResult result = BatikMappers.BatikContactMapper.Map(row, referenceRows); Contact contact = result.Value; string[] forbidden = [ "PE_", "CL_", "AD_", "PA_", "PKID", "HfSql", "HFSQL", "GUID" ]; AssertHasNoPhysicalNames(contact, forbidden); if (contact.Coordonnees is not null) { AssertHasNoPhysicalNames(contact.Coordonnees, forbidden); } if (contact.SourceReference is not null) { AssertHasNoPhysicalNames(contact.SourceReference, forbidden); } } // ---- Référentiels non résolus -------------------------------------------------- [Fact] public void ContactBatik_UnresolvedCiviliteAndRole_KeepsLegacyIdAndWarns() { var row = new BatikModels.PersonneRow { PEPKID = 7, PEIDCivilite = 77, PEIDRole = 88 }; CanonicalMappingResult result = BatikMappers.BatikContactMapper.Map(row); Assert.NotNull(result.Value.Civilite); Assert.Equal(77, result.Value.Civilite!.LegacyId); Assert.Equal(string.Empty, result.Value.Civilite!.Label); Assert.Single(result.Value.Roles); Assert.Equal(88, result.Value.Roles[0].LegacyId); Assert.Contains(result.Warnings, w => w.Contains("PE_IDCivilite", StringComparison.Ordinal) && w.Contains(MappingWarningCodes.UnresolvedReference, StringComparison.Ordinal)); Assert.Contains(result.Warnings, w => w.Contains("PE_IDRole", StringComparison.Ordinal) && w.Contains(MappingWarningCodes.UnresolvedReference, StringComparison.Ordinal)); } [Fact] public void ContactBatik_ZeroCiviliteAndRole_ProducesNoReference() { var row = new BatikModels.PersonneRow { PEPKID = 8, PEIDCivilite = 0, PEIDRole = 0 }; CanonicalMappingResult result = BatikMappers.BatikContactMapper.Map(row); Assert.Null(result.Value.Civilite); Assert.Empty(result.Value.Roles); Assert.Empty(result.Warnings); } // ---- Rubriques sans équivalent -------------------------------------------------- [Fact] public void ContactBatik_FieldsWithoutCanonicalEquivalent_AreReportedAsIgnored() { var row = new BatikModels.PersonneRow { PEPKID = 9, PEIDClient = 10, PEIDAdressePrincipale = 20, PENote = "note", PEUsageInterne = true, ModifDate = new DateTime(2024, 1, 1), CreationDate = new DateTime(2023, 1, 1), DateArchivage = new DateTime(2024, 6, 1) }; CanonicalMappingResult result = BatikMappers.BatikContactMapper.Map(row); Assert.Contains(result.IgnoredFields, f => f == "PE_IDClient"); Assert.Contains(result.IgnoredFields, f => f == "PE_IDAdressePrincipale"); Assert.Contains(result.IgnoredFields, f => f == "PE_Note"); Assert.Contains(result.IgnoredFields, f => f == "PE_UsageInterne"); Assert.Contains(result.IgnoredFields, f => f == "ModifDate"); Assert.Contains(result.IgnoredFields, f => f == "CreationDate"); Assert.Contains(result.IgnoredFields, f => f == "DateArchivage"); Assert.Null(result.Value.ClientId); Assert.Empty(result.Value.SiteIds); } // ---- 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)); } } }