using Omika.Core.Compatibility; using Omika.Core.Domain.Entities.Clients; using Omika.Core.Domain.ValueObjects; 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 sites (table Lieu). /// Aucune base réelle, aucun HFSQL : uniquement des objets en mémoire. /// public class OmikaSiteMapperTests { // ---- 1. Site Omika minimal -------------------------------------------------- [Fact] public void SiteOmika_Minimal_MapsWithoutExceptionAndKeepsNoFabricatedValue() { var row = new OmikaModels.LieuRow { LIPKID = 1 }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row); Assert.Equal(DatabaseSchemaVersion.Omika, result.SourceVersion); Assert.NotNull(result.Value); Assert.Null(result.Value.Code); Assert.Null(result.Value.Type); Assert.Null(result.Value.Adresse); Assert.Null(result.Value.InformationsComplementaires); Assert.False(result.Value.EstArchive); Assert.NotNull(result.Value.SourceReference); Assert.Equal(DatabaseSchemaVersion.Omika, result.Value.SourceReference!.Source); Assert.Equal(1, result.Value.SourceReference!.NumericId); Assert.Null(result.Value.SourceReference!.EntityGuid); Assert.Empty(result.Warnings); } // ---- 2. Site Omika complet -------------------------------------------------- [Fact] public void SiteOmika_Full_MapsAllCanonicalProperties() { var row = new OmikaModels.LieuRow { LIPKID = 42, GUID = "550e8400-e29b-41d4-a716-446655440000", LICodeLieu = "S-001", LICleType = "IMM", LIIDType = 2, LINote = "Site principal", Archive = true, LIIDImport = "IMP-O-42" }; var referenceRows = new Dictionary { ["IMM"] = new() { PAPKID = 2, PACLE = "IMM", PALibelle = "Immeuble", PACategorie = "TYPE" } }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row, referenceRows); Site site = result.Value; Assert.Equal("S-001", site.Code); Assert.Equal("Immeuble", site.Type!.Label); Assert.Equal("IMM", site.Type!.Key); Assert.Equal(2, site.Type!.LegacyId); Assert.Equal("Site principal", site.InformationsComplementaires); Assert.True(site.EstArchive); Assert.Equal(42, site.SourceReference!.NumericId); Assert.Equal("IMP-O-42", site.SourceReference!.ImportId); Assert.Equal(DatabaseSchemaVersion.Omika, site.SourceReference!.Source); Assert.NotNull(site.SourceReference!.EntityGuid); } // ---- 3. GUID valide avec tirets ---------------------------------------------- [Fact] public void SiteOmika_ValidGuidWithDashes_IsKeptInEntityGuid() { var row = new OmikaModels.LieuRow { LIPKID = 7, GUID = "550e8400-e29b-41d4-a716-446655440000", LICodeLieu = "S-007" }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row); Assert.NotNull(result.Value.SourceReference!.EntityGuid); Assert.Equal( "550e8400-e29b-41d4-a716-446655440000", result.Value.SourceReference!.EntityGuid!.Value.ToString("D")); Assert.Equal("550e8400-e29b-41d4-a716-446655440000", result.Value.SourceReference!.PhysicalGuid); Assert.Empty(result.Warnings); } // ---- 4. GUID valide sans tirets ---------------------------------------------- [Fact] public void SiteOmika_ValidGuidWithoutDashes_IsParsedByTryParse() { var row = new OmikaModels.LieuRow { LIPKID = 8, GUID = "550e8400e29b41d4a716446655440000" }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row); Assert.NotNull(result.Value.SourceReference!.EntityGuid); Assert.Equal( "550e8400-e29b-41d4-a716-446655440000", result.Value.SourceReference!.EntityGuid!.Value.ToString("D")); Assert.Empty(result.Warnings); } // ---- 5. GUID invalide --------------------------------------------------------- [Fact] public void SiteOmika_InvalidGuid_ProducesNullGuidAndWarningWithoutException() { var row = new OmikaModels.LieuRow { LIPKID = 9, GUID = "not-a-guid", LICodeLieu = "S-009" }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row); Assert.Null(result.Value.SourceReference!.EntityGuid); Assert.Equal("not-a-guid", result.Value.SourceReference!.PhysicalGuid); Assert.Contains(result.Warnings, w => w.StartsWith(MappingWarningCodes.InvalidPhysicalGuid, StringComparison.Ordinal)); Assert.Equal("S-009", result.Value.Code); } // ---- 6. Absence de GUID fabriqué ---------------------------------------------- [Fact] public void SiteOmika_WithoutGuid_ProducesNoFabricatedGuid() { var row = new OmikaModels.LieuRow { LIPKID = 11 }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row); Assert.Null(result.Value.SourceReference!.EntityGuid); Assert.Null(result.Value.SourceReference!.PhysicalGuid); Assert.Empty(result.Warnings); } // ---- 7. Conservation de l'identifiant numérique historique --------------------- [Fact] public void SiteOmika_NumericIdentifier_IsPreserved() { var row = new OmikaModels.LieuRow { LIPKID = 1234 }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row); Assert.NotNull(result.Value.SourceReference); Assert.Equal(DatabaseSchemaVersion.Omika, result.Value.SourceReference!.Source); Assert.Equal(1234, result.Value.SourceReference!.NumericId); } // ---- 8. Conservation de l'identifiant d'import --------------------------------- [Fact] public void SiteOmika_ImportId_IsPreserved() { var row = new OmikaModels.LieuRow { LIPKID = 5678, LIIDImport = "IMP-O" }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row); Assert.Equal("IMP-O", result.Value.SourceReference!.ImportId); Assert.Equal(5678, result.Value.SourceReference!.NumericId); } // ---- 9. Priorité de la clé métier sur l'ancien identifiant --------------------- [Fact] public void SiteOmika_BusinessKeyIsPreferredAndLegacyIdIsKeptAsCompatibility() { var referenceRows = new Dictionary { ["IMM"] = new() { PAPKID = 50, PACLE = "IMM", PALibelle = "Immeuble", PACategorie = "TYPE" } }; var row = new OmikaModels.LieuRow { LIPKID = 13, LICleType = "IMM", LIIDType = 99 // zombie : ancien identifiant }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row, referenceRows); Assert.Equal("IMM", result.Value.Type!.Key); Assert.Equal("Immeuble", result.Value.Type!.Label); Assert.Equal(99, result.Value.Type!.LegacyId); Assert.Empty(result.Warnings); } // ---- 10. Repli sur un champ zombie avec avertissement --------------------------- [Fact] public void SiteOmika_MissingBusinessKey_ExposesZombieLegacyIdWithWarning() { var row = new OmikaModels.LieuRow { LIPKID = 14, LICleType = null, // rubrique active absente LIIDType = 77 // ancien identifiant (zombie) }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row); Assert.NotNull(result.Value.Type); Assert.Equal(77, result.Value.Type!.LegacyId); Assert.Null(result.Value.Type!.Key); Assert.Contains(result.Warnings, w => w.StartsWith(MappingWarningCodes.ZombieFieldUsedAsFallback, StringComparison.Ordinal)); } // ---- 11. Référentiel non résolu ------------------------------------------------- [Fact] public void SiteOmika_UnresolvedBusinessKey_KeepsKeyAndLegacyIdAndWarns() { var row = new OmikaModels.LieuRow { LIPKID = 15, LICleType = "IMM", LIIDType = 77 }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row); Assert.NotNull(result.Value.Type); Assert.Equal("IMM", result.Value.Type!.Key); Assert.Equal(77, result.Value.Type!.LegacyId); Assert.Equal(string.Empty, result.Value.Type!.Label); Assert.Contains(result.Warnings, w => w.Contains(MappingWarningCodes.UnresolvedReference, StringComparison.Ordinal)); } [Fact] public void SiteOmika_ZeroType_ProducesNoReference() { var row = new OmikaModels.LieuRow { LIPKID = 16, LIIDType = 0 }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row); Assert.Null(result.Value.Type); Assert.Empty(result.Warnings); } // ---- 12. Adresse déjà chargée en mémoire --------------------------------------- [Fact] public void SiteOmika_Adresse_IsMappedFromInMemoryDictionary() { var row = new OmikaModels.LieuRow { LIPKID = 17, LIIDAdresse = 20 }; var adresses = new Dictionary { [20] = new() { ADPKID = 20, ADAdresse = "1 place de la Comédie", ADVille = "Lyon", ADCodePostal = "69001" } }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row, adresses: adresses); Assert.NotNull(result.Value.Adresse); Assert.Equal("1 place de la Comédie", result.Value.Adresse!.Line1); Assert.Equal("69001", result.Value.Adresse!.PostalCode); Assert.Equal("Lyon", result.Value.Adresse!.City); } [Fact] public void SiteOmika_NoAdresse_WhenNothingProvided() { var row = new OmikaModels.LieuRow { LIPKID = 18, LIIDAdresse = 0 }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row); Assert.Null(result.Value.Adresse); Assert.Empty(result.Warnings); } [Fact] public void SiteOmika_UnresolvedAdresse_WarnsAndProducesNull() { var row = new OmikaModels.LieuRow { LIPKID = 19, LIIDAdresse = 99 }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row); Assert.Null(result.Value.Adresse); Assert.Contains(result.Warnings, w => w.Contains(MappingWarningCodes.UnresolvedReference, StringComparison.Ordinal)); } // ---- 13. Relations différées ---------------------------------------------------- [Fact] public void SiteOmika_Relations_AreLeftForLaterAssembly() { var row = new OmikaModels.LieuRow { LIPKID = 20, LIIDClientPrincipal = 30 }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row); Assert.Null(result.Value.ClientId); Assert.Empty(result.Value.ContactIds); Assert.Contains(result.IgnoredFields, f => f == "LI_IDClientPrincipal"); } // ---- 14. Chaînes vides sans exception ------------------------------------------- [Fact] public void SiteOmika_EmptyOrNullStrings_NeverThrow() { var row = new OmikaModels.LieuRow { LICodeLieu = "", LINote = " ", LIIDImport = "", LICodeAcces = " ", LIRaisonBlocage = "", LIRepertoireDocs = " ", GUID = null }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row); Assert.NotNull(result.Value); Assert.Null(result.Value.Code); Assert.Null(result.Value.InformationsComplementaires); Assert.Null(result.Value.SourceReference!.ImportId); Assert.Empty(result.Warnings); } [Fact] public void SiteOmika_DefaultRow_NeverThrows() { var row = new OmikaModels.LieuRow(); CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row); Assert.NotNull(result.Value); Assert.Null(result.Value.SourceReference!.NumericId); Assert.False(result.Value.EstArchive); } // ---- 15. Champs sans équivalent dans IgnoredFields -------------------------------- [Fact] public void SiteOmika_FieldsWithoutCanonicalEquivalent_AreReportedAsIgnored() { var row = new OmikaModels.LieuRow { LIPKID = 21, LICodeAcces = "Code 1234", LIBadge = true, LIInterphone = true, LIBlocage = true, LIRaisonBlocage = "Travaux", LIRepertoireDocs = "docs/site", LIIDClientPrincipal = 30, DateArchivage = new DateTime(2024, 6, 1), ModifDate = new DateTime(2024, 1, 1), CreationDate = new DateTime(2023, 1, 1), CreationUser = 5, ModifUser = 6 }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row); Assert.Equal("Code 1234", result.Value.CodeAcces); Assert.True(result.Value.Badge); Assert.True(result.Value.Interphone); Assert.DoesNotContain(result.IgnoredFields, f => f == "LI_CodeAcces"); Assert.DoesNotContain(result.IgnoredFields, f => f == "LI_Badge"); Assert.DoesNotContain(result.IgnoredFields, f => f == "LI_Interphone"); Assert.Contains(result.IgnoredFields, f => f == "LI_Blocage"); Assert.Contains(result.IgnoredFields, f => f == "LI_RaisonBlocage"); Assert.Contains(result.IgnoredFields, f => f == "LI_RepertoireDocs"); Assert.Contains(result.IgnoredFields, f => f == "LI_IDClientPrincipal"); Assert.Contains(result.IgnoredFields, f => f == "DateArchivage"); Assert.Equal(new DateTime(2024, 1, 1), result.Value.DateModification); Assert.Contains(result.IgnoredFields, f => f == "CreationDate"); Assert.Contains(result.IgnoredFields, f => f == "CreationUser"); Assert.Contains(result.IgnoredFields, f => f == "ModifUser"); } // ---- 16. Absence de noms physiques HFSQL dans le domaine canonique ---------------- [Fact] public void SiteOmika_CanonicalDomain_DoesNotExposeHfSqlNames() { var row = new OmikaModels.LieuRow { LIPKID = 40, GUID = "550e8400-e29b-41d4-a716-446655440000", LICodeLieu = "S-040", LICleType = "IMM", LIIDType = 2, LINote = "note", Archive = false, LIIDImport = "IMP-40", LIIDAdresse = 20, LIIDClientPrincipal = 30, LICodeAcces = "code", LIBadge = true, LIInterphone = true, LIBlocage = true, LIRaisonBlocage = "raison", LIRepertoireDocs = "docs" }; var referenceRows = new Dictionary { ["IMM"] = new() { PAPKID = 2, PACLE = "IMM", PALibelle = "Immeuble", PACategorie = "TYPE" } }; var adresses = new Dictionary { [20] = new() { ADPKID = 20, ADAdresse = "1 rue de la Paix", ADVille = "Lyon", ADCodePostal = "69001" } }; CanonicalMappingResult result = OmikaMappers.OmikaSiteMapper.Map(row, referenceRows, adresses); Site site = result.Value; string[] forbidden = [ "LI_", "AD_", "PA_", "PKID", "HfSql", "HFSQL", "GUID" ]; AssertHasNoPhysicalNames(site, forbidden); if (site.Adresse is not null) { AssertHasNoPhysicalNames(site.Adresse, forbidden); } if (site.SourceReference is not null) { AssertHasNoPhysicalNames(site.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)); } } }