from datetime import date, time from app_new.core.services.day_planner import DayPlanner, PlanningCandidate, WorkWindow def test_proposal_respects_pause_and_is_deterministic(monkeypatch): monkeypatch.setattr( DayPlanner, "work_windows", staticmethod(lambda day, user_id=None: [WorkWindow(time(8), time(12)), WorkWindow(time(13, 30), time(17))]), ) candidates = [ PlanningCandidate("scheduled_task", 2, "Contrôle B", duration_minutes=45), PlanningCandidate("scheduled_task", 1, "Contrôle A", duration_minutes=45), ] first = DayPlanner.propose(date(2026, 8, 24), candidates) assert [(item.proposed_start, item.proposed_end) for item in first] == [ (time(8), time(8, 45)), (time(8, 45), time(9, 30)) ] assert all(item.proposed_end <= time(12) for item in first) second = DayPlanner.propose(date(2026, 8, 24), [ PlanningCandidate("scheduled_task", 2, "Contrôle B", duration_minutes=45), PlanningCandidate("scheduled_task", 1, "Contrôle A", duration_minutes=45), ]) assert [(item.proposed_start, item.proposed_end) for item in first] == [ (item.proposed_start, item.proposed_end) for item in second ] def test_fixed_event_is_preserved(monkeypatch): monkeypatch.setattr(DayPlanner, "work_windows", staticmethod(lambda day, user_id=None: [WorkWindow(time(8), time(17))])) fixed = PlanningCandidate("company", 1, "Entreprise", constraint="fixed", fixed_start=time(9), fixed_end=time(10)) flexible = PlanningCandidate("admin", 2, "Commande", duration_minutes=30) result = DayPlanner.propose(date(2026, 8, 24), [flexible, fixed]) assert next(item for item in result if item.source_id == 1).proposed_start == time(9) assert next(item for item in result if item.source_id == 2).proposed_end <= time(9) def test_task_too_long_is_marked_to_reschedule(monkeypatch): monkeypatch.setattr(DayPlanner, "work_windows", staticmethod(lambda day, user_id=None: [WorkWindow(time(8), time(9))])) result = DayPlanner.propose(date(2026, 8, 24), [PlanningCandidate("admin", 1, "Tâche", duration_minutes=90)]) assert result[0].status == "à replanifier" assert result[0].proposed_start is None def test_no_work_window_explains_missing_schedule(monkeypatch): monkeypatch.setattr(DayPlanner, "work_windows", staticmethod(lambda day, user_id=None: [])) result = DayPlanner.propose(date(2026, 8, 24), [PlanningCandidate("admin", 1, "Tâche")]) assert "horaire de travail" in result[0].explanation