Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
using Omika.Core.Compatibility;
|
||||
using Omika.Core.Domain.Entities.Clients;
|
||||
using Omika.Core.Domain.ValueObjects;
|
||||
|
||||
namespace Omika.Tests.Domain;
|
||||
|
||||
/// <summary>
|
||||
/// Tests de l'entité métier canonique <see cref="Client"/> : indépendance vis-à
|
||||
/// vis du stockage HFSQL et portage des collections sans table de liaison.
|
||||
/// </summary>
|
||||
public class ClientDomainTests
|
||||
{
|
||||
[Fact]
|
||||
public void Client_DoesNotExposePhysicalColumnNames()
|
||||
{
|
||||
var client = new Client();
|
||||
|
||||
string[] forbidden = ["CL_PKID", "CL_IDType", "CL_CleType", "CL_IDStatut", "CL_CleStatut"];
|
||||
|
||||
foreach (var property in client.GetType().GetProperties())
|
||||
{
|
||||
Assert.DoesNotContain(forbidden, f => property.Name.Contains(f, StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Client_CanCarryTypeStatutAndFormeJuridiqueAsReferenceValues()
|
||||
{
|
||||
var client = new Client
|
||||
{
|
||||
Nom = "Ville de Lyon",
|
||||
Type = new ReferenceValue("Collectivité") { Key = "COLLECTIVITE" },
|
||||
Statut = new ReferenceValue("Actif") { LegacyId = 5 },
|
||||
FormeJuridique = new ReferenceValue("Commune")
|
||||
};
|
||||
|
||||
Assert.Equal("Collectivité", client.Type!.Label);
|
||||
Assert.Equal("Actif", client.Statut!.Label);
|
||||
Assert.Equal("Commune", client.FormeJuridique!.Label);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Client_CanCarrySourceReferenceWithoutHfSqlName()
|
||||
{
|
||||
var client = new Client
|
||||
{
|
||||
SourceReference = LegacyEntityReference.FromLegacyId(12_345)
|
||||
};
|
||||
|
||||
Assert.Equal(DatabaseSchemaVersion.Batik, client.SourceReference!.Source);
|
||||
Assert.Equal(12_345, client.SourceReference.NumericId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Client_CollectionsAreControlledByAddMethods()
|
||||
{
|
||||
var client = new Client();
|
||||
|
||||
client.AddAdresse(new Adresse { Etiquette = "Mairie" });
|
||||
client.AddContact(new Contact { Nom = "Dupont" });
|
||||
client.AddSite(new Site { Nom = "Hôtel de ville" });
|
||||
client.AddReferenceExterne(new ExternalReference("Batigest", ExternalId: "X-1"));
|
||||
|
||||
Assert.Single(client.Adresses);
|
||||
Assert.Single(client.Contacts);
|
||||
Assert.Single(client.Sites);
|
||||
Assert.Single(client.ReferencesExternes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Omika.Core.Domain.Exceptions;
|
||||
using Omika.Core.Domain.ValueObjects;
|
||||
|
||||
namespace Omika.Tests.Domain;
|
||||
|
||||
/// <summary>
|
||||
/// Tests de l'objet de valeur <see cref="DateTimeRange"/>.
|
||||
/// </summary>
|
||||
public class DateTimeRangeTests
|
||||
{
|
||||
[Fact]
|
||||
public void DateTimeRange_RejectsEndBeforeStart()
|
||||
{
|
||||
var start = new DateTime(2026, 1, 10, 9, 0, 0);
|
||||
var end = new DateTime(2026, 1, 10, 8, 0, 0);
|
||||
|
||||
Assert.Throws<DomainValidationException>(() => new DateTimeRange(start, end));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DateTimeRange_AcceptsEqualDates()
|
||||
{
|
||||
var instant = new DateTime(2026, 1, 10, 9, 0, 0);
|
||||
var range = new DateTimeRange(instant, instant);
|
||||
|
||||
Assert.Equal(TimeSpan.Zero, range.Duration);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DateTimeRange_ComputesDurationAndContains()
|
||||
{
|
||||
var range = new DateTimeRange(
|
||||
new DateTime(2026, 1, 10, 9, 0, 0),
|
||||
new DateTime(2026, 1, 10, 11, 30, 0));
|
||||
|
||||
Assert.Equal(TimeSpan.FromMinutes(150), range.Duration);
|
||||
Assert.True(range.Contains(new DateTime(2026, 1, 10, 10, 0, 0)));
|
||||
Assert.False(range.Contains(new DateTime(2026, 1, 10, 12, 0, 0)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DateTimeRange_DetectsOverlap()
|
||||
{
|
||||
var a = new DateTimeRange(
|
||||
new DateTime(2026, 1, 10, 9, 0, 0),
|
||||
new DateTime(2026, 1, 10, 11, 0, 0));
|
||||
var b = new DateTimeRange(
|
||||
new DateTime(2026, 1, 10, 10, 0, 0),
|
||||
new DateTime(2026, 1, 10, 12, 0, 0));
|
||||
var c = new DateTimeRange(
|
||||
new DateTime(2026, 1, 10, 13, 0, 0),
|
||||
new DateTime(2026, 1, 10, 14, 0, 0));
|
||||
|
||||
Assert.True(a.Overlaps(b));
|
||||
Assert.False(a.Overlaps(c));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Omika.Core.Domain.Entities.Interventions;
|
||||
using Omika.Core.Domain.ValueObjects;
|
||||
|
||||
namespace Omika.Tests.Domain;
|
||||
|
||||
/// <summary>
|
||||
/// Tests de l'entité métier canonique <see cref="Intervention"/>.
|
||||
/// </summary>
|
||||
public class InterventionDomainTests
|
||||
{
|
||||
[Fact]
|
||||
public void Intervention_UsesReferenceValuesForTypeEtatPriorite()
|
||||
{
|
||||
var intervention = new Intervention
|
||||
{
|
||||
Type = new ReferenceValue("Maintenance") { Key = "MAINT" },
|
||||
Etat = new ReferenceValue("En cours"),
|
||||
Priorite = new ReferenceValue("Haute") { LegacyId = 3 }
|
||||
};
|
||||
|
||||
Assert.Equal("MAINT", intervention.Type!.Key);
|
||||
Assert.Equal("En cours", intervention.Etat!.Label);
|
||||
Assert.Equal(3, intervention.Priorite!.LegacyId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Intervention_CanCarryCollaborateursActionsAndComptesRendus()
|
||||
{
|
||||
var intervention = new Intervention();
|
||||
|
||||
intervention.AddCollaborateur(Guid.NewGuid());
|
||||
intervention.AddContact(Guid.NewGuid());
|
||||
intervention.AddAction(new ActionIntervention { Description = "Préparer le plan", Terminee = false });
|
||||
intervention.AddCompteRendu(new CompteRendu { Contenu = "Site inspecté" });
|
||||
|
||||
Assert.Single(intervention.CollaborateurIds);
|
||||
Assert.Single(intervention.ContactIds);
|
||||
Assert.Single(intervention.Actions);
|
||||
Assert.Single(intervention.ComptesRendus);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Intervention_DoesNotExposeZombieFields()
|
||||
{
|
||||
var intervention = new Intervention();
|
||||
|
||||
string[] forbidden = ["Pkid", "IdStatut", "CleStatut", "CleType", "RDP_"];
|
||||
|
||||
foreach (var property in intervention.GetType().GetProperties())
|
||||
{
|
||||
Assert.DoesNotContain(forbidden, f => property.Name.Contains(f, StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
using Omika.Core.Contracts.Common;
|
||||
using Omika.Core.Contracts.Planning;
|
||||
using Omika.Core.Mapping;
|
||||
using Omika.Core.Domain.Entities.Planning;
|
||||
using Omika.Core.Domain.ValueObjects;
|
||||
|
||||
namespace Omika.Tests.Domain;
|
||||
|
||||
/// <summary>
|
||||
/// Scénario de compatibilité des rendez-vous : le même
|
||||
/// <see cref="RendezVousDetailsDto"/> doit pouvoir représenter un rendez-vous de
|
||||
/// l'ancien schéma (Batik) et du nouveau schéma (Omika), avec ou sans contacts,
|
||||
/// avec plusieurs collaborateurs, et avec des informations absentes selon la
|
||||
/// version.
|
||||
/// </summary>
|
||||
public class RendezVousCrossSchemaTests
|
||||
{
|
||||
private static RendezVous BuildOldSchemaRendezVous()
|
||||
{
|
||||
var rendezVous = new RendezVous
|
||||
{
|
||||
Titre = "RDV Batik",
|
||||
Description = null, // absent dans l'ancien modèle
|
||||
Periode = new DateTimeRange(new DateTime(2026, 2, 10, 9, 0, 0), new DateTime(2026, 2, 10, 10, 0, 0)),
|
||||
TypeActivite = new ReferenceValue("Visite") { LegacyId = 3 },
|
||||
Statut = new ReferenceValue("Planifié") { LegacyId = 1 },
|
||||
Couleur = "#FF0000",
|
||||
EstTouteLaJournee = false,
|
||||
EstArchive = false
|
||||
};
|
||||
|
||||
rendezVous.AddCollaborateur(new AffectationCollaborateur
|
||||
{
|
||||
CollaborateurId = Guid.NewGuid(),
|
||||
NomAffiche = "Dupont Jean",
|
||||
Responsable = true,
|
||||
Statut = new ReferenceValue("Affecté") { LegacyId = 2 },
|
||||
TempsValide = false
|
||||
});
|
||||
rendezVous.AddCollaborateur(new AffectationCollaborateur
|
||||
{
|
||||
CollaborateurId = Guid.NewGuid(),
|
||||
NomAffiche = "Martin Claire",
|
||||
Responsable = false,
|
||||
TempsValide = false
|
||||
});
|
||||
rendezVous.AddContact(new ParticipantRendezVous
|
||||
{
|
||||
ContactId = Guid.NewGuid(),
|
||||
NomAffiche = "Durand Paul",
|
||||
Email = "paul@exemple.fr",
|
||||
Telephone = "0600000000",
|
||||
PresenceConfirmee = false
|
||||
});
|
||||
|
||||
return rendezVous;
|
||||
}
|
||||
|
||||
private static RendezVous BuildNewSchemaRendezVous()
|
||||
{
|
||||
var rendezVous = new RendezVous
|
||||
{
|
||||
Titre = "RDV Omika",
|
||||
Description = "Visite de contrôle annuelle",
|
||||
Periode = new DateTimeRange(new DateTime(2026, 2, 12, 14, 0, 0), new DateTime(2026, 2, 12, 16, 0, 0)),
|
||||
ClientId = Guid.NewGuid(),
|
||||
SiteId = Guid.NewGuid(),
|
||||
DossierId = Guid.NewGuid(),
|
||||
InterventionId = Guid.NewGuid(),
|
||||
TypeActivite = new ReferenceValue("Contrôle") { Key = "CONTROLE" },
|
||||
Statut = new ReferenceValue("Confirmé") { Key = "CONFIRME" },
|
||||
Couleur = "#00FF00",
|
||||
EstTouteLaJournee = false,
|
||||
EstArchive = false
|
||||
};
|
||||
|
||||
rendezVous.AddCollaborateur(new AffectationCollaborateur
|
||||
{
|
||||
CollaborateurId = Guid.NewGuid(),
|
||||
NomAffiche = "Dupont Jean",
|
||||
Responsable = true,
|
||||
Statut = new ReferenceValue("Validé") { Key = "VALIDE" },
|
||||
TempsValide = true,
|
||||
PeriodeParticuliere = new DateTimeRange(new DateTime(2026, 2, 12, 14, 30, 0), new DateTime(2026, 2, 12, 15, 30, 0))
|
||||
});
|
||||
|
||||
// Pas de contact : le nouveau schéma gère RDV_Contact séparément.
|
||||
|
||||
return rendezVous;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SameDto_RepresentsOldAndNewSchema()
|
||||
{
|
||||
RendezVousDetailsDto oldDto = RendezVousContractMapper.ToDetails(BuildOldSchemaRendezVous());
|
||||
RendezVousDetailsDto newDto = RendezVousContractMapper.ToDetails(BuildNewSchemaRendezVous());
|
||||
|
||||
// Le type du DTO est strictement le même pour les deux versions.
|
||||
Assert.Equal(oldDto.GetType(), newDto.GetType());
|
||||
|
||||
// Les informations partagées sont portées de la même façon.
|
||||
Assert.Equal("RDV Batik", oldDto.Titre);
|
||||
Assert.Equal("RDV Omika", newDto.Titre);
|
||||
Assert.Equal(new DateTime(2026, 2, 10, 9, 0, 0), oldDto.Debut);
|
||||
Assert.Equal(new DateTime(2026, 2, 12, 16, 0, 0), newDto.Fin);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NewSchema_CanCarryLinkedEntities()
|
||||
{
|
||||
RendezVousDetailsDto newDto = RendezVousContractMapper.ToDetails(BuildNewSchemaRendezVous());
|
||||
|
||||
Assert.NotNull(newDto.ClientId);
|
||||
Assert.NotNull(newDto.SiteId);
|
||||
Assert.NotNull(newDto.DossierId);
|
||||
Assert.NotNull(newDto.InterventionId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OldSchema_AbsentInformationIsNull()
|
||||
{
|
||||
RendezVousDetailsDto oldDto = RendezVousContractMapper.ToDetails(BuildOldSchemaRendezVous());
|
||||
|
||||
// La description et les liens métier n'existaient pas dans l'ancien modèle.
|
||||
Assert.Null(oldDto.Description);
|
||||
Assert.Null(oldDto.ClientId);
|
||||
Assert.Null(oldDto.DossierId);
|
||||
Assert.Null(oldDto.InterventionId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OldSchema_CarriesLegacyReferenceValues()
|
||||
{
|
||||
RendezVousDetailsDto oldDto = RendezVousContractMapper.ToDetails(BuildOldSchemaRendezVous());
|
||||
|
||||
Assert.NotNull(oldDto.TypeActivite);
|
||||
Assert.Equal(3, oldDto.TypeActivite.LegacyId);
|
||||
Assert.Equal(1, oldDto.Statut!.LegacyId);
|
||||
// La nouvelle clé métier n'existe pas dans l'ancien schéma.
|
||||
Assert.Null(oldDto.TypeActivite.Key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NewSchema_CarriesBusinessKeys()
|
||||
{
|
||||
RendezVousDetailsDto newDto = RendezVousContractMapper.ToDetails(BuildNewSchemaRendezVous());
|
||||
|
||||
Assert.NotNull(newDto.TypeActivite);
|
||||
Assert.Equal("CONTROLE", newDto.TypeActivite.Key);
|
||||
Assert.Null(newDto.TypeActivite.LegacyId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RendezVous_WithMultipleCollaborateurs_MapsAllOfThem()
|
||||
{
|
||||
RendezVousDetailsDto oldDto = RendezVousContractMapper.ToDetails(BuildOldSchemaRendezVous());
|
||||
|
||||
Assert.Equal(2, oldDto.Collaborateurs.Count);
|
||||
Assert.True(oldDto.Collaborateurs[0].Responsable);
|
||||
Assert.False(oldDto.Collaborateurs[1].Responsable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RendezVous_WithoutContacts_IsSupported()
|
||||
{
|
||||
RendezVousDetailsDto newDto = RendezVousContractMapper.ToDetails(BuildNewSchemaRendezVous());
|
||||
|
||||
Assert.Empty(newDto.Contacts);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RendezVous_WithSingleCollaborateurCarriesTempsValide()
|
||||
{
|
||||
RendezVousDetailsDto newDto = RendezVousContractMapper.ToDetails(BuildNewSchemaRendezVous());
|
||||
|
||||
Assert.Single(newDto.Collaborateurs);
|
||||
Assert.True(newDto.Collaborateurs[0].TempsValide);
|
||||
Assert.NotNull(newDto.Collaborateurs[0].PeriodeDebut);
|
||||
Assert.NotNull(newDto.Collaborateurs[0].PeriodeFin);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using Omika.Core.Domain.Entities.Planning;
|
||||
using Omika.Core.Domain.ValueObjects;
|
||||
|
||||
namespace Omika.Tests.Domain;
|
||||
|
||||
/// <summary>
|
||||
/// Tests de l'agrégat métier canonique <see cref="RendezVous"/>, unique pour
|
||||
/// l'ancien et le nouveau schéma de base.
|
||||
/// </summary>
|
||||
public class RendezVousDomainTests
|
||||
{
|
||||
[Fact]
|
||||
public void RendezVous_IsIndependentOfPhysicalTableNames()
|
||||
{
|
||||
var rendezVous = new RendezVous();
|
||||
|
||||
string[] forbidden = ["Planning_RDV", "RDV_Planning", "RDP_GUID", "PL_PKID"];
|
||||
|
||||
Assert.DoesNotContain(forbidden, name => rendezVous.GetType().Name.Contains(name, StringComparison.Ordinal));
|
||||
|
||||
foreach (var property in rendezVous.GetType().GetProperties())
|
||||
{
|
||||
Assert.DoesNotContain(forbidden, name => property.Name.Contains(name, StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RendezVous_HoldsPeriodeAsValueObject()
|
||||
{
|
||||
var rendezVous = new RendezVous
|
||||
{
|
||||
Titre = "Visite",
|
||||
Periode = new DateTimeRange(new DateTime(2026, 2, 1, 9, 0, 0), new DateTime(2026, 2, 1, 11, 0, 0))
|
||||
};
|
||||
|
||||
Assert.Equal(new DateTime(2026, 2, 1, 9, 0, 0), rendezVous.Periode.Start);
|
||||
Assert.Equal(new DateTime(2026, 2, 1, 11, 0, 0), rendezVous.Periode.End);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RendezVous_CanCarryCollaborateursAndContacts()
|
||||
{
|
||||
var rendezVous = new RendezVous();
|
||||
|
||||
rendezVous.AddCollaborateur(new AffectationCollaborateur { CollaborateurId = Guid.NewGuid(), Responsable = true });
|
||||
rendezVous.AddCollaborateur(new AffectationCollaborateur { CollaborateurId = Guid.NewGuid() });
|
||||
rendezVous.AddContact(new ParticipantRendezVous { ContactId = Guid.NewGuid(), PresenceConfirmee = true });
|
||||
|
||||
Assert.Equal(2, rendezVous.Collaborateurs.Count);
|
||||
Assert.Single(rendezVous.Contacts);
|
||||
Assert.True(rendezVous.Contacts[0].PresenceConfirmee);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RendezVous_CanCarryAnnulation()
|
||||
{
|
||||
var rendezVous = new RendezVous
|
||||
{
|
||||
Annulation = new AnnulationRendezVous
|
||||
{
|
||||
DateAnnulation = new DateTime(2026, 2, 1, 8, 0, 0),
|
||||
Commentaire = "Reporté"
|
||||
}
|
||||
};
|
||||
|
||||
Assert.NotNull(rendezVous.Annulation);
|
||||
Assert.Equal("Reporté", rendezVous.Annulation.Commentaire);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using Omika.Core.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace Omika.Tests.Domain;
|
||||
|
||||
public class RubriqueSubstitutionEngineTests
|
||||
{
|
||||
[Fact]
|
||||
public void Substitute_ReplacesTokensCaseInsensitively()
|
||||
{
|
||||
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["NomClient"] = "ACME",
|
||||
["DateIntervention"] = "2026-08-06"
|
||||
};
|
||||
|
||||
string result = RubriqueSubstitutionEngine.Substitute(
|
||||
"Bonjour $NOMCLIENT$, intervention du $DateIntervention$.",
|
||||
values);
|
||||
|
||||
Assert.Equal("Bonjour ACME, intervention du 2026-08-06.", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Substitute_LeavesUnknownTokensUntouched()
|
||||
{
|
||||
var values = new Dictionary<string, string> { ["NomClient"] = "ACME" };
|
||||
|
||||
string result = RubriqueSubstitutionEngine.Substitute(
|
||||
"Bonjour $NomClient$ — $Inconnu$",
|
||||
values);
|
||||
|
||||
Assert.Equal("Bonjour ACME — $Inconnu$", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Substitute_NullOrEmptyTemplate_ReturnsEmpty()
|
||||
{
|
||||
Assert.Equal(string.Empty, RubriqueSubstitutionEngine.Substitute(null, new Dictionary<string, string>()));
|
||||
Assert.Equal(string.Empty, RubriqueSubstitutionEngine.Substitute(string.Empty, new Dictionary<string, string>()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Substitute_UnclosedTokenIsLeftUntouched()
|
||||
{
|
||||
string result = RubriqueSubstitutionEngine.Substitute(
|
||||
"Bonjour $NomClient",
|
||||
new Dictionary<string, string> { ["NomClient"] = "ACME" });
|
||||
|
||||
Assert.Equal("Bonjour $NomClient", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractTokens_ReturnsOrderedUniqueTokens()
|
||||
{
|
||||
IReadOnlyList<string> tokens = RubriqueSubstitutionEngine.ExtractTokens(
|
||||
"$A$ puis $B$ et encore $a$");
|
||||
|
||||
Assert.Equal(["A", "B"], tokens);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindUnknownTokens_ReturnsTokensMissingFromValues()
|
||||
{
|
||||
var values = new Dictionary<string, string> { ["A"] = "1" };
|
||||
|
||||
IReadOnlyList<string> unknown = RubriqueSubstitutionEngine.FindUnknownTokens(
|
||||
"$A$ $B$ $A$",
|
||||
values);
|
||||
|
||||
Assert.Equal(["B"], unknown);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user