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

496 lines
20 KiB
C#

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 OmikaModels = Omika.Data.HfSql.Models.Omika;
using BatikMappers = Omika.Data.Mapping.Batik;
using OmikaMappers = Omika.Data.Mapping.Omika;
namespace Omika.Tests.Mapping;
/// <summary>
/// Tests unitaires des premiers mappers physiques → canonique (Clients,
/// Adresses et valeurs de référentiel). Aucune base réelle, aucun HFSQL :
/// uniquement des objets en mémoire.
/// </summary>
public class PhysicalToCanonicalMappingTests
{
// ---- 1. Client Batik minimal -------------------------------------------------
[Fact]
public void ClientBatik_Minimal_MapsWithoutExceptionAndKeepsNoFabricatedValue()
{
var row = new BatikModels.ClientRow { CLPKID = 1 };
CanonicalMappingResult<Client> result = BatikMappers.BatikClientMapper.Map(row);
Assert.Equal(DatabaseSchemaVersion.Batik, result.SourceVersion);
Assert.NotNull(result.Value);
Assert.Null(result.Value.Nom);
Assert.Null(result.Value.Type);
Assert.Null(result.Value.Statut);
Assert.Null(result.Value.Coordonnees);
Assert.Null(result.Value.AdressePrincipale);
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. Client Batik complet -------------------------------------------------
[Fact]
public void ClientBatik_Full_MapsAllCanonicalProperties()
{
var row = new BatikModels.ClientRow
{
CLPKID = 42,
CLCodeClient = "C-001",
CLNom = "Ville de Lyon",
CLIDType = 2,
CLIDStatut = 3,
CLFormeJuridique = "Collectivité",
CLCodeTVA = "FR12345678901",
CLCodeComptable = "411LYON",
CLSIREN = "123456789",
CLSIRET = "12345678900012",
CLEmail = "contact@ville-lyon.fr",
CLTel = "0472000000",
CLFax = "0472000001",
CLPays = "FR",
CLInfoSup = "Client principal",
CLBlocage = true,
ClRaisonBlocage = "Litige en cours",
Archive = true,
CLIDAdressePrincipale = 10
};
var referenceRows = new Dictionary<long, BatikModels.ParametreMetierRow>
{
[2] = new() { PAPKID = 2, PACode = "T", PAValeur = "1", PALibelle = "Régie" },
[3] = new() { PAPKID = 3, PACode = "S", PAValeur = "1", PALibelle = "Actif" }
};
var adresses = new Dictionary<long, BatikModels.AdresseRow>
{
[10] = new() { ADPKID = 10, ADEtiquette = "Mairie", ADLigne1 = "1 place de la Comédie", ADVille = "Lyon", ADCodePostal = "69001" }
};
CanonicalMappingResult<Client> result = BatikMappers.BatikClientMapper.Map(row, referenceRows, adresses);
Client client = result.Value;
Assert.Equal("C-001", client.Code);
Assert.Equal("Ville de Lyon", client.Nom);
Assert.Equal("Régie", client.Type!.Label);
Assert.Equal(2, client.Type!.LegacyId);
Assert.Equal("T", client.Type!.Code);
Assert.Equal("Actif", client.Statut!.Label);
Assert.Equal(3, client.Statut!.LegacyId);
Assert.Equal("Collectivité", client.FormeJuridique!.Label);
Assert.Equal("FR12345678901", client.CodeTva);
Assert.Equal("411LYON", client.CodeComptable);
Assert.Equal("123456789", client.Siren);
Assert.Equal("12345678900012", client.Siret);
Assert.Equal("contact@ville-lyon.fr", client.Coordonnees!.Email);
Assert.Equal("0472000000", client.Coordonnees!.Phone);
Assert.Equal("0472000001", client.Coordonnees!.Fax);
Assert.Equal("FR", client.Pays);
Assert.Equal("Client principal", client.InformationsComplementaires);
Assert.True(client.EstBloque);
Assert.Equal("Litige en cours", client.RaisonBlocage);
Assert.True(client.EstArchive);
Assert.NotNull(client.AdressePrincipale);
Assert.True(client.AdressePrincipale!.EstPrincipale);
Assert.Equal("Mairie", client.AdressePrincipale!.Etiquette);
Assert.Equal("69001", client.AdressePrincipale!.AdressePostale!.PostalCode);
Assert.Equal(42, client.SourceReference!.NumericId);
Assert.Equal(DatabaseSchemaVersion.Batik, client.SourceReference!.Source);
}
// ---- 3. Adresse Batik ---------------------------------------------------------
[Fact]
public void AdresseBatik_MapsLinesAndFillsLine3FromLine4Line5WhenEmpty()
{
var complete = new BatikModels.AdresseRow
{
ADPKID = 5,
ADEtiquette = "Siège",
ADLigne1 = "M. Dupont",
ADLigne2 = "Service comptabilité",
ADLigne3 = "Bâtiment A",
ADLigne4 = "12 rue de la Paix",
ADLigne5 = "ZI Nord",
ADCodePostal = "69001",
ADVille = "Lyon",
ADPresentation = "bloc courrier"
};
CanonicalMappingResult<Adresse> result = BatikMappers.BatikAdresseMapper.Map(complete);
Adresse adresse = result.Value;
Assert.Equal(DatabaseSchemaVersion.Batik, result.SourceVersion);
Assert.Equal("Siège", adresse.Etiquette);
Assert.Equal("M. Dupont", adresse.AdressePostale!.Line1);
Assert.Equal("Service comptabilité", adresse.AdressePostale!.Line2);
Assert.Equal("Bâtiment A", adresse.AdressePostale!.Line3);
Assert.Equal("69001", adresse.AdressePostale!.PostalCode);
Assert.Equal("Lyon", adresse.AdressePostale!.City);
Assert.Equal(5, adresse.SourceReference!.NumericId);
Assert.Contains(result.IgnoredFields, f => f == "AD_Presentation");
var withoutLine3 = new BatikModels.AdresseRow
{
ADPKID = 6,
ADLigne1 = "M. Dupont",
ADLigne4 = "12 rue de la Paix",
ADLigne5 = "ZI Nord",
ADCodePostal = "69001",
ADVille = "Lyon"
};
CanonicalMappingResult<Adresse> result2 = BatikMappers.BatikAdresseMapper.Map(withoutLine3);
Assert.Equal("12 rue de la Paix ZI Nord", result2.Value.AdressePostale!.Line3);
}
// ---- 4. Client Omika avec GUID valide ----------------------------------------
[Fact]
public void ClientOmika_ValidGuid_KeepsEntityGuidAndPhysicalGuid()
{
var row = new OmikaModels.ClientRow
{
CLPKID = 7,
GUID = "550e8400-e29b-41d4-a716-446655440000",
CLNom = "SARL Omika"
};
CanonicalMappingResult<Client> result = OmikaMappers.OmikaClientMapper.Map(row);
Assert.Equal(DatabaseSchemaVersion.Omika, result.SourceVersion);
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.Equal(7, result.Value.SourceReference!.NumericId);
Assert.Equal("SARL Omika", result.Value.Nom);
}
// ---- 5. Client Omika avec GUID sans tirets ------------------------------------
[Fact]
public void ClientOmika_GuidWithoutDashes_IsParsedByTryParse()
{
var row = new OmikaModels.ClientRow
{
CLPKID = 8,
GUID = "550e8400e29b41d4a716446655440000"
};
CanonicalMappingResult<Client> result = OmikaMappers.OmikaClientMapper.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);
}
// ---- 6. Client Omika avec GUID invalide ---------------------------------------
[Fact]
public void ClientOmika_InvalidGuid_ProducesNullGuidAndWarningWithoutException()
{
var row = new OmikaModels.ClientRow
{
CLPKID = 9,
GUID = "not-a-guid",
CLNom = "Société inconnue"
};
CanonicalMappingResult<Client> result = OmikaMappers.OmikaClientMapper.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("Société inconnue", result.Value.Nom);
}
// ---- 7. Adresse Omika ----------------------------------------------------------
[Fact]
public void AdresseOmika_NativeMemoIsPreferredOverZombieLines()
{
var row = new OmikaModels.AdresseRow
{
ADPKID = 11,
GUID = "1f0e8400-e29b-41d4-a716-446655441111",
ADEtiquette = "Agence Lyon",
ADAdresse = "12 rue de la Paix",
ADLigne1 = "ancienne ligne 1",
ADCodePostal = "69001",
ADVille = "Lyon"
};
CanonicalMappingResult<Adresse> result = OmikaMappers.OmikaAdresseMapper.Map(row);
Assert.Equal(DatabaseSchemaVersion.Omika, result.SourceVersion);
Assert.Equal("12 rue de la Paix", result.Value.AdressePostale!.Line1);
Assert.Equal("Agence Lyon", result.Value.Etiquette);
Assert.Equal("69001", result.Value.AdressePostale!.PostalCode);
Assert.Equal("Lyon", result.Value.AdressePostale!.City);
Assert.DoesNotContain(result.Warnings, w => w.StartsWith(MappingWarningCodes.ZombieFieldUsedAsFallback, StringComparison.Ordinal));
Assert.NotNull(result.Value.SourceReference!.EntityGuid);
}
[Fact]
public void AdresseOmika_WithoutNativeMemo_UsesZombieLinesAsDocumentedFallback()
{
var row = new OmikaModels.AdresseRow
{
ADPKID = 12,
ADLigne1 = "M. Dupont",
ADLigne2 = "Service comptabilité",
ADCodePostal = "69002",
ADVille = "Lyon"
};
CanonicalMappingResult<Adresse> result = OmikaMappers.OmikaAdresseMapper.Map(row);
Assert.Equal("M. Dupont", result.Value.AdressePostale!.Line1);
Assert.Equal("Service comptabilité", result.Value.AdressePostale!.Line2);
Assert.Contains(result.Warnings, w => w.StartsWith(MappingWarningCodes.ZombieFieldUsedAsFallback, StringComparison.Ordinal));
}
// ---- 8. Priorité de la clé métier Omika ----------------------------------------
[Fact]
public void ClientOmika_BusinessKeyIsPreferredAndLegacyIdIsKeptAsCompatibility()
{
var referenceRows = new Dictionary<string, OmikaModels.ParametreMetierRow>
{
["ENT"] = new() { PAPKID = 50, PACLE = "ENT", PALibelle = "Entreprise", PACategorie = "TYPE" }
};
var row = new OmikaModels.ClientRow
{
CLPKID = 13,
CLCleType = "ENT",
CLIDType = 99, // zombie : ancien identifiant
CLCleStatut = null,
CLIDStatut = 5 // zombie : ancien identifiant, pas de clé métier
};
CanonicalMappingResult<Client> result = OmikaMappers.OmikaClientMapper.Map(row, referenceRows);
Assert.Equal("ENT", result.Value.Type!.Key);
Assert.Equal("Entreprise", result.Value.Type!.Label);
Assert.Equal(99, result.Value.Type!.LegacyId);
Assert.NotNull(result.Value.Statut);
Assert.Equal(5, result.Value.Statut!.LegacyId);
Assert.Null(result.Value.Statut!.Key);
Assert.Contains(result.Warnings, w => w.StartsWith(MappingWarningCodes.ZombieFieldUsedAsFallback, StringComparison.Ordinal));
}
// ---- 9. Conservation des identifiants sources ----------------------------------
[Fact]
public void SourceIdentifiers_ArePreservedOnBothSchemas()
{
var batik = new BatikModels.ClientRow { CLPKID = 1234, CLIDImport = "IMP-B" };
CanonicalMappingResult<Client> batikResult = BatikMappers.BatikClientMapper.Map(batik);
Assert.Equal(DatabaseSchemaVersion.Batik, batikResult.Value.SourceReference!.Source);
Assert.Equal(1234, batikResult.Value.SourceReference!.NumericId);
Assert.Equal("IMP-B", batikResult.Value.SourceReference!.ImportId);
Assert.Null(batikResult.Value.SourceReference!.EntityGuid);
var omika = new OmikaModels.ClientRow
{
CLPKID = 5678,
GUID = "550e8400-e29b-41d4-a716-446655440000",
CLIDImport = "IMP-O"
};
CanonicalMappingResult<Client> omikaResult = OmikaMappers.OmikaClientMapper.Map(omika);
Assert.Equal(DatabaseSchemaVersion.Omika, omikaResult.Value.SourceReference!.Source);
Assert.Equal(5678, omikaResult.Value.SourceReference!.NumericId);
Assert.Equal("IMP-O", omikaResult.Value.SourceReference!.ImportId);
Assert.NotNull(omikaResult.Value.SourceReference!.EntityGuid);
}
// ---- 10. Aucune exception sur les valeurs historiques ou nulles ----------------
[Fact]
public void Mapping_HistoricalOrNullValues_NeverThrow()
{
var batikClient = new BatikModels.ClientRow();
BatikMappers.BatikClientMapper.Map(batikClient);
var batikAdresse = new BatikModels.AdresseRow();
BatikMappers.BatikAdresseMapper.Map(batikAdresse);
var batikRef = new BatikModels.ParametreMetierRow();
BatikMappers.BatikReferenceValueMapper.Map(batikRef);
var omikaClient = new OmikaModels.ClientRow();
OmikaMappers.OmikaClientMapper.Map(omikaClient);
var omikaAdresse = new OmikaModels.AdresseRow();
OmikaMappers.OmikaAdresseMapper.Map(omikaAdresse);
var omikaRef = new OmikaModels.ParametreMetierRow();
OmikaMappers.OmikaReferenceValueMapper.Map(omikaRef);
var omikaClientLegacy = new OmikaModels.ClientRow { CLIDType = 12, CLIDStatut = 0 };
CanonicalMappingResult<Client> legacyResult = OmikaMappers.OmikaClientMapper.Map(omikaClientLegacy);
Assert.NotNull(legacyResult.Value.Type);
Assert.Equal(12, legacyResult.Value.Type!.LegacyId);
var omikaClientNullGuid = new OmikaModels.ClientRow { GUID = null! };
Assert.NotNull(OmikaMappers.OmikaClientMapper.Map(omikaClientNullGuid).Value);
}
// ---- 11. Présence des avertissements attendus ----------------------------------
[Fact]
public void Warnings_AreRaisedForInvalidGuidAndUnresolvedReference()
{
// GUID invalide.
var invalidGuid = new OmikaModels.ClientRow { CLPKID = 21, GUID = "zzz" };
CanonicalMappingResult<Client> invalidGuidResult = OmikaMappers.OmikaClientMapper.Map(invalidGuid);
Assert.Contains(invalidGuidResult.Warnings, w => w.StartsWith(MappingWarningCodes.InvalidPhysicalGuid, StringComparison.Ordinal));
// Référentiel Batik non résolu.
var unresolved = new BatikModels.ClientRow { CLPKID = 22, CLIDType = 77 };
CanonicalMappingResult<Client> unresolvedResult = BatikMappers.BatikClientMapper.Map(unresolved);
Assert.Contains(unresolvedResult.Warnings, w => w.Contains(MappingWarningCodes.UnresolvedReference, StringComparison.Ordinal));
// Clé métier absente sur une valeur de référentiel Omika.
var noKey = new OmikaModels.ParametreMetierRow { PAPKID = 30, PALibelle = "Actif" };
CanonicalMappingResult<ReferenceValue> noKeyResult = OmikaMappers.OmikaReferenceValueMapper.Map(noKey);
Assert.Contains(noKeyResult.Warnings, w => w.StartsWith(MappingWarningCodes.MissingBusinessKey, StringComparison.Ordinal));
Assert.Equal("Actif", noKeyResult.Value.Label);
// Libellé absent : jamais inventé, simple avertissement.
var noLabel = new BatikModels.ParametreMetierRow { PAPKID = 31, PACode = "T", PAValeur = "1" };
CanonicalMappingResult<ReferenceValue> noLabelResult = BatikMappers.BatikReferenceValueMapper.Map(noLabel);
Assert.Contains(noLabelResult.Warnings, w => w.StartsWith(MappingWarningCodes.EmptyReferenceLabel, StringComparison.Ordinal));
Assert.Equal(string.Empty, noLabelResult.Value.Label);
Assert.Equal(31, noLabelResult.Value.LegacyId);
}
// ---- 12. Absence de noms HFSQL dans le domaine canonique ------------------------
[Fact]
public void CanonicalDomain_DoesNotExposeHfSqlNames()
{
var row = new OmikaModels.ClientRow
{
CLPKID = 40,
GUID = "550e8400-e29b-41d4-a716-446655440000",
CLNom = "SARL Test",
CLIDType = 0,
CLCleType = null,
CLIDStatut = 0,
CLCodeTVA = "FR123",
CLEmail = "test@test.fr",
Archive = false
};
CanonicalMappingResult<Client> result = OmikaMappers.OmikaClientMapper.Map(row);
Client client = result.Value;
string[] forbidden =
[
"CL_", "AD_", "PA_", "PKID", "HfSql", "HFSQL", "Ligne_", "GUID"
];
AssertHasNoPhysicalNames(client, forbidden);
AssertHasNoPhysicalNames(client.AdressePrincipale ?? new Adresse(), forbidden);
if (client.Coordonnees is not null)
{
AssertHasNoPhysicalNames(client.Coordonnees, forbidden);
}
if (client.SourceReference is not null)
{
AssertHasNoPhysicalNames(client.SourceReference, forbidden);
}
}
// ---- Référentiels ----------------------------------------------------------------
[Fact]
public void BatikReferenceValue_MapFavorsLegacyIdCodeAndLabel()
{
var row = new BatikModels.ParametreMetierRow
{
PAPKID = 60,
PACode = "T",
PAValeur = "1",
PALibelle = "Régie"
};
CanonicalMappingResult<ReferenceValue> result = BatikMappers.BatikReferenceValueMapper.Map(row);
Assert.Equal("Régie", result.Value.Label);
Assert.Equal(60, result.Value.LegacyId);
Assert.Equal("T", result.Value.Code);
Assert.Null(result.Value.Key);
}
[Fact]
public void OmikaReferenceValue_MapFavorsBusinessKeyAndKeepsLegacyIdAsCompatibility()
{
var row = new OmikaModels.ParametreMetierRow
{
PAPKID = 61,
PACLE = "ACT",
PALibelle = "Actif",
PACategorie = "STATUT"
};
CanonicalMappingResult<ReferenceValue> result = OmikaMappers.OmikaReferenceValueMapper.Map(row);
Assert.Equal("Actif", result.Value.Label);
Assert.Equal("ACT", result.Value.Key);
Assert.Equal(61, result.Value.LegacyId);
Assert.Equal("STATUT", result.Value.Code);
}
[Fact]
public void OmikaReferenceValue_MapByKey_UsesInMemoryDictionary()
{
var referenceRows = new Dictionary<string, OmikaModels.ParametreMetierRow>
{
["RGE"] = new() { PAPKID = 70, PACLE = "RGE", PALibelle = "Régie", PACategorie = "TYPE" }
};
CanonicalMappingResult<ReferenceValue>? resolved = OmikaMappers.OmikaReferenceValueMapper.MapByKey("RGE", referenceRows);
Assert.NotNull(resolved);
Assert.Equal("Régie", resolved!.Value.Label);
Assert.Equal("RGE", resolved.Value.Key);
}
// ---- 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));
}
}
}