Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,306 @@
|
||||
using Omika.Core.Compatibility;
|
||||
using Omika.Core.Domain.Entities.Organisation;
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Tests unitaires du mapper Batik → canonique des collaborateurs (table Collaborateur).
|
||||
/// Aucune base réelle, aucun HFSQL : uniquement des objets en mémoire.
|
||||
/// </summary>
|
||||
public class BatikCollaborateurMapperTests
|
||||
{
|
||||
// ---- 1. Collaborateur Batik minimal -------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurBatik_Minimal_MapsWithoutExceptionAndKeepsNoFabricatedValue()
|
||||
{
|
||||
var row = new BatikModels.CollaborateurRow { COPKID = 1 };
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = BatikMappers.BatikCollaborateurMapper.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.Null(result.Value.Statut);
|
||||
Assert.Null(result.Value.Coordonnees);
|
||||
Assert.False(result.Value.Actif);
|
||||
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. Collaborateur Batik complet -------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurBatik_Full_MapsAllCanonicalProperties()
|
||||
{
|
||||
var row = new BatikModels.CollaborateurRow
|
||||
{
|
||||
COPKID = 42,
|
||||
COIDCivilite = 2,
|
||||
CONom = "Dupont",
|
||||
COPrenom = "Jean",
|
||||
COTrigramme = "JD",
|
||||
COTelFixe = "0472000000",
|
||||
COTelMobile = "0600000000",
|
||||
COEmail = "jean.dupont@ville-lyon.fr",
|
||||
COIDStatut = 3,
|
||||
COActif = true,
|
||||
COIDImport = "IMP-B-42"
|
||||
};
|
||||
|
||||
var referenceRows = new Dictionary<long, BatikModels.ParametreMetierRow>
|
||||
{
|
||||
[2] = new() { PAPKID = 2, PACode = "C", PAValeur = "1", PALibelle = "M." },
|
||||
[3] = new() { PAPKID = 3, PACode = "S", PAValeur = "1", PALibelle = "Actif" }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = BatikMappers.BatikCollaborateurMapper.Map(row, referenceRows);
|
||||
|
||||
Collaborateur collaborateur = result.Value;
|
||||
Assert.Equal("M.", collaborateur.Civilite!.Label);
|
||||
Assert.Equal(2, collaborateur.Civilite!.LegacyId);
|
||||
Assert.Equal("Actif", collaborateur.Statut!.Label);
|
||||
Assert.Equal(3, collaborateur.Statut!.LegacyId);
|
||||
Assert.Equal("Dupont", collaborateur.Nom);
|
||||
Assert.Equal("Jean", collaborateur.Prenom);
|
||||
Assert.Equal("JD", collaborateur.Code);
|
||||
|
||||
Assert.Equal("jean.dupont@ville-lyon.fr", collaborateur.Coordonnees!.Email);
|
||||
Assert.Equal("0472000000", collaborateur.Coordonnees!.Phone);
|
||||
Assert.Equal("0600000000", collaborateur.Coordonnees!.Mobile);
|
||||
|
||||
Assert.True(collaborateur.Actif);
|
||||
|
||||
Assert.Equal(42, collaborateur.SourceReference!.NumericId);
|
||||
Assert.Equal(DatabaseSchemaVersion.Batik, collaborateur.SourceReference!.Source);
|
||||
}
|
||||
|
||||
// ---- 3. Coordonnées nulles ----------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurBatik_EmptyCoordinates_ProducesNullCoordonnees()
|
||||
{
|
||||
var row = new BatikModels.CollaborateurRow
|
||||
{
|
||||
COPKID = 5,
|
||||
CONom = "Martin",
|
||||
COEmail = " ",
|
||||
COTelFixe = "",
|
||||
COTelMobile = null
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = BatikMappers.BatikCollaborateurMapper.Map(row);
|
||||
|
||||
Assert.Null(result.Value.Coordonnees);
|
||||
Assert.Null(result.Value.Prenom);
|
||||
Assert.Equal("Martin", result.Value.Nom);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurBatik_PartialCoordinates_KeepsOnlyProvidedValues()
|
||||
{
|
||||
var row = new BatikModels.CollaborateurRow
|
||||
{
|
||||
COPKID = 6,
|
||||
COEmail = "m@exemple.fr",
|
||||
COTelFixe = null,
|
||||
COTelMobile = "0600000000"
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = BatikMappers.BatikCollaborateurMapper.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);
|
||||
}
|
||||
|
||||
// ---- 4. Conservation de l'identifiant source ----------------------------------
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurBatik_SourceIdentifier_IsPreservedWithoutFabricatedGuid()
|
||||
{
|
||||
var row = new BatikModels.CollaborateurRow { COPKID = 1234, COIDImport = "IMP-B" };
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = BatikMappers.BatikCollaborateurMapper.Map(row);
|
||||
|
||||
Assert.NotNull(result.Value.SourceReference);
|
||||
Assert.Equal(DatabaseSchemaVersion.Batik, result.Value.SourceReference!.Source);
|
||||
Assert.Equal(1234, result.Value.SourceReference!.NumericId);
|
||||
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 CollaborateurBatik_EmptyOrNullStrings_NeverThrow()
|
||||
{
|
||||
var row = new BatikModels.CollaborateurRow
|
||||
{
|
||||
CONom = "",
|
||||
COPrenom = " ",
|
||||
COEmail = "",
|
||||
COTelFixe = null,
|
||||
COTelMobile = " ",
|
||||
COTrigramme = "",
|
||||
COIDImport = ""
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = BatikMappers.BatikCollaborateurMapper.Map(row);
|
||||
|
||||
Assert.NotNull(result.Value);
|
||||
Assert.Null(result.Value.Nom);
|
||||
Assert.Null(result.Value.Prenom);
|
||||
Assert.Null(result.Value.Coordonnees);
|
||||
Assert.Empty(result.Warnings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurBatik_DefaultRow_NeverThrows()
|
||||
{
|
||||
var row = new BatikModels.CollaborateurRow();
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = BatikMappers.BatikCollaborateurMapper.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 CollaborateurBatik_CanonicalDomain_DoesNotExposeHfSqlNames()
|
||||
{
|
||||
var row = new BatikModels.CollaborateurRow
|
||||
{
|
||||
COPKID = 40,
|
||||
COIDCivilite = 2,
|
||||
CONom = "Dupont",
|
||||
COPrenom = "Jean",
|
||||
COTelFixe = "01",
|
||||
COTelMobile = "06",
|
||||
COEmail = "j@exemple.fr",
|
||||
COActif = true,
|
||||
COIDImport = "IMP-40"
|
||||
};
|
||||
|
||||
var referenceRows = new Dictionary<long, BatikModels.ParametreMetierRow>
|
||||
{
|
||||
[2] = new() { PAPKID = 2, PACode = "C", PAValeur = "1", PALibelle = "M." }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = BatikMappers.BatikCollaborateurMapper.Map(row, referenceRows);
|
||||
Collaborateur collaborateur = result.Value;
|
||||
|
||||
string[] forbidden =
|
||||
[
|
||||
"CO_", "CL_", "PA_", "PKID", "HfSql", "HFSQL", "GUID"
|
||||
];
|
||||
|
||||
AssertHasNoPhysicalNames(collaborateur, forbidden);
|
||||
|
||||
if (collaborateur.Coordonnees is not null)
|
||||
{
|
||||
AssertHasNoPhysicalNames(collaborateur.Coordonnees, forbidden);
|
||||
}
|
||||
|
||||
if (collaborateur.SourceReference is not null)
|
||||
{
|
||||
AssertHasNoPhysicalNames(collaborateur.SourceReference, forbidden);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Référentiels non résolus --------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurBatik_UnresolvedCiviliteAndStatut_KeepsLegacyIdAndWarns()
|
||||
{
|
||||
var row = new BatikModels.CollaborateurRow
|
||||
{
|
||||
COPKID = 7,
|
||||
COIDCivilite = 77,
|
||||
COIDStatut = 88
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = BatikMappers.BatikCollaborateurMapper.Map(row);
|
||||
|
||||
Assert.NotNull(result.Value.Civilite);
|
||||
Assert.Equal(77, result.Value.Civilite!.LegacyId);
|
||||
Assert.Equal(string.Empty, result.Value.Civilite!.Label);
|
||||
|
||||
Assert.NotNull(result.Value.Statut);
|
||||
Assert.Equal(88, result.Value.Statut!.LegacyId);
|
||||
|
||||
Assert.Contains(result.Warnings, w => w.Contains("CO_IDCivilite", StringComparison.Ordinal)
|
||||
&& w.Contains(MappingWarningCodes.UnresolvedReference, StringComparison.Ordinal));
|
||||
Assert.Contains(result.Warnings, w => w.Contains("CO_IDStatut", StringComparison.Ordinal)
|
||||
&& w.Contains(MappingWarningCodes.UnresolvedReference, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurBatik_ZeroCiviliteAndStatut_ProducesNoReference()
|
||||
{
|
||||
var row = new BatikModels.CollaborateurRow { COPKID = 8, COIDCivilite = 0, COIDStatut = 0 };
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = BatikMappers.BatikCollaborateurMapper.Map(row);
|
||||
|
||||
Assert.Null(result.Value.Civilite);
|
||||
Assert.Null(result.Value.Statut);
|
||||
Assert.Empty(result.Warnings);
|
||||
}
|
||||
|
||||
// ---- Rubriques sans équivalent --------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurBatik_FieldsWithoutCanonicalEquivalent_AreReportedAsIgnored()
|
||||
{
|
||||
var row = new BatikModels.CollaborateurRow
|
||||
{
|
||||
COPKID = 9,
|
||||
COFonction = "Développeur",
|
||||
COPonderation = 1.5f,
|
||||
COIDImport = "IMP-9",
|
||||
COAuPlanning = true,
|
||||
COAccesBO = true,
|
||||
COAccesMobile = true,
|
||||
CreationDate = new DateTime(2024, 1, 1),
|
||||
ModifDate = new DateTime(2024, 6, 1)
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = BatikMappers.BatikCollaborateurMapper.Map(row);
|
||||
|
||||
Assert.Equal("Développeur", result.Value.Fonction);
|
||||
Assert.Contains(result.IgnoredFields, f => f == "CO_Ponderation");
|
||||
Assert.Contains(result.IgnoredFields, f => f == "CO_IDImport");
|
||||
Assert.Contains(result.IgnoredFields, f => f == "CO_AuPlanning");
|
||||
Assert.Contains(result.IgnoredFields, f => f == "CO_AccesBO");
|
||||
Assert.Contains(result.IgnoredFields, f => f == "CO_AccesMobile");
|
||||
Assert.Contains(result.IgnoredFields, f => f == "CreationDate");
|
||||
Assert.Contains(result.IgnoredFields, f => f == "ModifDate");
|
||||
Assert.Null(result.Value.SocieteId);
|
||||
}
|
||||
|
||||
// ---- 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Tests unitaires du mapper Batik → canonique des contacts (table Personne).
|
||||
/// Aucune base réelle, aucun HFSQL : uniquement des objets en mémoire.
|
||||
/// </summary>
|
||||
public class BatikContactMapperTests
|
||||
{
|
||||
// ---- 1. Contact Batik minimal -------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ContactBatik_Minimal_MapsWithoutExceptionAndKeepsNoFabricatedValue()
|
||||
{
|
||||
var row = new BatikModels.PersonneRow { PEPKID = 1 };
|
||||
|
||||
CanonicalMappingResult<Contact> 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<long, BatikModels.ParametreMetierRow>
|
||||
{
|
||||
[2] = new() { PAPKID = 2, PACode = "C", PAValeur = "1", PALibelle = "M." },
|
||||
[3] = new() { PAPKID = 3, PACode = "R", PAValeur = "1", PALibelle = "Contact principal" }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Contact> 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<Contact> 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<Contact> 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<Contact> 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<Contact> 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<Contact> 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<long, BatikModels.ParametreMetierRow>
|
||||
{
|
||||
[2] = new() { PAPKID = 2, PACode = "C", PAValeur = "1", PALibelle = "M." },
|
||||
[3] = new() { PAPKID = 3, PACode = "R", PAValeur = "1", PALibelle = "Rôle" }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Contact> 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<Contact> 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<Contact> 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<Contact> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Tests unitaires du mapper Batik → canonique des dossiers (table Dossier).
|
||||
/// Aucune base réelle, aucun HFSQL : uniquement des objets en mémoire.
|
||||
/// </summary>
|
||||
public class BatikDossierMapperTests
|
||||
{
|
||||
// ---- 1. Dossier Batik minimal ------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void DossierBatik_Minimal_MapsWithoutExceptionAndKeepsNoFabricatedValue()
|
||||
{
|
||||
var row = new BatikModels.DossierRow { DOPKID = 1 };
|
||||
|
||||
CanonicalMappingResult<Dossier> result = BatikMappers.BatikDossierMapper.Map(row);
|
||||
|
||||
Assert.Equal(DatabaseSchemaVersion.Batik, result.SourceVersion);
|
||||
Assert.NotNull(result.Value);
|
||||
Assert.Null(result.Value.ReferenceMetier);
|
||||
Assert.Null(result.Value.Titre);
|
||||
Assert.Null(result.Value.Type);
|
||||
Assert.Null(result.Value.Statut);
|
||||
Assert.False(result.Value.EstArchive);
|
||||
Assert.Empty(result.Warnings);
|
||||
}
|
||||
|
||||
// ---- 2. Conservation de l'identifiant source (cause du count=0 snapshot) -----
|
||||
|
||||
[Fact]
|
||||
public void DossierBatik_SourceReference_IsPreservedWithNumericId()
|
||||
{
|
||||
// Cause réelle du micro-lot 19 : le mapper ne remplissait pas SourceReference,
|
||||
// donc LoadDossiersAsync skippait tous les dossiers (NumericId null) → count=0.
|
||||
var row = new BatikModels.DossierRow { DOPKID = 1234 };
|
||||
|
||||
CanonicalMappingResult<Dossier> result = BatikMappers.BatikDossierMapper.Map(row);
|
||||
|
||||
Assert.NotNull(result.Value.SourceReference);
|
||||
Assert.Equal(DatabaseSchemaVersion.Batik, result.Value.SourceReference!.Source);
|
||||
Assert.Equal(1234, result.Value.SourceReference!.NumericId);
|
||||
Assert.Equal("1234", result.Value.SourceReference!.PublicIdentifier);
|
||||
Assert.Null(result.Value.SourceReference!.EntityGuid);
|
||||
Assert.Null(result.Value.SourceReference!.PhysicalGuid);
|
||||
}
|
||||
|
||||
// ---- 3. Dossier Batik complet ------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void DossierBatik_Full_MapsAllCanonicalProperties()
|
||||
{
|
||||
var row = new BatikModels.DossierRow
|
||||
{
|
||||
DOPKID = 42,
|
||||
DOIDTypeDossier = 2,
|
||||
DOIDStatut = 3,
|
||||
DOTitre = "Dossier chantier",
|
||||
DONumDossier = "DOS-2024-001",
|
||||
DOIDClient = 7,
|
||||
Archive = false,
|
||||
DODateDebut = new DateOnly(2024, 1, 15)
|
||||
};
|
||||
|
||||
var referenceRows = new Dictionary<long, BatikModels.ParametreMetierRow>
|
||||
{
|
||||
[2] = new() { PAPKID = 2, PACode = "T", PAValeur = "1", PALibelle = "Chantier" },
|
||||
[3] = new() { PAPKID = 3, PACode = "S", PAValeur = "1", PALibelle = "Ouvert" }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Dossier> result = BatikMappers.BatikDossierMapper.Map(row, referenceRows);
|
||||
|
||||
Dossier dossier = result.Value;
|
||||
Assert.Equal("DOS-2024-001", dossier.ReferenceMetier);
|
||||
Assert.Equal("Dossier chantier", dossier.Titre);
|
||||
Assert.Equal("Chantier", dossier.Type!.Label);
|
||||
Assert.Equal(2, dossier.Type!.LegacyId);
|
||||
Assert.Equal("Ouvert", dossier.Statut!.Label);
|
||||
Assert.Equal(3, dossier.Statut!.LegacyId);
|
||||
Assert.Equal(new DateTime(2024, 1, 15), dossier.DateOuverture);
|
||||
Assert.False(dossier.EstArchive);
|
||||
|
||||
Assert.Equal(42, dossier.SourceReference!.NumericId);
|
||||
Assert.Equal(DatabaseSchemaVersion.Batik, dossier.SourceReference!.Source);
|
||||
|
||||
// La référence physique du client lié est conservée en interne.
|
||||
Assert.NotNull(dossier.ClientSourceReference);
|
||||
Assert.Equal(7, dossier.ClientSourceReference!.NumericId);
|
||||
}
|
||||
|
||||
// ---- 4. Aucune exception sur les chaînes vides --------------------------------
|
||||
|
||||
[Fact]
|
||||
public void DossierBatik_EmptyOrNullStrings_NeverThrow()
|
||||
{
|
||||
var row = new BatikModels.DossierRow
|
||||
{
|
||||
DOPKID = 5,
|
||||
DOTitre = "",
|
||||
DONumDossier = " "
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Dossier> result = BatikMappers.BatikDossierMapper.Map(row);
|
||||
|
||||
Assert.NotNull(result.Value);
|
||||
Assert.Null(result.Value.Titre);
|
||||
Assert.Null(result.Value.ReferenceMetier);
|
||||
Assert.Empty(result.Warnings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DossierBatik_DefaultRow_NeverThrows()
|
||||
{
|
||||
var row = new BatikModels.DossierRow();
|
||||
|
||||
CanonicalMappingResult<Dossier> result = BatikMappers.BatikDossierMapper.Map(row);
|
||||
|
||||
Assert.NotNull(result.Value);
|
||||
// SourceReference est toujours rempli (porte DO_PKID) ; avec une ligne par
|
||||
// défaut DO_PKID = 0, NumericId vaut 0 (jamais null en production car la
|
||||
// clé primaire est auto-générée et strictement positive).
|
||||
Assert.NotNull(result.Value.SourceReference);
|
||||
Assert.Equal(0, result.Value.SourceReference!.NumericId);
|
||||
Assert.False(result.Value.EstArchive);
|
||||
}
|
||||
|
||||
|
||||
// ---- 5. Absence de noms physiques HFSQL dans le domaine canonique --------------
|
||||
|
||||
[Fact]
|
||||
public void DossierBatik_CanonicalDomain_DoesNotExposeHfSqlNames()
|
||||
{
|
||||
var row = new BatikModels.DossierRow
|
||||
{
|
||||
DOPKID = 40,
|
||||
DOIDTypeDossier = 2,
|
||||
DOIDStatut = 3,
|
||||
DOTitre = "Dossier",
|
||||
DONumDossier = "DOS-40",
|
||||
DOIDClient = 7,
|
||||
Archive = false
|
||||
};
|
||||
|
||||
var referenceRows = new Dictionary<long, BatikModels.ParametreMetierRow>
|
||||
{
|
||||
[2] = new() { PAPKID = 2, PACode = "T", PAValeur = "1", PALibelle = "Chantier" },
|
||||
[3] = new() { PAPKID = 3, PACode = "S", PAValeur = "1", PALibelle = "Ouvert" }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Dossier> result = BatikMappers.BatikDossierMapper.Map(row, referenceRows);
|
||||
Dossier dossier = result.Value;
|
||||
|
||||
string[] forbidden =
|
||||
[
|
||||
"DO_", "PA_", "PKID", "HfSql", "HFSQL", "GUID"
|
||||
];
|
||||
|
||||
AssertHasNoPhysicalNames(dossier, forbidden);
|
||||
|
||||
if (dossier.SourceReference is not null)
|
||||
{
|
||||
AssertHasNoPhysicalNames(dossier.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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Tests unitaires du mapper Batik → canonique des interventions.
|
||||
/// Aucune base réelle, aucun HFSQL : uniquement des objets en mémoire.
|
||||
/// </summary>
|
||||
public class BatikInterventionMapperTests
|
||||
{
|
||||
// ---- 1. Intervention Batik minimale -------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void InterventionBatik_Minimal_MapsWithoutExceptionAndKeepsNoFabricatedValue()
|
||||
{
|
||||
var row = new BatikModels.InterventionRow { INPKID = 1 };
|
||||
|
||||
CanonicalMappingResult<Intervention> 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<long, BatikModels.ParametreMetierRow>
|
||||
{
|
||||
[2] = new() { PAPKID = 2, PACode = "T", PAValeur = "1", PALibelle = "Maintenance" },
|
||||
[3] = new() { PAPKID = 3, PACode = "S", PAValeur = "1", PALibelle = "En cours" }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Intervention> 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<long, BatikModels.ParametreMetierRow>
|
||||
{
|
||||
[7] = new() { PAPKID = 7, PACode = "T", PAValeur = "2", PALibelle = "Urgence" }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Intervention> 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<Intervention> 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<Intervention> 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<Intervention> 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<Intervention> 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<Intervention> 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<Intervention> 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<long, BatikModels.ParametreMetierRow>
|
||||
{
|
||||
[2] = new() { PAPKID = 2, PACode = "T", PAValeur = "1", PALibelle = "Maintenance" },
|
||||
[3] = new() { PAPKID = 3, PACode = "S", PAValeur = "1", PALibelle = "En cours" }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Intervention> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using Omika.Core.Compatibility;
|
||||
using Omika.Core.Domain.Entities.Planning;
|
||||
using BatikModels = Omika.Data.HfSql.Models.Batik;
|
||||
using BatikMappers = Omika.Data.Mapping.Batik;
|
||||
|
||||
namespace Omika.Tests.Mapping;
|
||||
|
||||
/// <summary>
|
||||
/// Tests unitaires du mapping Batik des valeurs de référentiel d'annulation d'un
|
||||
/// rendez-vous. Aucune base réelle : uniquement des objets en mémoire.
|
||||
///
|
||||
/// En Batik, l'origine et le motif d'annulation sont stockés sous forme
|
||||
/// d'identifiants numériques (<c>PL_IDOrigineAnnul</c> / <c>PL_IDMotifAnnul</c> →
|
||||
/// <c>PA_PKID</c>). Ils doivent être résolus dans le référentiel ParametreMetier,
|
||||
/// sans quoi la fiche du rendez-vous n'affiche aucun libellé.
|
||||
/// </summary>
|
||||
public class BatikRendezVousMapperTests
|
||||
{
|
||||
[Fact]
|
||||
public void Map_AnnulationAvecReferentiel_LibellesOrigineEtMotifResolusParPkId()
|
||||
{
|
||||
var row = new BatikModels.PlanningRDVRow
|
||||
{
|
||||
PLPKID = 42,
|
||||
PLDateDebut = new DateTime(2026, 8, 19, 9, 0, 0),
|
||||
PLDateFin = new DateTime(2026, 8, 19, 10, 0, 0),
|
||||
PLIDOrigineAnnul = 2000UL,
|
||||
PLIDMotifAnnul = 3000UL
|
||||
};
|
||||
|
||||
var references = new Dictionary<long, BatikModels.ParametreMetierRow>
|
||||
{
|
||||
[2000L] = new BatikModels.ParametreMetierRow
|
||||
{
|
||||
PAPKID = 2000L,
|
||||
PACode = "34",
|
||||
PALibelle = "Externe : Client"
|
||||
},
|
||||
[3000L] = new BatikModels.ParametreMetierRow
|
||||
{
|
||||
PAPKID = 3000L,
|
||||
PACode = "35",
|
||||
PALibelle = "Absent"
|
||||
}
|
||||
};
|
||||
|
||||
CanonicalMappingResult<RendezVous> mapped = BatikMappers.BatikRendezVousMapper.Map(row, references);
|
||||
|
||||
Assert.NotNull(mapped.Value.Annulation);
|
||||
Assert.Equal(2000L, mapped.Value.Annulation!.Origine!.LegacyId);
|
||||
Assert.Equal("Externe : Client", mapped.Value.Annulation.Origine!.Label);
|
||||
Assert.Equal(3000L, mapped.Value.Annulation.Motif!.LegacyId);
|
||||
Assert.Equal("Absent", mapped.Value.Annulation.Motif!.Label);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_AnnulationSansReferentiel_ConserveLIdentifiantSansInventerDeLibelle()
|
||||
{
|
||||
var row = new BatikModels.PlanningRDVRow
|
||||
{
|
||||
PLPKID = 43,
|
||||
PLDateDebut = new DateTime(2026, 8, 19, 9, 0, 0),
|
||||
PLDateFin = new DateTime(2026, 8, 19, 10, 0, 0),
|
||||
PLIDMotifAnnul = 3000UL
|
||||
};
|
||||
|
||||
CanonicalMappingResult<RendezVous> mapped = BatikMappers.BatikRendezVousMapper.Map(row, null);
|
||||
|
||||
Assert.NotNull(mapped.Value.Annulation);
|
||||
Assert.Equal(3000L, mapped.Value.Annulation!.Motif!.LegacyId);
|
||||
Assert.True(string.IsNullOrEmpty(mapped.Value.Annulation.Motif!.Label));
|
||||
Assert.Null(mapped.Value.Annulation.Origine);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_SansAnnulation_AucuneAnnulation()
|
||||
{
|
||||
var row = new BatikModels.PlanningRDVRow
|
||||
{
|
||||
PLPKID = 44,
|
||||
PLDateDebut = new DateTime(2026, 8, 19, 9, 0, 0),
|
||||
PLDateFin = new DateTime(2026, 8, 19, 10, 0, 0)
|
||||
};
|
||||
|
||||
CanonicalMappingResult<RendezVous> mapped = BatikMappers.BatikRendezVousMapper.Map(row, null);
|
||||
|
||||
Assert.Null(mapped.Value.Annulation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Tests unitaires du mapper Batik → canonique des sites (table Lieu).
|
||||
/// Aucune base réelle, aucun HFSQL : uniquement des objets en mémoire.
|
||||
/// </summary>
|
||||
public class BatikSiteMapperTests
|
||||
{
|
||||
// ---- 1. Site Batik minimal -------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void SiteBatik_Minimal_MapsWithoutExceptionAndKeepsNoFabricatedValue()
|
||||
{
|
||||
var row = new BatikModels.LieuRow { LIPKID = 1 };
|
||||
|
||||
CanonicalMappingResult<Site> result = BatikMappers.BatikSiteMapper.Map(row);
|
||||
|
||||
Assert.Equal(DatabaseSchemaVersion.Batik, 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(1, result.Value.SourceReference!.NumericId);
|
||||
Assert.Equal(DatabaseSchemaVersion.Batik, result.Value.SourceReference!.Source);
|
||||
Assert.Null(result.Value.SourceReference!.EntityGuid);
|
||||
Assert.Empty(result.Warnings);
|
||||
}
|
||||
|
||||
// ---- 2. Site Batik complet -------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void SiteBatik_Full_MapsAllCanonicalProperties()
|
||||
{
|
||||
var row = new BatikModels.LieuRow
|
||||
{
|
||||
LIPKID = 42,
|
||||
LICodeLieu = "S-001",
|
||||
LIIDType = 2,
|
||||
LINote = "Site principal",
|
||||
Archive = true,
|
||||
LIIDImport = "IMP-B-42"
|
||||
};
|
||||
|
||||
var referenceRows = new Dictionary<long, BatikModels.ParametreMetierRow>
|
||||
{
|
||||
[2] = new() { PAPKID = 2, PACode = "T", PAValeur = "1", PALibelle = "Immeuble" }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Site> result = BatikMappers.BatikSiteMapper.Map(row, referenceRows);
|
||||
|
||||
Site site = result.Value;
|
||||
Assert.Equal("S-001", site.Code);
|
||||
Assert.Equal("Immeuble", site.Type!.Label);
|
||||
Assert.Equal(2, site.Type!.LegacyId);
|
||||
Assert.Equal("T", site.Type!.Code);
|
||||
Assert.Equal("Site principal", site.InformationsComplementaires);
|
||||
Assert.True(site.EstArchive);
|
||||
|
||||
Assert.Equal(42, site.SourceReference!.NumericId);
|
||||
Assert.Equal("IMP-B-42", site.SourceReference!.ImportId);
|
||||
Assert.Equal(DatabaseSchemaVersion.Batik, site.SourceReference!.Source);
|
||||
}
|
||||
|
||||
// ---- 3. Conservation de l'identifiant source ----------------------------------
|
||||
|
||||
[Fact]
|
||||
public void SiteBatik_SourceIdentifier_IsPreservedWithoutFabricatedGuid()
|
||||
{
|
||||
var row = new BatikModels.LieuRow { LIPKID = 1234, LIIDImport = "IMP-B" };
|
||||
|
||||
CanonicalMappingResult<Site> result = BatikMappers.BatikSiteMapper.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);
|
||||
}
|
||||
|
||||
// ---- 4. Type de site ----------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void SiteBatik_Type_IsResolvedFromLegacyId()
|
||||
{
|
||||
var row = new BatikModels.LieuRow { LIPKID = 5, LIIDType = 7 };
|
||||
|
||||
var referenceRows = new Dictionary<long, BatikModels.ParametreMetierRow>
|
||||
{
|
||||
[7] = new() { PAPKID = 7, PACode = "T", PAValeur = "2", PALibelle = "Maison Indiv." }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Site> result = BatikMappers.BatikSiteMapper.Map(row, referenceRows);
|
||||
|
||||
Assert.NotNull(result.Value.Type);
|
||||
Assert.Equal("Maison Indiv.", result.Value.Type!.Label);
|
||||
Assert.Equal(7, result.Value.Type!.LegacyId);
|
||||
Assert.Empty(result.Warnings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SiteBatik_UnresolvedType_KeepsLegacyIdAndWarns()
|
||||
{
|
||||
var row = new BatikModels.LieuRow { LIPKID = 6, LIIDType = 77 };
|
||||
|
||||
CanonicalMappingResult<Site> result = BatikMappers.BatikSiteMapper.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("LI_IDType", StringComparison.Ordinal)
|
||||
&& w.Contains(MappingWarningCodes.UnresolvedReference, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SiteBatik_ZeroType_ProducesNoReference()
|
||||
{
|
||||
var row = new BatikModels.LieuRow { LIPKID = 8, LIIDType = 0 };
|
||||
|
||||
CanonicalMappingResult<Site> result = BatikMappers.BatikSiteMapper.Map(row);
|
||||
|
||||
Assert.Null(result.Value.Type);
|
||||
Assert.Empty(result.Warnings);
|
||||
}
|
||||
|
||||
// ---- 5. Informations d'accès --------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void SiteBatik_AccessInformation_CodeAccesBadgeInterphone_Mapped()
|
||||
{
|
||||
var row = new BatikModels.LieuRow
|
||||
{
|
||||
LIPKID = 9,
|
||||
LICodeAcces = "Code 1234",
|
||||
LIBadge = true,
|
||||
LIInterphone = true
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Site> result = BatikMappers.BatikSiteMapper.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");
|
||||
}
|
||||
|
||||
// ---- 6. Blocage sans équivalent métier ----------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void SiteBatik_Blocage_IsReportedAsIgnored()
|
||||
{
|
||||
var row = new BatikModels.LieuRow
|
||||
{
|
||||
LIPKID = 10,
|
||||
LIBlocage = true,
|
||||
LIRaisonBlocage = "Travaux en cours"
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Site> result = BatikMappers.BatikSiteMapper.Map(row);
|
||||
|
||||
Assert.Contains(result.IgnoredFields, f => f == "LI_Blocage");
|
||||
Assert.Contains(result.IgnoredFields, f => f == "LI_RaisonBlocage");
|
||||
}
|
||||
|
||||
// ---- 7. Adresse déjà chargée en mémoire ---------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void SiteBatik_Adresse_IsMappedFromInMemoryDictionary()
|
||||
{
|
||||
var row = new BatikModels.LieuRow { LIPKID = 11, LIIDAdresse = 20 };
|
||||
|
||||
var adresses = new Dictionary<long, BatikModels.AdresseRow>
|
||||
{
|
||||
[20] = new() { ADPKID = 20, ADEtiquette = "Siège", ADLigne1 = "1 place de la Comédie", ADVille = "Lyon", ADCodePostal = "69001" }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Site> result = BatikMappers.BatikSiteMapper.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 SiteBatik_NoAdresse_WhenNothingProvided()
|
||||
{
|
||||
var row = new BatikModels.LieuRow { LIPKID = 12, LIIDAdresse = 0 };
|
||||
|
||||
CanonicalMappingResult<Site> result = BatikMappers.BatikSiteMapper.Map(row);
|
||||
|
||||
Assert.Null(result.Value.Adresse);
|
||||
Assert.Empty(result.Warnings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SiteBatik_UnresolvedAdresse_WarnsAndProducesNull()
|
||||
{
|
||||
var row = new BatikModels.LieuRow { LIPKID = 13, LIIDAdresse = 99 };
|
||||
|
||||
CanonicalMappingResult<Site> result = BatikMappers.BatikSiteMapper.Map(row);
|
||||
|
||||
Assert.Null(result.Value.Adresse);
|
||||
Assert.Contains(result.Warnings, w => w.Contains(MappingWarningCodes.UnresolvedReference, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
// ---- 8. Relations différées ----------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void SiteBatik_Relations_AreLeftForLaterAssembly()
|
||||
{
|
||||
var row = new BatikModels.LieuRow { LIPKID = 14, LIIDClientPrincipal = 30 };
|
||||
|
||||
CanonicalMappingResult<Site> result = BatikMappers.BatikSiteMapper.Map(row);
|
||||
|
||||
Assert.Null(result.Value.ClientId);
|
||||
Assert.Empty(result.Value.ContactIds);
|
||||
Assert.Contains(result.IgnoredFields, f => f == "LI_IDClientPrincipal");
|
||||
}
|
||||
|
||||
// ---- 9. Aucune exception sur les chaînes vides ---------------------------------
|
||||
|
||||
[Fact]
|
||||
public void SiteBatik_EmptyOrNullStrings_NeverThrow()
|
||||
{
|
||||
var row = new BatikModels.LieuRow
|
||||
{
|
||||
LICodeLieu = "",
|
||||
LINote = " ",
|
||||
LIIDImport = "",
|
||||
LICodeAcces = " ",
|
||||
LIRaisonBlocage = "",
|
||||
LIRepertoireDocs = " "
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Site> result = BatikMappers.BatikSiteMapper.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 SiteBatik_DefaultRow_NeverThrows()
|
||||
{
|
||||
var row = new BatikModels.LieuRow();
|
||||
|
||||
CanonicalMappingResult<Site> result = BatikMappers.BatikSiteMapper.Map(row);
|
||||
|
||||
Assert.NotNull(result.Value);
|
||||
Assert.Null(result.Value.SourceReference!.NumericId);
|
||||
Assert.False(result.Value.EstArchive);
|
||||
}
|
||||
|
||||
// ---- 10. Champs ignorés et warnings attendus -----------------------------------
|
||||
|
||||
[Fact]
|
||||
public void SiteBatik_FieldsWithoutCanonicalEquivalent_AreReportedAsIgnored()
|
||||
{
|
||||
var row = new BatikModels.LieuRow
|
||||
{
|
||||
LIPKID = 15,
|
||||
LIRepertoireDocs = "docs/site",
|
||||
DateArchivage = new DateTime(2024, 6, 1),
|
||||
ModifDate = new DateTime(2024, 1, 1),
|
||||
CreationDate = new DateTime(2023, 1, 1)
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Site> result = BatikMappers.BatikSiteMapper.Map(row);
|
||||
|
||||
Assert.Contains(result.IgnoredFields, f => f == "LI_RepertoireDocs");
|
||||
Assert.Contains(result.IgnoredFields, f => f == "DateArchivage");
|
||||
Assert.Equal(new DateTime(2024, 1, 1), result.Value.DateModification);
|
||||
Assert.Contains(result.IgnoredFields, f => f == "CreationDate");
|
||||
}
|
||||
|
||||
// ---- 11. Absence de noms physiques HFSQL dans le domaine canonique -------------
|
||||
|
||||
[Fact]
|
||||
public void SiteBatik_CanonicalDomain_DoesNotExposeHfSqlNames()
|
||||
{
|
||||
var row = new BatikModels.LieuRow
|
||||
{
|
||||
LIPKID = 40,
|
||||
LICodeLieu = "S-040",
|
||||
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<long, BatikModels.ParametreMetierRow>
|
||||
{
|
||||
[2] = new() { PAPKID = 2, PACode = "T", PAValeur = "1", PALibelle = "Immeuble" }
|
||||
};
|
||||
|
||||
var adresses = new Dictionary<long, BatikModels.AdresseRow>
|
||||
{
|
||||
[20] = new() { ADPKID = 20, ADLigne1 = "1 rue de la Paix", ADVille = "Lyon", ADCodePostal = "69001" }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Site> result = BatikMappers.BatikSiteMapper.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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
using Omika.Core.Compatibility;
|
||||
using Omika.Core.Domain.Entities.Clients;
|
||||
using Omika.Data.Mapping.Common;
|
||||
|
||||
namespace Omika.Tests.Mapping;
|
||||
|
||||
/// <summary>
|
||||
/// Tests unitaires de l'assembleur d'agrégat <see cref="ClientAggregateAssembler"/>.
|
||||
/// Aucune base réelle, aucun HFSQL : uniquement des objets canoniques en mémoire.
|
||||
/// </summary>
|
||||
public class ClientAggregateAssemblerTests
|
||||
{
|
||||
// ---- 1. Client seul avec collections vides -------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_ClientAlone_KeepsCollectionsEmpty()
|
||||
{
|
||||
var client = new Client { Nom = "Ville de Lyon" };
|
||||
|
||||
Client result = ClientAggregateAssembler.Assemble(client);
|
||||
|
||||
Assert.Same(client, result);
|
||||
Assert.Empty(result.Adresses);
|
||||
Assert.Empty(result.Contacts);
|
||||
Assert.Empty(result.Sites);
|
||||
Assert.Null(result.AdressePrincipale);
|
||||
Assert.Null(result.ContactPrincipal);
|
||||
}
|
||||
|
||||
// ---- 2. Assemblage d'une adresse ------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_SingleAdresse_IsAddedToCollection()
|
||||
{
|
||||
var client = new Client();
|
||||
var adresse = new Adresse { Etiquette = "Mairie" };
|
||||
|
||||
ClientAggregateAssembler.Assemble(client, adresses: [adresse]);
|
||||
|
||||
Assert.Single(client.Adresses);
|
||||
Assert.Same(adresse, client.Adresses[0]);
|
||||
}
|
||||
|
||||
// ---- 3. Plusieurs adresses ------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_MultipleAdresses_AreAllAdded()
|
||||
{
|
||||
var client = new Client();
|
||||
var a1 = new Adresse { Etiquette = "Mairie" };
|
||||
var a2 = new Adresse { Etiquette = "Agence" };
|
||||
|
||||
ClientAggregateAssembler.Assemble(client, adresses: [a1, a2]);
|
||||
|
||||
Assert.Equal(2, client.Adresses.Count);
|
||||
Assert.Contains(a1, client.Adresses);
|
||||
Assert.Contains(a2, client.Adresses);
|
||||
}
|
||||
|
||||
// ---- 4. Assemblage d'un contact -------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_SingleContact_IsAddedToCollection()
|
||||
{
|
||||
var client = new Client();
|
||||
var contact = new Contact { Nom = "Dupont" };
|
||||
|
||||
ClientAggregateAssembler.Assemble(client, contacts: [contact]);
|
||||
|
||||
Assert.Single(client.Contacts);
|
||||
Assert.Same(contact, client.Contacts[0]);
|
||||
}
|
||||
|
||||
// ---- 5. Plusieurs contacts ------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_MultipleContacts_AreAllAdded()
|
||||
{
|
||||
var client = new Client();
|
||||
var c1 = new Contact { Nom = "Dupont" };
|
||||
var c2 = new Contact { Nom = "Martin" };
|
||||
|
||||
ClientAggregateAssembler.Assemble(client, contacts: [c1, c2]);
|
||||
|
||||
Assert.Equal(2, client.Contacts.Count);
|
||||
Assert.Contains(c1, client.Contacts);
|
||||
Assert.Contains(c2, client.Contacts);
|
||||
}
|
||||
|
||||
// ---- 6. Assemblage d'un site ----------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_SingleSite_IsAddedToCollection()
|
||||
{
|
||||
var client = new Client();
|
||||
var site = new Site { Nom = "Hôtel de ville" };
|
||||
|
||||
ClientAggregateAssembler.Assemble(client, sites: [site]);
|
||||
|
||||
Assert.Single(client.Sites);
|
||||
Assert.Same(site, client.Sites[0]);
|
||||
}
|
||||
|
||||
// ---- 7. Plusieurs sites ---------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_MultipleSites_AreAllAdded()
|
||||
{
|
||||
var client = new Client();
|
||||
var s1 = new Site { Nom = "Hôtel de ville" };
|
||||
var s2 = new Site { Nom = "Agence" };
|
||||
|
||||
ClientAggregateAssembler.Assemble(client, sites: [s1, s2]);
|
||||
|
||||
Assert.Equal(2, client.Sites.Count);
|
||||
Assert.Contains(s1, client.Sites);
|
||||
Assert.Contains(s2, client.Sites);
|
||||
}
|
||||
|
||||
// ---- 8. Toutes les collections ensemble -----------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_AllCollections_AreAssembled()
|
||||
{
|
||||
var client = new Client();
|
||||
var adresse = new Adresse { Etiquette = "Mairie" };
|
||||
var contact = new Contact { Nom = "Dupont" };
|
||||
var site = new Site { Nom = "Hôtel de ville" };
|
||||
|
||||
ClientAggregateAssembler.Assemble(client, [adresse], [contact], [site]);
|
||||
|
||||
Assert.Single(client.Adresses);
|
||||
Assert.Single(client.Contacts);
|
||||
Assert.Single(client.Sites);
|
||||
}
|
||||
|
||||
// ---- 9. Détection de l'adresse principale ---------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_AdressePrincipale_AlreadyMapped_IsNotDuplicatedInCollection()
|
||||
{
|
||||
var principale = new Adresse
|
||||
{
|
||||
Etiquette = "Mairie",
|
||||
EstPrincipale = true,
|
||||
SourceReference = new LegacyEntityReference(DatabaseSchemaVersion.Omika, NumericId: 10)
|
||||
};
|
||||
var client = new Client { AdressePrincipale = principale };
|
||||
var secondaire = new Adresse { Etiquette = "Agence" };
|
||||
|
||||
// La collection contient l'adresse principale (même identité) et une secondaire.
|
||||
ClientAggregateAssembler.Assemble(client, adresses: [principale, secondaire]);
|
||||
|
||||
Assert.Same(principale, client.AdressePrincipale);
|
||||
Assert.Single(client.Adresses);
|
||||
Assert.Same(secondaire, client.Adresses[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Assemble_AdressePrincipale_NotMapped_IsDetectedFromEstPrincipale()
|
||||
{
|
||||
var client = new Client();
|
||||
var principale = new Adresse { Etiquette = "Mairie", EstPrincipale = true };
|
||||
var secondaire = new Adresse { Etiquette = "Agence", EstPrincipale = false };
|
||||
|
||||
ClientAggregateAssembler.Assemble(client, adresses: [secondaire, principale]);
|
||||
|
||||
Assert.Same(principale, client.AdressePrincipale);
|
||||
Assert.Equal(2, client.Adresses.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Assemble_NoAdressePrincipale_WhenNoneIsMarked()
|
||||
{
|
||||
var client = new Client();
|
||||
var adresse = new Adresse { Etiquette = "Agence", EstPrincipale = false };
|
||||
|
||||
ClientAggregateAssembler.Assemble(client, adresses: [adresse]);
|
||||
|
||||
Assert.Null(client.AdressePrincipale);
|
||||
Assert.Single(client.Adresses);
|
||||
}
|
||||
|
||||
// ---- 10. Contact principal ------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_ContactPrincipal_IsNotInvented()
|
||||
{
|
||||
var client = new Client();
|
||||
var contact = new Contact { Nom = "Dupont" };
|
||||
|
||||
ClientAggregateAssembler.Assemble(client, contacts: [contact]);
|
||||
|
||||
Assert.Single(client.Contacts);
|
||||
Assert.Null(client.ContactPrincipal);
|
||||
}
|
||||
|
||||
// ---- 11. Site principal ---------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_SitePrincipal_IsNotInvented()
|
||||
{
|
||||
var client = new Client();
|
||||
var site = new Site { Nom = "Hôtel de ville" };
|
||||
|
||||
ClientAggregateAssembler.Assemble(client, sites: [site]);
|
||||
|
||||
Assert.Single(client.Sites);
|
||||
// Aucune propriété SitePrincipal n'existe dans Client : la notion reste non assemblée.
|
||||
Assert.DoesNotContain(typeof(Client).GetProperties(), p => p.Name == "SitePrincipal");
|
||||
}
|
||||
|
||||
// ---- 12. Doublon exact d'adresse éliminé ----------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_DuplicateAdresse_IsEliminated()
|
||||
{
|
||||
var client = new Client();
|
||||
var adresse = new Adresse
|
||||
{
|
||||
Etiquette = "Mairie",
|
||||
SourceReference = new LegacyEntityReference(DatabaseSchemaVersion.Omika, NumericId: 10)
|
||||
};
|
||||
|
||||
ClientAggregateAssembler.Assemble(client, adresses: [adresse, adresse]);
|
||||
|
||||
Assert.Single(client.Adresses);
|
||||
}
|
||||
|
||||
// ---- 13. Doublon exact de contact éliminé ---------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_DuplicateContact_IsEliminated()
|
||||
{
|
||||
var client = new Client();
|
||||
var contact = new Contact
|
||||
{
|
||||
Nom = "Dupont",
|
||||
SourceReference = new LegacyEntityReference(DatabaseSchemaVersion.Omika, NumericId: 10)
|
||||
};
|
||||
|
||||
ClientAggregateAssembler.Assemble(client, contacts: [contact, contact]);
|
||||
|
||||
Assert.Single(client.Contacts);
|
||||
}
|
||||
|
||||
// ---- 14. Doublon exact de site éliminé ------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_DuplicateSite_IsEliminated()
|
||||
{
|
||||
var client = new Client();
|
||||
var site = new Site
|
||||
{
|
||||
Nom = "Hôtel de ville",
|
||||
SourceReference = new LegacyEntityReference(DatabaseSchemaVersion.Omika, NumericId: 10)
|
||||
};
|
||||
|
||||
ClientAggregateAssembler.Assemble(client, sites: [site, site]);
|
||||
|
||||
Assert.Single(client.Sites);
|
||||
}
|
||||
|
||||
// ---- 15. Deux homonymes restent distincts ---------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_Homonyms_RemainDistinct()
|
||||
{
|
||||
var client = new Client();
|
||||
var c1 = new Contact
|
||||
{
|
||||
Nom = "Dupont",
|
||||
SourceReference = new LegacyEntityReference(DatabaseSchemaVersion.Omika, NumericId: 1)
|
||||
};
|
||||
var c2 = new Contact
|
||||
{
|
||||
Nom = "Dupont",
|
||||
SourceReference = new LegacyEntityReference(DatabaseSchemaVersion.Omika, NumericId: 2)
|
||||
};
|
||||
|
||||
ClientAggregateAssembler.Assemble(client, contacts: [c1, c2]);
|
||||
|
||||
Assert.Equal(2, client.Contacts.Count);
|
||||
}
|
||||
|
||||
// ---- 16. Même NumericId mais sources différentes restent distinctes -------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_SameNumericId_DifferentSources_RemainDistinct()
|
||||
{
|
||||
var client = new Client();
|
||||
var batik = new Adresse
|
||||
{
|
||||
Etiquette = "Mairie",
|
||||
SourceReference = new LegacyEntityReference(DatabaseSchemaVersion.Batik, NumericId: 10)
|
||||
};
|
||||
var omika = new Adresse
|
||||
{
|
||||
Etiquette = "Mairie",
|
||||
SourceReference = new LegacyEntityReference(DatabaseSchemaVersion.Omika, NumericId: 10)
|
||||
};
|
||||
|
||||
ClientAggregateAssembler.Assemble(client, adresses: [batik, omika]);
|
||||
|
||||
Assert.Equal(2, client.Adresses.Count);
|
||||
}
|
||||
|
||||
// ---- 17. GUID identique reconnu comme même entité -------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_SameCanonicalGuid_IsRecognizedAsSameEntity()
|
||||
{
|
||||
var client = new Client();
|
||||
var guid = Guid.NewGuid();
|
||||
var a1 = new Adresse { Id = guid, Etiquette = "Mairie" };
|
||||
var a2 = new Adresse { Id = guid, Etiquette = "Mairie" };
|
||||
|
||||
ClientAggregateAssembler.Assemble(client, adresses: [a1, a2]);
|
||||
|
||||
Assert.Single(client.Adresses);
|
||||
}
|
||||
|
||||
// ---- 18. Collections nulles ou vides sans exception -----------------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_NullCollections_DoNotThrow()
|
||||
{
|
||||
var client = new Client { Nom = "Ville" };
|
||||
|
||||
Client result = ClientAggregateAssembler.Assemble(client, null, null, null);
|
||||
|
||||
Assert.Same(client, result);
|
||||
Assert.Empty(result.Adresses);
|
||||
Assert.Empty(result.Contacts);
|
||||
Assert.Empty(result.Sites);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Assemble_EmptyCollections_DoNotThrow()
|
||||
{
|
||||
var client = new Client { Nom = "Ville" };
|
||||
|
||||
ClientAggregateAssembler.Assemble(client, [], [], []);
|
||||
|
||||
Assert.Empty(client.Adresses);
|
||||
Assert.Empty(client.Contacts);
|
||||
Assert.Empty(client.Sites);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Assemble_NullEntries_AreIgnored()
|
||||
{
|
||||
var client = new Client();
|
||||
Adresse? adresse = null;
|
||||
Contact? contact = null;
|
||||
Site? site = null;
|
||||
|
||||
// Les éléments null sont ignorés par l'assembleur.
|
||||
ClientAggregateAssembler.Assemble(client, new[] { adresse! }, new[] { contact! }, new[] { site! });
|
||||
|
||||
Assert.Empty(client.Adresses);
|
||||
Assert.Empty(client.Contacts);
|
||||
Assert.Empty(client.Sites);
|
||||
}
|
||||
|
||||
// ---- 19. Assemblage déterministe ------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_IsDeterministic_AndIdempotent()
|
||||
{
|
||||
var client = new Client();
|
||||
var adresse = new Adresse
|
||||
{
|
||||
Etiquette = "Mairie",
|
||||
SourceReference = new LegacyEntityReference(DatabaseSchemaVersion.Omika, NumericId: 10)
|
||||
};
|
||||
var contact = new Contact
|
||||
{
|
||||
Nom = "Dupont",
|
||||
SourceReference = new LegacyEntityReference(DatabaseSchemaVersion.Omika, NumericId: 20)
|
||||
};
|
||||
var site = new Site
|
||||
{
|
||||
Nom = "Hôtel de ville",
|
||||
SourceReference = new LegacyEntityReference(DatabaseSchemaVersion.Omika, NumericId: 30)
|
||||
};
|
||||
|
||||
// Premier assemblage.
|
||||
ClientAggregateAssembler.Assemble(client, [adresse], [contact], [site]);
|
||||
Assert.Single(client.Adresses);
|
||||
Assert.Single(client.Contacts);
|
||||
Assert.Single(client.Sites);
|
||||
|
||||
// Second assemblage avec les mêmes objets : aucun doublon créé.
|
||||
ClientAggregateAssembler.Assemble(client, [adresse], [contact], [site]);
|
||||
Assert.Single(client.Adresses);
|
||||
Assert.Single(client.Contacts);
|
||||
Assert.Single(client.Sites);
|
||||
}
|
||||
|
||||
// ---- 20. Aucun type physique HFSQL dans la signature publique -------------------
|
||||
|
||||
[Fact]
|
||||
public void Assemble_PublicSignature_DoesNotExposePhysicalTypes()
|
||||
{
|
||||
var method = typeof(ClientAggregateAssembler).GetMethod(nameof(ClientAggregateAssembler.Assemble))!;
|
||||
|
||||
foreach (var parameter in method.GetParameters())
|
||||
{
|
||||
Assert.DoesNotContain("HfSql", parameter.ParameterType.FullName, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("Row", parameter.ParameterType.Name, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
Assert.Equal(typeof(Client), method.ReturnType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Omika.Core.Compatibility;
|
||||
using Omika.Core.Contracts.Documents;
|
||||
using Omika.Core.Domain.Entities.Documents;
|
||||
using Omika.Core.Mapping;
|
||||
using Xunit;
|
||||
|
||||
namespace Omika.Tests.Mapping;
|
||||
|
||||
public class ModeleDocumentContractMapperTests
|
||||
{
|
||||
[Fact]
|
||||
public void ModeleMailContractMapper_UsesSourceReferenceAsPublicId()
|
||||
{
|
||||
var modele = new ModeleMail
|
||||
{
|
||||
Id = Guid.Empty,
|
||||
Code = "REL1",
|
||||
Nom = "Relance",
|
||||
Sujet = "Bonjour [[NomClient]]",
|
||||
Corps = "Corps",
|
||||
EnvoiMobile = true
|
||||
};
|
||||
modele.SourceReference = LegacyEntityReference.FromLegacyId(42);
|
||||
|
||||
ModeleMailDto dto = ModeleMailContractMapper.ToDto(modele);
|
||||
|
||||
Assert.Equal("42", dto.Id);
|
||||
Assert.Equal("REL1", dto.Code);
|
||||
Assert.Equal("Bonjour [[NomClient]]", dto.Sujet);
|
||||
Assert.True(dto.EnvoiMobile);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModeleRapportContractMapper_UsesSourceReferenceAsPublicId()
|
||||
{
|
||||
var modele = new ModeleRapport
|
||||
{
|
||||
Id = Guid.Empty,
|
||||
Nom = "Compte rendu",
|
||||
Modele = "Contenu [[Site]]",
|
||||
EnvoiMobile = true
|
||||
};
|
||||
modele.SourceReference = new LegacyEntityReference(DatabaseSchemaVersion.Omika, NumericId: 7);
|
||||
|
||||
ModeleRapportDto dto = ModeleRapportContractMapper.ToDto(modele);
|
||||
|
||||
Assert.Equal("7", dto.Id);
|
||||
Assert.Equal("Compte rendu", dto.Nom);
|
||||
Assert.True(dto.EnvoiMobile);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
using Omika.Core.Compatibility;
|
||||
using Omika.Core.Domain.Entities.Organisation;
|
||||
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 collaborateurs (table Collaborateur).
|
||||
/// Aucune base réelle, aucun HFSQL : uniquement des objets en mémoire.
|
||||
/// </summary>
|
||||
public class OmikaCollaborateurMapperTests
|
||||
{
|
||||
[Fact]
|
||||
public void CollaborateurOmika_Minimal_MapsWithoutExceptionAndKeepsNoFabricatedValue()
|
||||
{
|
||||
var row = new OmikaModels.CollaborateurRow { COPKID = 1 };
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = OmikaMappers.OmikaCollaborateurMapper.Map(row);
|
||||
|
||||
Assert.Equal(DatabaseSchemaVersion.Omika, result.SourceVersion);
|
||||
Assert.NotNull(result.Value);
|
||||
Assert.Null(result.Value.Nom);
|
||||
Assert.Null(result.Value.Prenom);
|
||||
Assert.Null(result.Value.Civilite);
|
||||
Assert.Null(result.Value.Statut);
|
||||
Assert.Null(result.Value.Coordonnees);
|
||||
Assert.False(result.Value.Actif);
|
||||
Assert.False(result.Value.EstArchive);
|
||||
Assert.NotNull(result.Value.SourceReference);
|
||||
Assert.Equal(1, result.Value.SourceReference!.NumericId);
|
||||
Assert.Equal(DatabaseSchemaVersion.Omika, result.Value.SourceReference!.Source);
|
||||
Assert.Null(result.Value.SourceReference!.EntityGuid);
|
||||
Assert.Empty(result.Warnings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurOmika_Full_MapsAllCanonicalProperties()
|
||||
{
|
||||
var row = new OmikaModels.CollaborateurRow
|
||||
{
|
||||
COPKID = 42,
|
||||
COCleCivilite = "M",
|
||||
CONom = "Dupont",
|
||||
COPrenom = "Jean",
|
||||
COTrigramme = "JD",
|
||||
COTelFixe = "0472000000",
|
||||
COTelMobile = "0600000000",
|
||||
COEmail = "jean.dupont@ville-lyon.fr",
|
||||
COCleStatut = "ACTIF",
|
||||
COActif = true,
|
||||
GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
|
||||
};
|
||||
|
||||
var referenceRows = new Dictionary<string, OmikaModels.ParametreMetierRow>
|
||||
{
|
||||
["M"] = new() { PACLE = "M", PALibelle = "M.", PAPKID = 2 },
|
||||
["ACTIF"] = new() { PACLE = "ACTIF", PALibelle = "Actif", PAPKID = 3 }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = OmikaMappers.OmikaCollaborateurMapper.Map(row, referenceRows);
|
||||
|
||||
Collaborateur collaborateur = result.Value;
|
||||
Assert.Equal("M.", collaborateur.Civilite!.Label);
|
||||
Assert.Equal("M", collaborateur.Civilite!.Key);
|
||||
Assert.Equal("Actif", collaborateur.Statut!.Label);
|
||||
Assert.Equal("ACTIF", collaborateur.Statut!.Key);
|
||||
Assert.Equal("Dupont", collaborateur.Nom);
|
||||
Assert.Equal("Jean", collaborateur.Prenom);
|
||||
Assert.Equal("JD", collaborateur.Code);
|
||||
|
||||
Assert.Equal("jean.dupont@ville-lyon.fr", collaborateur.Coordonnees!.Email);
|
||||
Assert.Equal("0472000000", collaborateur.Coordonnees!.Phone);
|
||||
Assert.Equal("0600000000", collaborateur.Coordonnees!.Mobile);
|
||||
|
||||
Assert.True(collaborateur.Actif);
|
||||
|
||||
Assert.Equal(42, collaborateur.SourceReference!.NumericId);
|
||||
Assert.Equal(DatabaseSchemaVersion.Omika, collaborateur.SourceReference!.Source);
|
||||
Assert.Equal(Guid.Parse("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"), collaborateur.SourceReference!.EntityGuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurOmika_EmptyCoordinates_ProducesNullCoordonnees()
|
||||
{
|
||||
var row = new OmikaModels.CollaborateurRow
|
||||
{
|
||||
COPKID = 5,
|
||||
CONom = "Martin",
|
||||
COEmail = " ",
|
||||
COTelFixe = "",
|
||||
COTelMobile = null
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = OmikaMappers.OmikaCollaborateurMapper.Map(row);
|
||||
|
||||
Assert.Null(result.Value.Coordonnees);
|
||||
Assert.Null(result.Value.Prenom);
|
||||
Assert.Equal("Martin", result.Value.Nom);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurOmika_PartialCoordinates_KeepsOnlyProvidedValues()
|
||||
{
|
||||
var row = new OmikaModels.CollaborateurRow
|
||||
{
|
||||
COPKID = 6,
|
||||
COEmail = "m@exemple.fr",
|
||||
COTelFixe = null,
|
||||
COTelMobile = "0600000000"
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = OmikaMappers.OmikaCollaborateurMapper.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);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurOmika_InvalidGuid_ProducesNullEntityGuidAndWarns()
|
||||
{
|
||||
var row = new OmikaModels.CollaborateurRow
|
||||
{
|
||||
COPKID = 7,
|
||||
GUID = "not-a-guid"
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = OmikaMappers.OmikaCollaborateurMapper.Map(row);
|
||||
|
||||
Assert.Null(result.Value.SourceReference!.EntityGuid);
|
||||
Assert.Equal("not-a-guid", result.Value.SourceReference!.PhysicalGuid);
|
||||
Assert.Contains(result.Warnings, w => w.Contains(MappingWarningCodes.InvalidPhysicalGuid, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurOmika_NoGuid_ProducesNullEntityGuid()
|
||||
{
|
||||
var row = new OmikaModels.CollaborateurRow { COPKID = 8 };
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = OmikaMappers.OmikaCollaborateurMapper.Map(row);
|
||||
|
||||
Assert.Null(result.Value.SourceReference!.EntityGuid);
|
||||
Assert.Null(result.Value.SourceReference!.PhysicalGuid);
|
||||
Assert.Empty(result.Warnings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurOmika_EmptyOrNullStrings_NeverThrow()
|
||||
{
|
||||
var row = new OmikaModels.CollaborateurRow
|
||||
{
|
||||
CONom = "",
|
||||
COPrenom = " ",
|
||||
COEmail = "",
|
||||
COTelFixe = null,
|
||||
COTelMobile = " ",
|
||||
COTrigramme = "",
|
||||
COCleCivilite = "",
|
||||
COCleStatut = null
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = OmikaMappers.OmikaCollaborateurMapper.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.Civilite);
|
||||
Assert.Null(result.Value.Statut);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurOmika_DefaultRow_NeverThrows()
|
||||
{
|
||||
var row = new OmikaModels.CollaborateurRow();
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = OmikaMappers.OmikaCollaborateurMapper.Map(row);
|
||||
|
||||
Assert.NotNull(result.Value);
|
||||
Assert.Null(result.Value.SourceReference!.NumericId);
|
||||
Assert.False(result.Value.Actif);
|
||||
Assert.False(result.Value.EstArchive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurOmika_CanonicalDomain_DoesNotExposeHfSqlNames()
|
||||
{
|
||||
var row = new OmikaModels.CollaborateurRow
|
||||
{
|
||||
COPKID = 40,
|
||||
COCleCivilite = "M",
|
||||
CONom = "Dupont",
|
||||
COPrenom = "Jean",
|
||||
COTelFixe = "01",
|
||||
COTelMobile = "06",
|
||||
COEmail = "j@exemple.fr",
|
||||
COActif = true,
|
||||
GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
|
||||
};
|
||||
|
||||
var referenceRows = new Dictionary<string, OmikaModels.ParametreMetierRow>
|
||||
{
|
||||
["M"] = new() { PACLE = "M", PALibelle = "M.", PAPKID = 2 }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = OmikaMappers.OmikaCollaborateurMapper.Map(row, referenceRows);
|
||||
Collaborateur collaborateur = result.Value;
|
||||
|
||||
string[] forbidden =
|
||||
[
|
||||
"CO_", "CL_", "PA_", "PKID", "HfSql", "HFSQL", "GUID"
|
||||
];
|
||||
|
||||
AssertHasNoPhysicalNames(collaborateur, forbidden);
|
||||
|
||||
if (collaborateur.Coordonnees is not null)
|
||||
{
|
||||
AssertHasNoPhysicalNames(collaborateur.Coordonnees, forbidden);
|
||||
}
|
||||
|
||||
if (collaborateur.SourceReference is not null)
|
||||
{
|
||||
AssertHasNoPhysicalNames(collaborateur.SourceReference, forbidden);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurOmika_UnresolvedCle_KeepsKeyAndWarns()
|
||||
{
|
||||
var row = new OmikaModels.CollaborateurRow
|
||||
{
|
||||
COPKID = 9,
|
||||
COCleCivilite = "INCONNU",
|
||||
COCleStatut = "AUTRE"
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = OmikaMappers.OmikaCollaborateurMapper.Map(row);
|
||||
|
||||
Assert.NotNull(result.Value.Civilite);
|
||||
Assert.Equal("INCONNU", result.Value.Civilite!.Key);
|
||||
Assert.Equal(string.Empty, result.Value.Civilite!.Label);
|
||||
|
||||
Assert.NotNull(result.Value.Statut);
|
||||
Assert.Equal("AUTRE", result.Value.Statut!.Key);
|
||||
|
||||
Assert.Contains(result.Warnings, w => w.Contains("CO_CleCivilite", StringComparison.Ordinal)
|
||||
&& w.Contains(MappingWarningCodes.UnresolvedReference, StringComparison.Ordinal));
|
||||
Assert.Contains(result.Warnings, w => w.Contains("CO_CleStatut", StringComparison.Ordinal)
|
||||
&& w.Contains(MappingWarningCodes.UnresolvedReference, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurOmika_NoCleButLegacyId_FallsBackToLegacyIdAndWarns()
|
||||
{
|
||||
var row = new OmikaModels.CollaborateurRow
|
||||
{
|
||||
COPKID = 10,
|
||||
COIDCivilite = 77,
|
||||
COIDStatut = 88
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = OmikaMappers.OmikaCollaborateurMapper.Map(row);
|
||||
|
||||
Assert.NotNull(result.Value.Civilite);
|
||||
Assert.Equal(77, result.Value.Civilite!.LegacyId);
|
||||
|
||||
Assert.NotNull(result.Value.Statut);
|
||||
Assert.Equal(88, result.Value.Statut!.LegacyId);
|
||||
|
||||
Assert.Contains(result.Warnings, w => w.Contains(MappingWarningCodes.ZombieFieldUsedAsFallback, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurOmika_NoCleAndZeroLegacyId_ProducesNoReference()
|
||||
{
|
||||
var row = new OmikaModels.CollaborateurRow { COPKID = 11 };
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = OmikaMappers.OmikaCollaborateurMapper.Map(row);
|
||||
|
||||
Assert.Null(result.Value.Civilite);
|
||||
Assert.Null(result.Value.Statut);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollaborateurOmika_FieldsWithoutCanonicalEquivalent_AreReportedAsIgnored()
|
||||
{
|
||||
var row = new OmikaModels.CollaborateurRow
|
||||
{
|
||||
COPKID = 12,
|
||||
COFonction = "Développeur",
|
||||
COPonderation = 1.5f,
|
||||
COIDImport = "IMP-12",
|
||||
COAuPlanning = true,
|
||||
COAccesBO = true,
|
||||
COAccesMobile = true,
|
||||
CreationDate = new DateTime(2024, 1, 1),
|
||||
ModifDate = new DateTime(2024, 6, 1)
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Collaborateur> result = OmikaMappers.OmikaCollaborateurMapper.Map(row);
|
||||
|
||||
Assert.Equal("Développeur", result.Value.Fonction);
|
||||
Assert.Contains(result.IgnoredFields, f => f == "CO_Ponderation");
|
||||
Assert.Contains(result.IgnoredFields, f => f == "CO_IDImport");
|
||||
Assert.Contains(result.IgnoredFields, f => f == "CO_AuPlanning");
|
||||
Assert.Contains(result.IgnoredFields, f => f == "CO_AccesBO");
|
||||
Assert.Contains(result.IgnoredFields, f => f == "CO_AccesMobile");
|
||||
Assert.Contains(result.IgnoredFields, f => f == "CreationDate");
|
||||
Assert.Contains(result.IgnoredFields, f => f == "ModifDate");
|
||||
Assert.Null(result.Value.SocieteId);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,467 @@
|
||||
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 contacts (table Personne).
|
||||
/// Aucune base réelle, aucun HFSQL : uniquement des objets en mémoire.
|
||||
/// </summary>
|
||||
public class OmikaContactMapperTests
|
||||
{
|
||||
// ---- 1. Contact Omika minimal --------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ContactOmika_Minimal_MapsWithoutExceptionAndKeepsNoFabricatedValue()
|
||||
{
|
||||
var row = new OmikaModels.PersonneRow { PEPKID = 1 };
|
||||
|
||||
CanonicalMappingResult<Contact> result = OmikaMappers.OmikaContactMapper.Map(row);
|
||||
|
||||
Assert.Equal(DatabaseSchemaVersion.Omika, 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(DatabaseSchemaVersion.Omika, result.Value.SourceReference!.Source);
|
||||
Assert.Equal(1, result.Value.SourceReference!.NumericId);
|
||||
Assert.Null(result.Value.SourceReference!.EntityGuid);
|
||||
Assert.Empty(result.Warnings);
|
||||
}
|
||||
|
||||
// ---- 2. Contact Omika complet --------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ContactOmika_Full_MapsAllCanonicalProperties()
|
||||
{
|
||||
var row = new OmikaModels.PersonneRow
|
||||
{
|
||||
PEPKID = 42,
|
||||
GUID = "550e8400-e29b-41d4-a716-446655440000",
|
||||
PECleCivilite = "MR",
|
||||
PEIDCivilite = 2,
|
||||
PENom = "Dupont",
|
||||
PEPrenom = "Jean",
|
||||
PECleRole = "R_CP",
|
||||
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-O-42"
|
||||
};
|
||||
|
||||
var referenceRows = new Dictionary<string, OmikaModels.ParametreMetierRow>
|
||||
{
|
||||
["MR"] = new() { PAPKID = 2, PACLE = "MR", PALibelle = "M.", PACategorie = "CIV" },
|
||||
["R_CP"] = new() { PAPKID = 3, PACLE = "R_CP", PALibelle = "Contact principal", PACategorie = "ROLE" }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Contact> result = OmikaMappers.OmikaContactMapper.Map(row, referenceRows);
|
||||
|
||||
Contact contact = result.Value;
|
||||
Assert.Equal("M.", contact.Civilite!.Label);
|
||||
Assert.Equal("MR", contact.Civilite!.Key);
|
||||
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("R_CP", contact.Roles[0].Key);
|
||||
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-O-42", contact.SourceReference!.ImportId);
|
||||
Assert.Equal(DatabaseSchemaVersion.Omika, contact.SourceReference!.Source);
|
||||
Assert.NotNull(contact.SourceReference!.EntityGuid);
|
||||
}
|
||||
|
||||
// ---- 3. GUID valide avec tirets -------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ContactOmika_ValidGuidWithDashes_IsKeptInEntityGuid()
|
||||
{
|
||||
var row = new OmikaModels.PersonneRow
|
||||
{
|
||||
PEPKID = 7,
|
||||
GUID = "550e8400-e29b-41d4-a716-446655440000",
|
||||
PENom = "Dupont"
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Contact> result = OmikaMappers.OmikaContactMapper.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 ContactOmika_ValidGuidWithoutDashes_IsParsedByTryParse()
|
||||
{
|
||||
var row = new OmikaModels.PersonneRow
|
||||
{
|
||||
PEPKID = 8,
|
||||
GUID = "550e8400e29b41d4a716446655440000"
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Contact> result = OmikaMappers.OmikaContactMapper.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 ContactOmika_InvalidGuid_ProducesNullGuidAndWarningWithoutException()
|
||||
{
|
||||
var row = new OmikaModels.PersonneRow
|
||||
{
|
||||
PEPKID = 9,
|
||||
GUID = "not-a-guid",
|
||||
PENom = "Société inconnue"
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Contact> result = OmikaMappers.OmikaContactMapper.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);
|
||||
}
|
||||
|
||||
// ---- 6. Coordonnées entièrement nulles -------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ContactOmika_EmptyCoordinates_ProducesNullCoordonnees()
|
||||
{
|
||||
var row = new OmikaModels.PersonneRow
|
||||
{
|
||||
PEPKID = 5,
|
||||
PENom = "Martin",
|
||||
PEEmail = " ",
|
||||
PETelFixe = "",
|
||||
PETelMobile = null,
|
||||
PEFax = "\t"
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Contact> result = OmikaMappers.OmikaContactMapper.Map(row);
|
||||
|
||||
Assert.Null(result.Value.Coordonnees);
|
||||
Assert.Equal("Martin", result.Value.Nom);
|
||||
}
|
||||
|
||||
// ---- 7. Coordonnées partielles ---------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ContactOmika_PartialCoordinates_KeepsOnlyProvidedValues()
|
||||
{
|
||||
var row = new OmikaModels.PersonneRow
|
||||
{
|
||||
PEPKID = 6,
|
||||
PEEmail = "m@exemple.fr",
|
||||
PETelFixe = null,
|
||||
PETelMobile = "0600000000",
|
||||
PEFax = ""
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Contact> result = OmikaMappers.OmikaContactMapper.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);
|
||||
}
|
||||
|
||||
// ---- 8. Priorité de la clé métier sur l'ancien identifiant ------------------------
|
||||
|
||||
[Fact]
|
||||
public void ContactOmika_BusinessKeyIsPreferredAndLegacyIdIsKeptAsCompatibility()
|
||||
{
|
||||
var referenceRows = new Dictionary<string, OmikaModels.ParametreMetierRow>
|
||||
{
|
||||
["MR"] = new() { PAPKID = 50, PACLE = "MR", PALibelle = "M.", PACategorie = "CIV" }
|
||||
};
|
||||
|
||||
var row = new OmikaModels.PersonneRow
|
||||
{
|
||||
PEPKID = 13,
|
||||
PECleCivilite = "MR",
|
||||
PEIDCivilite = 99 // zombie : ancien identifiant
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Contact> result = OmikaMappers.OmikaContactMapper.Map(row, referenceRows);
|
||||
|
||||
Assert.Equal("MR", result.Value.Civilite!.Key);
|
||||
Assert.Equal("M.", result.Value.Civilite!.Label);
|
||||
Assert.Equal(99, result.Value.Civilite!.LegacyId);
|
||||
Assert.Empty(result.Warnings);
|
||||
}
|
||||
|
||||
// ---- 9. Conservation de l'identifiant numérique historique -------------------------
|
||||
|
||||
[Fact]
|
||||
public void ContactOmika_NumericIdentifier_IsPreserved()
|
||||
{
|
||||
var row = new OmikaModels.PersonneRow { PEPKID = 1234 };
|
||||
|
||||
CanonicalMappingResult<Contact> result = OmikaMappers.OmikaContactMapper.Map(row);
|
||||
|
||||
Assert.NotNull(result.Value.SourceReference);
|
||||
Assert.Equal(DatabaseSchemaVersion.Omika, result.Value.SourceReference!.Source);
|
||||
Assert.Equal(1234, result.Value.SourceReference!.NumericId);
|
||||
}
|
||||
|
||||
// ---- 10. Conservation de l'identifiant d'import -------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ContactOmika_ImportId_IsPreserved()
|
||||
{
|
||||
var row = new OmikaModels.PersonneRow { PEPKID = 5678, PEIDImport = "IMP-O" };
|
||||
|
||||
CanonicalMappingResult<Contact> result = OmikaMappers.OmikaContactMapper.Map(row);
|
||||
|
||||
Assert.Equal("IMP-O", result.Value.SourceReference!.ImportId);
|
||||
Assert.Equal(5678, result.Value.SourceReference!.NumericId);
|
||||
}
|
||||
|
||||
// ---- 11. Absence de GUID fabriqué -----------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ContactOmika_WithoutGuid_ProducesNoFabricatedGuid()
|
||||
{
|
||||
var row = new OmikaModels.PersonneRow { PEPKID = 11 };
|
||||
|
||||
CanonicalMappingResult<Contact> result = OmikaMappers.OmikaContactMapper.Map(row);
|
||||
|
||||
Assert.Null(result.Value.SourceReference!.EntityGuid);
|
||||
Assert.Null(result.Value.SourceReference!.PhysicalGuid);
|
||||
Assert.Empty(result.Warnings);
|
||||
}
|
||||
|
||||
// ---- 12. Repli sur un champ zombie avec avertissement --------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ContactOmika_MissingBusinessKey_ExposesZombieLegacyIdWithWarning()
|
||||
{
|
||||
var row = new OmikaModels.PersonneRow
|
||||
{
|
||||
PEPKID = 14,
|
||||
PECleCivilite = null, // rubrique active absente
|
||||
PEIDCivilite = 77 // ancien identifiant (zombie)
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Contact> result = OmikaMappers.OmikaContactMapper.Map(row);
|
||||
|
||||
Assert.NotNull(result.Value.Civilite);
|
||||
Assert.Equal(77, result.Value.Civilite!.LegacyId);
|
||||
Assert.Null(result.Value.Civilite!.Key);
|
||||
Assert.Contains(result.Warnings, w => w.StartsWith(MappingWarningCodes.ZombieFieldUsedAsFallback, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
// ---- 13. Référentiel non résolu ---------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ContactOmika_UnresolvedBusinessKey_KeepsKeyAndLegacyIdAndWarns()
|
||||
{
|
||||
var row = new OmikaModels.PersonneRow
|
||||
{
|
||||
PEPKID = 15,
|
||||
PECleCivilite = "MR",
|
||||
PEIDCivilite = 77
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Contact> result = OmikaMappers.OmikaContactMapper.Map(row);
|
||||
|
||||
Assert.NotNull(result.Value.Civilite);
|
||||
Assert.Equal("MR", result.Value.Civilite!.Key);
|
||||
Assert.Equal(77, result.Value.Civilite!.LegacyId);
|
||||
Assert.Equal(string.Empty, result.Value.Civilite!.Label);
|
||||
Assert.Contains(result.Warnings, w => w.Contains(MappingWarningCodes.UnresolvedReference, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContactOmika_ZeroCiviliteAndRole_ProducesNoReference()
|
||||
{
|
||||
var row = new OmikaModels.PersonneRow { PEPKID = 16, PEIDCivilite = 0, PEIDRole = 0 };
|
||||
|
||||
CanonicalMappingResult<Contact> result = OmikaMappers.OmikaContactMapper.Map(row);
|
||||
|
||||
Assert.Null(result.Value.Civilite);
|
||||
Assert.Empty(result.Value.Roles);
|
||||
Assert.Empty(result.Warnings);
|
||||
}
|
||||
|
||||
// ---- 14. Chaînes vides sans exception -----------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ContactOmika_EmptyOrNullStrings_NeverThrow()
|
||||
{
|
||||
var row = new OmikaModels.PersonneRow
|
||||
{
|
||||
PENom = "",
|
||||
PEPrenom = " ",
|
||||
PEEmail = "",
|
||||
PETelFixe = null,
|
||||
PETelMobile = " ",
|
||||
PEFax = "",
|
||||
PENote = "",
|
||||
PEInfoSupp = " ",
|
||||
PEIDImport = "",
|
||||
GUID = null
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Contact> result = OmikaMappers.OmikaContactMapper.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 ContactOmika_DefaultRow_NeverThrows()
|
||||
{
|
||||
var row = new OmikaModels.PersonneRow();
|
||||
|
||||
CanonicalMappingResult<Contact> result = OmikaMappers.OmikaContactMapper.Map(row);
|
||||
|
||||
Assert.NotNull(result.Value);
|
||||
Assert.Null(result.Value.SourceReference!.NumericId);
|
||||
Assert.False(result.Value.Actif);
|
||||
Assert.False(result.Value.EstArchive);
|
||||
}
|
||||
|
||||
// ---- 15. Champs sans équivalent dans IgnoredFields ----------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ContactOmika_FieldsWithoutCanonicalEquivalent_AreReportedAsIgnored()
|
||||
{
|
||||
var row = new OmikaModels.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<Contact> result = OmikaMappers.OmikaContactMapper.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);
|
||||
}
|
||||
|
||||
// ---- 16. Absence de noms physiques HFSQL dans le domaine canonique -------------------------
|
||||
|
||||
[Fact]
|
||||
public void ContactOmika_CanonicalDomain_DoesNotExposeHfSqlNames()
|
||||
{
|
||||
var row = new OmikaModels.PersonneRow
|
||||
{
|
||||
PEPKID = 40,
|
||||
GUID = "550e8400-e29b-41d4-a716-446655440000",
|
||||
PECleCivilite = "MR",
|
||||
PEIDCivilite = 2,
|
||||
PENom = "Dupont",
|
||||
PEPrenom = "Jean",
|
||||
PECleRole = "R_CP",
|
||||
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<string, OmikaModels.ParametreMetierRow>
|
||||
{
|
||||
["MR"] = new() { PAPKID = 2, PACLE = "MR", PALibelle = "M.", PACategorie = "CIV" },
|
||||
["R_CP"] = new() { PAPKID = 3, PACLE = "R_CP", PALibelle = "Rôle", PACategorie = "ROLE" }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Contact> result = OmikaMappers.OmikaContactMapper.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);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
using Omika.Core.Compatibility;
|
||||
using Omika.Core.Domain.Entities.Interventions;
|
||||
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 interventions.
|
||||
/// Aucune base réelle, aucun HFSQL : uniquement des objets en mémoire.
|
||||
///
|
||||
/// Le mapper ne renseigne pas <c>SourceReference</c> sur l'entité canonique
|
||||
/// (la référence source est construite en interne mais non affectée) ; ces
|
||||
/// tests se concentrent donc sur les propriétés canoniques réellement mappées.
|
||||
/// </summary>
|
||||
public class OmikaInterventionMapperTests
|
||||
{
|
||||
// ---- 1. Intervention Omika minimale -------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void InterventionOmika_Minimal_MapsWithoutExceptionAndKeepsNoFabricatedValue()
|
||||
{
|
||||
var row = new OmikaModels.InterventionRow { INPKID = 1 };
|
||||
|
||||
CanonicalMappingResult<Intervention> result = OmikaMappers.OmikaInterventionMapper.Map(row);
|
||||
|
||||
Assert.Equal(DatabaseSchemaVersion.Omika, 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 Omika complète -------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void InterventionOmika_Full_MapsAllCanonicalProperties()
|
||||
{
|
||||
var row = new OmikaModels.InterventionRow
|
||||
{
|
||||
INPKID = 42,
|
||||
INTitre = "Réparation chaudière",
|
||||
INRefInterne = "INT-042",
|
||||
INDescription = "Remplacement de la chaudière",
|
||||
INCleType = "TYP-MAINT",
|
||||
INCleEtat = "STA-ENCOURS",
|
||||
INDateIntervention = new DateOnly(2026, 3, 15),
|
||||
Archive = true,
|
||||
INIDImport = "IMP-O-42",
|
||||
GUID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
};
|
||||
|
||||
var referenceRows = new Dictionary<string, OmikaModels.ParametreMetierRow>
|
||||
{
|
||||
["TYP-MAINT"] = new() { PAPKID = 2, PACLE = "TYP-MAINT", PALibelle = "Maintenance" },
|
||||
["STA-ENCOURS"] = new() { PAPKID = 3, PACLE = "STA-ENCOURS", PALibelle = "En cours" }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Intervention> result = OmikaMappers.OmikaInterventionMapper.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("TYP-MAINT", intervention.Type!.Key);
|
||||
Assert.Equal("En cours", intervention.Etat!.Label);
|
||||
Assert.Equal("STA-ENCOURS", intervention.Etat!.Key);
|
||||
Assert.Equal(new DateTime(2026, 3, 15), intervention.DatePrevueDebut);
|
||||
Assert.True(intervention.EstArchive);
|
||||
}
|
||||
|
||||
// ---- 3. GUID ------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void InterventionOmika_InvalidGuid_ProducesWarning()
|
||||
{
|
||||
var row = new OmikaModels.InterventionRow { INPKID = 42, GUID = "not-a-valid-guid" };
|
||||
|
||||
CanonicalMappingResult<Intervention> result = OmikaMappers.OmikaInterventionMapper.Map(row);
|
||||
|
||||
Assert.Contains(result.Warnings, w => w.Contains(MappingWarningCodes.InvalidPhysicalGuid, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InterventionOmika_EmptyGuid_NoWarning()
|
||||
{
|
||||
var row = new OmikaModels.InterventionRow { INPKID = 42, GUID = "" };
|
||||
|
||||
CanonicalMappingResult<Intervention> result = OmikaMappers.OmikaInterventionMapper.Map(row);
|
||||
|
||||
Assert.Empty(result.Warnings);
|
||||
}
|
||||
|
||||
// ---- 4. Type et état (clés métier) --------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void InterventionOmika_Type_IsResolvedFromCleType()
|
||||
{
|
||||
var row = new OmikaModels.InterventionRow { INPKID = 5, INCleType = "TYP-URG" };
|
||||
|
||||
var referenceRows = new Dictionary<string, OmikaModels.ParametreMetierRow>
|
||||
{
|
||||
["TYP-URG"] = new() { PAPKID = 7, PACLE = "TYP-URG", PALibelle = "Urgence" }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Intervention> result = OmikaMappers.OmikaInterventionMapper.Map(row, referenceRows);
|
||||
|
||||
Assert.NotNull(result.Value.Type);
|
||||
Assert.Equal("Urgence", result.Value.Type!.Label);
|
||||
Assert.Equal("TYP-URG", result.Value.Type!.Key);
|
||||
Assert.Empty(result.Warnings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InterventionOmika_UnresolvedType_KeepsKeyAndWarns()
|
||||
{
|
||||
var row = new OmikaModels.InterventionRow { INPKID = 6, INCleType = "TYP-INCONNU" };
|
||||
|
||||
CanonicalMappingResult<Intervention> result = OmikaMappers.OmikaInterventionMapper.Map(row);
|
||||
|
||||
Assert.NotNull(result.Value.Type);
|
||||
Assert.Equal("TYP-INCONNU", result.Value.Type!.Key);
|
||||
Assert.Equal(string.Empty, result.Value.Type!.Label);
|
||||
Assert.Contains(result.Warnings, w => w.Contains("IN_CleType", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InterventionOmika_NoTypeKey_ProducesNoReference()
|
||||
{
|
||||
var row = new OmikaModels.InterventionRow { INPKID = 8 };
|
||||
|
||||
CanonicalMappingResult<Intervention> result = OmikaMappers.OmikaInterventionMapper.Map(row);
|
||||
|
||||
Assert.Null(result.Value.Type);
|
||||
Assert.Empty(result.Warnings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InterventionOmika_UnresolvedEtat_KeepsKeyAndWarns()
|
||||
{
|
||||
var row = new OmikaModels.InterventionRow { INPKID = 9, INCleEtat = "STA-INCONNU" };
|
||||
|
||||
CanonicalMappingResult<Intervention> result = OmikaMappers.OmikaInterventionMapper.Map(row);
|
||||
|
||||
Assert.NotNull(result.Value.Etat);
|
||||
Assert.Equal("STA-INCONNU", result.Value.Etat!.Key);
|
||||
Assert.Contains(result.Warnings, w => w.Contains("IN_CleEtat", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
// ---- 5. Champs ignorés --------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void InterventionOmika_FieldsWithoutCanonicalEquivalent_AreReportedAsIgnored()
|
||||
{
|
||||
var row = new OmikaModels.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<Intervention> result = OmikaMappers.OmikaInterventionMapper.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 InterventionOmika_EmptyOrNullStrings_NeverThrow()
|
||||
{
|
||||
var row = new OmikaModels.InterventionRow
|
||||
{
|
||||
INTitre = "",
|
||||
INRefInterne = " ",
|
||||
INDescription = "",
|
||||
INNoteInterne = " ",
|
||||
INRefExterne = "",
|
||||
INIDImport = " "
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Intervention> result = OmikaMappers.OmikaInterventionMapper.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 InterventionOmika_DefaultRow_NeverThrows()
|
||||
{
|
||||
var row = new OmikaModels.InterventionRow();
|
||||
|
||||
CanonicalMappingResult<Intervention> result = OmikaMappers.OmikaInterventionMapper.Map(row);
|
||||
|
||||
Assert.NotNull(result.Value);
|
||||
Assert.False(result.Value.EstArchive);
|
||||
}
|
||||
|
||||
// ---- 7. Absence de noms physiques HFSQL dans le domaine canonique -------------
|
||||
|
||||
[Fact]
|
||||
public void InterventionOmika_CanonicalDomain_DoesNotExposeHfSqlNames()
|
||||
{
|
||||
var row = new OmikaModels.InterventionRow
|
||||
{
|
||||
INPKID = 40,
|
||||
INTitre = "Titre",
|
||||
INRefInterne = "INT-040",
|
||||
INDescription = "desc",
|
||||
INCleType = "TYP-MAINT",
|
||||
INCleEtat = "STA-ENCOURS",
|
||||
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",
|
||||
GUID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
};
|
||||
|
||||
var referenceRows = new Dictionary<string, OmikaModels.ParametreMetierRow>
|
||||
{
|
||||
["TYP-MAINT"] = new() { PAPKID = 2, PACLE = "TYP-MAINT", PALibelle = "Maintenance" },
|
||||
["STA-ENCOURS"] = new() { PAPKID = 3, PACLE = "STA-ENCOURS", PALibelle = "En cours" }
|
||||
};
|
||||
|
||||
CanonicalMappingResult<Intervention> result = OmikaMappers.OmikaInterventionMapper.Map(row, referenceRows);
|
||||
Intervention intervention = result.Value;
|
||||
|
||||
string[] forbidden =
|
||||
[
|
||||
"IN_", "PA_", "PKID", "HfSql", "HFSQL"
|
||||
];
|
||||
|
||||
AssertHasNoPhysicalNames(intervention, 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
using Omika.Core.Compatibility;
|
||||
using Omika.Core.Domain.Entities.Planning;
|
||||
using Omika.Core.Domain.ValueObjects;
|
||||
using OmikaModels = Omika.Data.HfSql.Models.Omika;
|
||||
using OmikaMappers = Omika.Data.Mapping.Omika;
|
||||
|
||||
namespace Omika.Tests.Mapping;
|
||||
|
||||
/// <summary>
|
||||
/// Tests unitaires du mapping Omika de l'affectation Collaborateur d'un
|
||||
/// rendez-vous (table RDV_Collaborateur). Aucune base réelle : uniquement des
|
||||
/// objets en mémoire.
|
||||
/// </summary>
|
||||
public class OmikaRendezVousMapperTests
|
||||
{
|
||||
[Fact]
|
||||
public void MapCollaborateur_DateFinAnterieureADateDeb_NeLevePasEtPeriodeNull()
|
||||
{
|
||||
var row = new OmikaModels.RDVCollaborateurRow
|
||||
{
|
||||
RVCPKID = 1,
|
||||
RVCGUIDColb = "0f8fad5b-d9cb-469f-a165-70867728950e",
|
||||
RVCDateDeb = new DateTime(2026, 8, 19, 10, 0, 0),
|
||||
RVCDateFin = new DateTime(2026, 8, 19, 9, 0, 0), // antérieure → anomalie historique
|
||||
RVCResponsable = true,
|
||||
RVCTempsValide = true
|
||||
};
|
||||
|
||||
// Aucune DomainValidationException ne doit remonter.
|
||||
AffectationCollaborateur affectation = OmikaMappers.OmikaRendezVousMapper.MapCollaborateur(row);
|
||||
|
||||
Assert.NotNull(affectation);
|
||||
Assert.Null(affectation.PeriodeParticuliere);
|
||||
// L'affectation elle-même est conservée (identifiant + flags).
|
||||
Assert.NotNull(affectation.CollaborateurId);
|
||||
Assert.True(affectation.Responsable);
|
||||
Assert.True(affectation.TempsValide);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapCollaborateur_PlageValide_PeriodeMappee()
|
||||
{
|
||||
var debut = new DateTime(2026, 8, 19, 9, 0, 0);
|
||||
var fin = new DateTime(2026, 8, 19, 10, 0, 0);
|
||||
var row = new OmikaModels.RDVCollaborateurRow
|
||||
{
|
||||
RVCDateDeb = debut,
|
||||
RVCDateFin = fin
|
||||
};
|
||||
|
||||
AffectationCollaborateur affectation = OmikaMappers.OmikaRendezVousMapper.MapCollaborateur(row);
|
||||
|
||||
Assert.NotNull(affectation.PeriodeParticuliere);
|
||||
Assert.Equal(debut, affectation.PeriodeParticuliere!.Start);
|
||||
Assert.Equal(fin, affectation.PeriodeParticuliere.End);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapCollaborateur_DateEgale_PeriodeMappee()
|
||||
{
|
||||
var instant = new DateTime(2026, 8, 19, 9, 0, 0);
|
||||
var row = new OmikaModels.RDVCollaborateurRow
|
||||
{
|
||||
RVCDateDeb = instant,
|
||||
RVCDateFin = instant
|
||||
};
|
||||
|
||||
AffectationCollaborateur affectation = OmikaMappers.OmikaRendezVousMapper.MapCollaborateur(row);
|
||||
|
||||
Assert.NotNull(affectation.PeriodeParticuliere);
|
||||
Assert.Equal(instant, affectation.PeriodeParticuliere!.Start);
|
||||
Assert.Equal(instant, affectation.PeriodeParticuliere.End);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapCollaborateur_DatesParDefaut_PeriodeNull()
|
||||
{
|
||||
var row = new OmikaModels.RDVCollaborateurRow
|
||||
{
|
||||
RVCDateDeb = default,
|
||||
RVCDateFin = default
|
||||
};
|
||||
|
||||
AffectationCollaborateur affectation = OmikaMappers.OmikaRendezVousMapper.MapCollaborateur(row);
|
||||
|
||||
Assert.Null(affectation.PeriodeParticuliere);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapCollaborateur_PkNumeriqueFourni_SourceReferencePorteNumericIdEtEntityGuid()
|
||||
{
|
||||
Guid guid = Guid.Parse("0f8fad5b-d9cb-469f-a165-70867728950e");
|
||||
var row = new OmikaModels.RDVCollaborateurRow
|
||||
{
|
||||
RVCGUIDColb = guid.ToString("D")
|
||||
};
|
||||
|
||||
AffectationCollaborateur affectation =
|
||||
OmikaMappers.OmikaRendezVousMapper.MapCollaborateur(row, collaborateurNumericId: 42);
|
||||
|
||||
Assert.NotNull(affectation.SourceReference);
|
||||
Assert.Equal(42, affectation.SourceReference!.NumericId);
|
||||
Assert.Equal(guid, affectation.SourceReference.EntityGuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapCollaborateur_SansPkNumerique_SourceReferenceSansNumericId()
|
||||
{
|
||||
Guid guid = Guid.Parse("0f8fad5b-d9cb-469f-a165-70867728950e");
|
||||
var row = new OmikaModels.RDVCollaborateurRow
|
||||
{
|
||||
RVCGUIDColb = guid.ToString("D")
|
||||
};
|
||||
|
||||
AffectationCollaborateur affectation =
|
||||
OmikaMappers.OmikaRendezVousMapper.MapCollaborateur(row);
|
||||
|
||||
Assert.NotNull(affectation.SourceReference);
|
||||
Assert.Null(affectation.SourceReference!.NumericId);
|
||||
Assert.Equal(guid, affectation.SourceReference.EntityGuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_RVIdInterPresent_InterventionSourceReferenceAvecGUID()
|
||||
{
|
||||
var interventionGuid = Guid.Parse("11111111-1111-1111-1111-111111111111");
|
||||
var row = new OmikaModels.RDVPlanningRow
|
||||
{
|
||||
RVPKID = 1,
|
||||
GUID = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
RVDate = new DateOnly(2026, 8, 19),
|
||||
RVIdInter = 105
|
||||
};
|
||||
var interventionGuids = new Dictionary<long, Guid> { [105] = interventionGuid };
|
||||
|
||||
CanonicalMappingResult<RendezVous> mapped =
|
||||
OmikaMappers.OmikaRendezVousMapper.Map(row, null, interventionGuids);
|
||||
|
||||
Assert.NotNull(mapped.Value.InterventionSourceReference);
|
||||
Assert.Equal(105, mapped.Value.InterventionSourceReference.NumericId);
|
||||
Assert.Equal(interventionGuid, mapped.Value.InterventionSourceReference.EntityGuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_RVIdInterAbsent_InterventionSourceReferenceNull()
|
||||
{
|
||||
var row = new OmikaModels.RDVPlanningRow
|
||||
{
|
||||
RVPKID = 1,
|
||||
GUID = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
RVDate = new DateOnly(2026, 8, 19),
|
||||
RVIdInter = 0
|
||||
};
|
||||
|
||||
CanonicalMappingResult<RendezVous> mapped =
|
||||
OmikaMappers.OmikaRendezVousMapper.Map(row, null, null);
|
||||
|
||||
Assert.Null(mapped.Value.InterventionSourceReference);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_RVIdInterOrphelin_InterventionSourceReferenceSansGUID()
|
||||
{
|
||||
var row = new OmikaModels.RDVPlanningRow
|
||||
{
|
||||
RVPKID = 1,
|
||||
GUID = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
RVDate = new DateOnly(2026, 8, 19),
|
||||
RVIdInter = 999
|
||||
};
|
||||
|
||||
CanonicalMappingResult<RendezVous> mapped =
|
||||
OmikaMappers.OmikaRendezVousMapper.Map(row, null, new Dictionary<long, Guid>());
|
||||
|
||||
Assert.NotNull(mapped.Value.InterventionSourceReference);
|
||||
Assert.Equal(999, mapped.Value.InterventionSourceReference.NumericId);
|
||||
Assert.Null(mapped.Value.InterventionSourceReference.EntityGuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_AnnulationAvecReferentiel_LibellesOrigineEtMotifResolus()
|
||||
{
|
||||
// La fiche du rendez-vous affiche le motif et l'origine d'annulation : sans
|
||||
// résolution dans le référentiel, elle n'afficherait que le CODE du
|
||||
// paramètre (« O.ANU.RDV.E.CLI ») au lieu de son libellé.
|
||||
var row = new OmikaModels.RDVPlanningRow
|
||||
{
|
||||
RVPKID = 7,
|
||||
GUID = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
RVDate = new DateOnly(2026, 8, 19),
|
||||
RVCleOrigineAnnu = "O.ANU.RDV.E.CLI",
|
||||
RVCleMotifAnnu = "M.ANU.RDV.ABS"
|
||||
};
|
||||
|
||||
var references = new Dictionary<string, OmikaModels.ParametreMetierRow>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["O.ANU.RDV.E.CLI"] = new OmikaModels.ParametreMetierRow
|
||||
{
|
||||
PAPKID = 10,
|
||||
PACLE = "O.ANU.RDV.E.CLI",
|
||||
PACategorie = "O.ANU.RDV",
|
||||
PALibelle = "Externe : Client"
|
||||
},
|
||||
["M.ANU.RDV.ABS"] = new OmikaModels.ParametreMetierRow
|
||||
{
|
||||
PAPKID = 11,
|
||||
PACLE = "M.ANU.RDV.ABS",
|
||||
PACategorie = "M.ANU.RDV",
|
||||
PALibelle = "Absent"
|
||||
}
|
||||
};
|
||||
|
||||
CanonicalMappingResult<RendezVous> mapped =
|
||||
OmikaMappers.OmikaRendezVousMapper.Map(row, references, null);
|
||||
|
||||
Assert.NotNull(mapped.Value.Annulation);
|
||||
Assert.Equal("O.ANU.RDV.E.CLI", mapped.Value.Annulation!.Origine!.Key);
|
||||
Assert.Equal("Externe : Client", mapped.Value.Annulation.Origine!.Label);
|
||||
Assert.Equal("M.ANU.RDV.ABS", mapped.Value.Annulation.Motif!.Key);
|
||||
Assert.Equal("Absent", mapped.Value.Annulation.Motif!.Label);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_AnnulationSansReferentiel_ConserveLaCleSansInventerDeLibelle()
|
||||
{
|
||||
var row = new OmikaModels.RDVPlanningRow
|
||||
{
|
||||
RVPKID = 8,
|
||||
GUID = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
RVDate = new DateOnly(2026, 8, 19),
|
||||
RVCleMotifAnnu = "M.ANU.RDV.ABS"
|
||||
};
|
||||
|
||||
CanonicalMappingResult<RendezVous> mapped =
|
||||
OmikaMappers.OmikaRendezVousMapper.Map(row, null, null);
|
||||
|
||||
Assert.NotNull(mapped.Value.Annulation);
|
||||
Assert.Equal("M.ANU.RDV.ABS", mapped.Value.Annulation!.Motif!.Key);
|
||||
Assert.True(string.IsNullOrEmpty(mapped.Value.Annulation.Motif!.Label));
|
||||
Assert.Null(mapped.Value.Annulation.Origine);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_SansAnnulation_AucuneAnnulation()
|
||||
{
|
||||
var row = new OmikaModels.RDVPlanningRow
|
||||
{
|
||||
RVPKID = 9,
|
||||
GUID = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
RVDate = new DateOnly(2026, 8, 19)
|
||||
};
|
||||
|
||||
CanonicalMappingResult<RendezVous> mapped =
|
||||
OmikaMappers.OmikaRendezVousMapper.Map(row, null, null);
|
||||
|
||||
Assert.Null(mapped.Value.Annulation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,495 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user