using Omika.Core.Contracts.Configuration;
namespace Omika.Tests.Contracts;
///
/// Tests de la règle centrale d'obligation de saisie d'un type de temps sur un
/// rendez-vous ().
///
///
/// Les cas de référence sont ceux du métier : pause déjeuner autorisée dans les
/// rendez-vous de type intervention, plage horaire 12:00 → 15:00, saisie
/// obligatoire dès que le rendez-vous dépasse 30 minutes.
///
///
public class TempsObligatoireRulesTests
{
private static readonly DateTime Jour = new(2026, 3, 10);
private static DateTime Heure(int heures, int minutes = 0) => Jour.AddHours(heures).AddMinutes(minutes);
/// Type de temps « pause déjeuner » : 12:00 → 15:00, minimum 10 minutes.
private static TypeTempsParametresDto PauseDejeuner(
string? tempsMin = "10",
string? obligatoire = "0030",
string? typeRdv = null,
string? heureDebut = "120000",
string? heureFin = "150000")
=> new()
{
Obligatoire = obligatoire,
HeureDebut = heureDebut,
HeureFin = heureFin,
TempsMin = tempsMin,
TypeRdv = typeRdv
};
/// Type de rendez-vous « intervention » exigeant une saisie de temps.
private static TypeRdvParametresDto TypeRdvIntervention(bool saisirTempsObligatoire = true)
=> new() { SaisirTempsObligatoire = saisirTempsObligatoire };
// ===== Déclenchement par le seuil et la plage horaire =====
[Fact]
public void EstObligatoire_RendezVous8h17h_QuiEnglobeLaPlage_EstObligatoire()
{
bool obligatoire = TempsObligatoireRules.EstObligatoire(
PauseDejeuner(), TypeRdvIntervention(), "T.RDV.INTER", "Intervention", Heure(8), Heure(17));
Assert.True(obligatoire);
}
[Fact]
public void EstObligatoire_RendezVous11h15h_QuiEnglobeLaPlage_EstObligatoire()
{
bool obligatoire = TempsObligatoireRules.EstObligatoire(
PauseDejeuner(), TypeRdvIntervention(), "T.RDV.INTER", "Intervention", Heure(11), Heure(15));
Assert.True(obligatoire);
}
[Fact]
public void EstObligatoire_RendezVous13h15h_CreneauLibreAvant_NAPasEteObligatoire()
{
// 12:00 → 13:00 reste libre : la pause a pu être prise avant le rendez-vous.
bool obligatoire = TempsObligatoireRules.EstObligatoire(
PauseDejeuner(), TypeRdvIntervention(), "T.RDV.INTER", "Intervention", Heure(13), Heure(15));
Assert.False(obligatoire);
}
[Fact]
public void EstObligatoire_RendezVous11h14h_CreneauLibreApres_NAPasEteObligatoire()
{
// 14:00 → 15:00 reste libre : la pause peut venir après le rendez-vous.
bool obligatoire = TempsObligatoireRules.EstObligatoire(
PauseDejeuner(), TypeRdvIntervention(), "T.RDV.INTER", "Intervention", Heure(11), Heure(14));
Assert.False(obligatoire);
}
[Fact]
public void EstObligatoire_RendezVous12h30_15h_CreneauLibre30Min_AvecMinimum10Min_NAPasEteObligatoire()
{
// 12:00 → 12:30 reste libre, soit 30 minutes : de quoi prendre une pause
// dont la durée minimale est de 10 minutes.
bool obligatoire = TempsObligatoireRules.EstObligatoire(
PauseDejeuner(tempsMin: "10"), TypeRdvIntervention(), "T.RDV.INTER", "Intervention",
Heure(12, 30), Heure(15));
Assert.False(obligatoire);
}
[Fact]
public void EstObligatoire_RendezVous12h30_15h_CreneauLibre30Min_AvecMinimum1Heure_EstObligatoire()
{
// Mêmes horaires, mais une pause d'au moins 1 heure ne tient plus dans les
// 30 minutes libres avant le rendez-vous : le collaborateur doit la déclarer.
bool obligatoire = TempsObligatoireRules.EstObligatoire(
PauseDejeuner(tempsMin: "0100"), TypeRdvIntervention(), "T.RDV.INTER", "Intervention",
Heure(12, 30), Heure(15));
Assert.True(obligatoire);
}
[Fact]
public void EstObligatoire_RendezVous13h30_15h_CreneauLibre1h30_AvecMinimum1Heure_NAPasEteObligatoire()
{
// 12:00 → 13:30 reste libre (1 h 30) : la pause d'une heure peut y être prise.
bool obligatoire = TempsObligatoireRules.EstObligatoire(
PauseDejeuner(tempsMin: "0100"), TypeRdvIntervention(), "T.RDV.INTER", "Intervention",
Heure(13, 30), Heure(15));
Assert.False(obligatoire);
}
[Fact]
public void EstObligatoire_CreneauLibreInsuffisantDesDeuxCotes_EstObligatoire()
{
// 30 minutes avant et 30 minutes après, mais une pause d'une heure : aucun
// créneau libre n'est assez long, la saisie est obligatoire.
bool obligatoire = TempsObligatoireRules.EstObligatoire(
PauseDejeuner(tempsMin: "0100"), TypeRdvIntervention(), "T.RDV.INTER", "Intervention",
Heure(12, 30), Heure(14, 30));
Assert.True(obligatoire);
}
[Fact]
public void EstObligatoire_SansTempsMin_NAExigeQueSiAucunCreneauLibre()
{
TypeTempsParametresDto sansMinimum = PauseDejeuner(tempsMin: null);
// 5 minutes libres suffisent à pouvoir prendre le temps ailleurs.
Assert.False(TempsObligatoireRules.EstObligatoire(
sansMinimum, TypeRdvIntervention(), "T.RDV.INTER", "Intervention", Heure(12, 5), Heure(15)));
// Aucun créneau libre : la saisie est obligatoire.
Assert.True(TempsObligatoireRules.EstObligatoire(
sansMinimum, TypeRdvIntervention(), "T.RDV.INTER", "Intervention", Heure(11), Heure(16)));
}
// ===== Paramètres absents ou nuls : aucune restriction =====
[Fact]
public void EstObligatoire_SeuilVideOuZero_NAPasEteObligatoire()
{
Assert.False(TempsObligatoireRules.EstObligatoire(
PauseDejeuner(obligatoire: null), TypeRdvIntervention(), "T.RDV.INTER", "Intervention",
Heure(8), Heure(17)));
Assert.False(TempsObligatoireRules.EstObligatoire(
PauseDejeuner(obligatoire: "0"), TypeRdvIntervention(), "T.RDV.INTER", "Intervention",
Heure(8), Heure(17)));
}
[Fact]
public void EstObligatoire_RendezVousPlusCourtQueLeSeuil_NAPasEteObligatoire()
{
// 20 minutes de rendez-vous pour un seuil de 30 minutes.
bool obligatoire = TempsObligatoireRules.EstObligatoire(
PauseDejeuner(), TypeRdvIntervention(), "T.RDV.INTER", "Intervention", Heure(12), Heure(12, 20));
Assert.False(obligatoire);
}
[Fact]
public void EstObligatoire_TypeRdvSansSaisieObligatoire_NAPasEteObligatoire()
{
bool obligatoire = TempsObligatoireRules.EstObligatoire(
PauseDejeuner(), TypeRdvIntervention(saisirTempsObligatoire: false), "T.RDV.INTER", "Intervention",
Heure(8), Heure(17));
Assert.False(obligatoire);
}
[Fact]
public void EstObligatoire_ParametresDuTypeDeTempsAbsents_NAPasEteObligatoire()
{
bool obligatoire = TempsObligatoireRules.EstObligatoire(
null, TypeRdvIntervention(), "T.RDV.INTER", "Intervention", Heure(8), Heure(17));
Assert.False(obligatoire);
}
[Fact]
public void EstObligatoire_SansPlageHoraire_LeSeuilSeulDeclenche()
{
// Aucune borne d'horaire : aucune restriction, le seuil décide.
Assert.True(TempsObligatoireRules.EstObligatoire(
PauseDejeuner(heureDebut: null, heureFin: null), TypeRdvIntervention(),
"T.RDV.INTER", "Intervention", Heure(8), Heure(17)));
Assert.True(TempsObligatoireRules.EstObligatoire(
PauseDejeuner(heureDebut: "000000", heureFin: "000000"), TypeRdvIntervention(),
"T.RDV.INTER", "Intervention", Heure(8), Heure(17)));
}
// ===== Premier paramètre : type de rendez-vous autorisé =====
[Fact]
public void EstObligatoire_TypeDeTempsNonAutorisePourCeTypeDeRdv_NAPasEteObligatoire()
{
TypeTempsParametresDto pause = PauseDejeuner(typeRdv: "T.RDV.INTER");
Assert.True(TempsObligatoireRules.EstObligatoire(
pause, TypeRdvIntervention(), "T.RDV.INTER", "Intervention", Heure(8), Heure(17)));
Assert.False(TempsObligatoireRules.EstObligatoire(
pause, TypeRdvIntervention(), "T.RDV.SAV", "SAV", Heure(8), Heure(17)));
}
[Fact]
public void EstObligatoire_TypeDeRdvAutoriseParSonLibelle_EstObligatoire()
{
// Génération Batik : le type de temps liste le libellé, le rendez-vous non
// plus (aucune clé métier disponible).
bool obligatoire = TempsObligatoireRules.EstObligatoire(
PauseDejeuner(typeRdv: "Intervention"), TypeRdvIntervention(), null, "Intervention",
Heure(8), Heure(17));
Assert.True(obligatoire);
}
[Fact]
public void EstObligatoire_AncienCodeTypeAct_EstPrisEnCompte()
{
TypeTempsParametresDto pause = PauseDejeuner(typeRdv: null);
pause = pause with { TypeAct = "T.RDV.INTER" };
Assert.True(TempsObligatoireRules.EstObligatoire(
pause, TypeRdvIntervention(), "T.RDV.INTER", "Intervention", Heure(8), Heure(17)));
}
[Fact]
public void EstAutorise_ListeVide_AutoriseTousLesTypesDeRdv()
{
Assert.True(TempsObligatoireRules.EstAutorise(PauseDejeuner(), "T.RDV.SAV", "SAV"));
Assert.True(TempsObligatoireRules.EstAutorise(null, "T.RDV.SAV", "SAV"));
Assert.True(TempsObligatoireRules.EstAutorise(PauseDejeuner(), null, null));
}
[Fact]
public void EstAutorise_TypeDeRdvIndetermine_NAInterditPasLeTypeDeTemps()
{
// Sans type de rendez-vous connu, aucune restriction ne peut être appliquée :
// le type de temps reste saisissable.
Assert.True(TempsObligatoireRules.EstAutorise(PauseDejeuner(typeRdv: "T.RDV.INTER"), null, null));
}
// ===== Plage horaire : bornes partielles, créneau libre =====
[Fact]
public void EstObligatoire_PlageSansHeureDeFin_SEnTendJusquEnFinDeJournee()
{
// Plage 12:00 → fin de journée : un rendez-vous 12:00 → 24:00 la couvre
// entièrement, la pause ne peut plus être prise ailleurs.
bool obligatoire = TempsObligatoireRules.EstObligatoire(
PauseDejeuner(heureDebut: "120000", heureFin: null), TypeRdvIntervention(),
"T.RDV.INTER", "Intervention", Heure(12), Heure(24));
Assert.True(obligatoire);
}
[Fact]
public void EstObligatoire_PlageQuiChevaucheMinuit_EstLueSurLeLendemain()
{
// Plage 22:00 → 02:00 (nuit) et rendez-vous 22:30 → 02:00 : il reste
// 22:00 → 22:30 (30 minutes) pour prendre le temps ailleurs.
TypeTempsParametresDto tempsNuit = PauseDejeuner(
heureDebut: "220000", heureFin: "020000", tempsMin: "10");
Assert.False(TempsObligatoireRules.EstObligatoire(
tempsNuit, TypeRdvIntervention(), "T.RDV.INTER", "Intervention", Heure(22, 30), Heure(26)));
// Même plage, rendez-vous 22:00 → 02:00 : plus aucune place libre.
Assert.True(TempsObligatoireRules.EstObligatoire(
tempsNuit, TypeRdvIntervention(), "T.RDV.INTER", "Intervention", Heure(22), Heure(26)));
}
[Fact]
public void PlusGrandCreneauLibreMinutes_RetourneLePlusGrandCote()
{
Assert.Equal(60, TempsObligatoireRules.PlusGrandCreneauLibreMinutes(
PauseDejeuner(), Heure(13), Heure(15)));
Assert.Equal(120, TempsObligatoireRules.PlusGrandCreneauLibreMinutes(
PauseDejeuner(), Heure(14), Heure(16)));
Assert.Equal(0, TempsObligatoireRules.PlusGrandCreneauLibreMinutes(
PauseDejeuner(), Heure(11), Heure(16)));
}
[Fact]
public void PlusGrandCreneauLibreMinutes_SansPlageHoraire_EstIndetermine()
{
Assert.Null(TempsObligatoireRules.PlusGrandCreneauLibreMinutes(
PauseDejeuner(heureDebut: null, heureFin: null), Heure(8), Heure(17)));
Assert.Null(TempsObligatoireRules.PlusGrandCreneauLibreMinutes(null, Heure(8), Heure(17)));
}
[Fact]
public void PlageTexte_FormateLaPlageAutorisee()
{
Assert.Equal("12:00–15:00", TempsObligatoireRules.PlageTexte(PauseDejeuner()));
Assert.Null(TempsObligatoireRules.PlageTexte(PauseDejeuner(heureDebut: null, heureFin: null)));
Assert.Null(TempsObligatoireRules.PlageTexte(null));
}
[Fact]
public void PlusGrandCreneauLibreMinutes_RendezVousHorsPlage_LaisseTouteLaPlage()
{
// Rendez-vous du matin : toute la plage 12:00 → 15:00 reste libre.
Assert.Equal(180, TempsObligatoireRules.PlusGrandCreneauLibreMinutes(
PauseDejeuner(), Heure(8), Heure(11)));
}
[Fact]
public void EstObligatoire_RendezVousTermineAvantLaPlage_NAPasEteObligatoire()
{
// Matinée 8:00 → 9:00 (plus d'une heure de rendez-vous) : la pause reste
// possible dans la plage horaire, elle n'est donc pas obligatoire.
bool obligatoire = TempsObligatoireRules.EstObligatoire(
PauseDejeuner(), TypeRdvIntervention(), "T.RDV.INTER", "Intervention", Heure(8), Heure(9));
Assert.False(obligatoire);
}
}