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); } }