""" Tests pour le service Scheduler. Couvre: - Validation des expressions cron - Création/mise à jour/suppression de schedules - Pause/Resume - Statistiques - Exécution planifiée (mocked) """ import pytest from datetime import datetime, timezone, timedelta from unittest.mock import patch, AsyncMock, MagicMock from freezegun import freeze_time pytestmark = pytest.mark.unit class TestSchedulerCronValidation: """Tests pour la validation des expressions cron.""" def test_validate_cron_valid_expression(self): """Expression cron valide retourne valid=True.""" from app.services.scheduler_service import SchedulerService service = SchedulerService() result = service.validate_cron_expression("0 2 * * *") assert result["valid"] is True assert result["expression"] == "0 2 * * *" assert result["error"] is None assert len(result["next_runs"]) == 5 def test_validate_cron_invalid_expression(self): """Expression cron invalide retourne valid=False.""" from app.services.scheduler_service import SchedulerService service = SchedulerService() result = service.validate_cron_expression("invalid cron") assert result["valid"] is False assert result["error"] is not None def test_validate_cron_edge_cases(self): """Expressions cron valides complexes.""" from app.services.scheduler_service import SchedulerService service = SchedulerService() # Every hour result = service.validate_cron_expression("0 * * * *") assert result["valid"] is True # Every Monday at 9am result = service.validate_cron_expression("0 9 * * 1") assert result["valid"] is True # First of every month result = service.validate_cron_expression("0 0 1 * *") assert result["valid"] is True class TestSchedulerServiceCache: """Tests pour la gestion du cache des schedules.""" def test_get_all_schedules_empty(self): """Cache vide retourne liste vide.""" from app.services.scheduler_service import SchedulerService service = SchedulerService() schedules = service.get_all_schedules() assert schedules == [] def test_get_schedule_not_found(self): """Schedule inexistant retourne None.""" from app.services.scheduler_service import SchedulerService service = SchedulerService() schedule = service.get_schedule("nonexistent") assert schedule is None def test_add_schedule_to_cache(self): """Ajoute un schedule au cache.""" from app.services.scheduler_service import SchedulerService from app.schemas.schedule_api import Schedule service = SchedulerService() schedule = Schedule( id="test-schedule", name="Test Schedule", playbook="test.yml", target="all", schedule_type="once", enabled=False, ) service.add_schedule_to_cache(schedule) assert service.get_schedule("test-schedule") is not None def test_remove_schedule_from_cache(self): """Supprime un schedule du cache.""" from app.services.scheduler_service import SchedulerService from app.schemas.schedule_api import Schedule service = SchedulerService() schedule = Schedule( id="to-remove", name="Remove Me", playbook="test.yml", target="all", schedule_type="once", enabled=False, ) service.add_schedule_to_cache(schedule) assert service.get_schedule("to-remove") is not None service.remove_schedule_from_cache("to-remove") assert service.get_schedule("to-remove") is None class TestSchedulerServiceFiltering: """Tests pour le filtrage des schedules.""" def test_filter_by_enabled(self): """Filtre par état enabled.""" from app.services.scheduler_service import SchedulerService from app.schemas.schedule_api import Schedule service = SchedulerService() service.add_schedule_to_cache(Schedule( id="enabled-1", name="Enabled", playbook="test.yml", target="all", schedule_type="once", enabled=True )) service.add_schedule_to_cache(Schedule( id="disabled-1", name="Disabled", playbook="test.yml", target="all", schedule_type="once", enabled=False )) enabled = service.get_all_schedules(enabled=True) disabled = service.get_all_schedules(enabled=False) assert len(enabled) == 1 assert enabled[0].id == "enabled-1" assert len(disabled) == 1 assert disabled[0].id == "disabled-1" def test_filter_by_playbook(self): """Filtre par nom de playbook.""" from app.services.scheduler_service import SchedulerService from app.schemas.schedule_api import Schedule service = SchedulerService() service.add_schedule_to_cache(Schedule( id="backup-1", name="Backup", playbook="backup.yml", target="all", schedule_type="once", enabled=True )) service.add_schedule_to_cache(Schedule( id="health-1", name="Health", playbook="health-check.yml", target="all", schedule_type="once", enabled=True )) backup_schedules = service.get_all_schedules(playbook="backup") assert len(backup_schedules) == 1 assert backup_schedules[0].id == "backup-1" class TestSchedulerServicePauseResume: """Tests pour pause/resume des schedules.""" def test_pause_schedule(self): """Pause un schedule.""" from app.services.scheduler_service import SchedulerService from app.schemas.schedule_api import Schedule service = SchedulerService() schedule = Schedule( id="pausable", name="Pausable", playbook="test.yml", target="all", schedule_type="once", enabled=True ) service.add_schedule_to_cache(schedule) result = service.pause_schedule("pausable") assert result is True assert service.get_schedule("pausable").enabled is False def test_pause_nonexistent_schedule(self): """Pause d'un schedule inexistant retourne False.""" from app.services.scheduler_service import SchedulerService service = SchedulerService() result = service.pause_schedule("nonexistent") assert result is False def test_resume_schedule(self): """Resume un schedule en pause.""" from app.services.scheduler_service import SchedulerService from app.schemas.schedule_api import Schedule service = SchedulerService() schedule = Schedule( id="resumable", name="Resumable", playbook="test.yml", target="all", schedule_type="once", enabled=False ) service.add_schedule_to_cache(schedule) result = service.resume_schedule("resumable") assert result is True assert service.get_schedule("resumable").enabled is True class TestSchedulerServiceStats: """Tests pour les statistiques des schedules.""" def test_get_stats_empty(self): """Stats avec cache vide.""" from app.services.scheduler_service import SchedulerService service = SchedulerService() stats = service.get_stats() assert stats.total == 0 assert stats.active == 0 assert stats.paused == 0 def test_get_stats_with_schedules(self): """Stats avec des schedules.""" from app.services.scheduler_service import SchedulerService from app.schemas.schedule_api import Schedule service = SchedulerService() # Add mix of enabled/disabled for i in range(3): service.add_schedule_to_cache(Schedule( id=f"active-{i}", name=f"Active {i}", playbook="test.yml", target="all", schedule_type="once", enabled=True )) for i in range(2): service.add_schedule_to_cache(Schedule( id=f"paused-{i}", name=f"Paused {i}", playbook="test.yml", target="all", schedule_type="once", enabled=False )) stats = service.get_stats() assert stats.total == 5 assert stats.active == 3 assert stats.paused == 2 @freeze_time("2024-01-15 10:00:00") def test_get_upcoming_executions(self): """Récupère les prochaines exécutions.""" from app.services.scheduler_service import SchedulerService from app.schemas.schedule_api import Schedule service = SchedulerService() future_time = datetime.now(timezone.utc) + timedelta(hours=2) service.add_schedule_to_cache(Schedule( id="upcoming-1", name="Upcoming", playbook="test.yml", target="all", schedule_type="once", enabled=True, next_run_at=future_time )) upcoming = service.get_upcoming_executions(limit=5) assert len(upcoming) == 1 assert upcoming[0]["schedule_id"] == "upcoming-1" class TestSchedulerServiceUpdate: """Tests pour la mise à jour des schedules.""" def test_update_schedule_name(self): """Met à jour le nom d'un schedule.""" from app.services.scheduler_service import SchedulerService from app.schemas.schedule_api import Schedule, ScheduleUpdateRequest service = SchedulerService() schedule = Schedule( id="update-me", name="Old Name", playbook="test.yml", target="all", schedule_type="once", enabled=True ) service.add_schedule_to_cache(schedule) update = ScheduleUpdateRequest(name="New Name") updated = service.update_schedule("update-me", update) assert updated is not None assert updated.name == "New Name" def test_update_schedule_not_found(self): """Mise à jour d'un schedule inexistant retourne None.""" from app.services.scheduler_service import SchedulerService from app.schemas.schedule_api import ScheduleUpdateRequest service = SchedulerService() update = ScheduleUpdateRequest(name="New Name") result = service.update_schedule("nonexistent", update) assert result is None def test_delete_schedule(self): """Supprime un schedule.""" from app.services.scheduler_service import SchedulerService from app.schemas.schedule_api import Schedule service = SchedulerService() schedule = Schedule( id="delete-me", name="Delete Me", playbook="test.yml", target="all", schedule_type="once", enabled=True ) service.add_schedule_to_cache(schedule) result = service.delete_schedule("delete-me") assert result is True assert service.get_schedule("delete-me") is None