Files
Omika/tests/Omika.Tests/Mapping/OmikaSiteMapperTests.cs
T
2026-10-23 18:46:50 +02:00

457 lines
16 KiB
C#

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;
/// <summary>
/// Tests unitaires du mapper Omika → canonique des sites (table Lieu).
/// Aucune base réelle, aucun HFSQL : uniquement des objets en mémoire.
/// </summary>
public class OmikaSiteMapperTests
{
// ---- 1. Site Omika minimal --------------------------------------------------
[Fact]
public void SiteOmika_Minimal_MapsWithoutExceptionAndKeepsNoFabricatedValue()
{
var row = new OmikaModels.LieuRow { LIPKID = 1 };
CanonicalMappingResult<Site> 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<string, OmikaModels.ParametreMetierRow>
{
["IMM"] = new() { PAPKID = 2, PACLE = "IMM", PALibelle = "Immeuble", PACategorie = "TYPE" }
};
CanonicalMappingResult<Site> 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<Site> 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<Site> 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<Site> 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<Site> 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<Site> 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<Site> 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<string, OmikaModels.ParametreMetierRow>
{
["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<Site> 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<Site> 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<Site> 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<Site> 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<long, OmikaModels.AdresseRow>
{
[20] = new() { ADPKID = 20, ADAdresse = "1 place de la Comédie", ADVille = "Lyon", ADCodePostal = "69001" }
};
CanonicalMappingResult<Site> 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<Site> 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<Site> 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<Site> 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<Site> 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<Site> 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<Site> 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<string, OmikaModels.ParametreMetierRow>
{
["IMM"] = new() { PAPKID = 2, PACLE = "IMM", PALibelle = "Immeuble", PACategorie = "TYPE" }
};
var adresses = new Dictionary<long, OmikaModels.AdresseRow>
{
[20] = new() { ADPKID = 20, ADAdresse = "1 rue de la Paix", ADVille = "Lyon", ADCodePostal = "69001" }
};
CanonicalMappingResult<Site> 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));
}
}
}