#[cfg(test)] mod integration_tests { use rota_lib::{ config::{ToxicPair, UserConfig}, fixtures::{ complex_config, hard_config, manual_shifts_heavy_config, maximal_config, minimal_config, }, schedule::MonthlySchedule, scheduler::Scheduler, slot::{Day, ShiftPosition, Slot}, workload::{WorkloadBounds, WorkloadTracker}, }; use rstest::rstest; #[ctor::ctor] fn global_setup() { env_logger::builder() .filter_level(log::LevelFilter::Info) .init(); } #[rstest] fn test_minimal_config(minimal_config: UserConfig) -> anyhow::Result<()> { let mut schedule = MonthlySchedule::new(); let mut tracker = WorkloadTracker::default(); let scheduler = Scheduler::new_with_config(minimal_config.clone()); let solved = scheduler.run(&mut schedule, &mut tracker)?; println!("{}", schedule.report(&minimal_config, &tracker)); assert!(solved); validate_all_constraints(&schedule, &tracker, &minimal_config); Ok(()) } #[rstest] fn test_maximal_config(maximal_config: UserConfig) -> anyhow::Result<()> { let mut schedule = MonthlySchedule::new(); let mut tracker = WorkloadTracker::default(); let scheduler = Scheduler::new_with_config(maximal_config.clone()); let solved = scheduler.run(&mut schedule, &mut tracker)?; println!("{}", schedule.report(&maximal_config, &tracker)); assert!(solved); validate_all_constraints(&schedule, &tracker, &maximal_config); Ok(()) } #[rstest] fn test_manual_shifts_heavy_config( manual_shifts_heavy_config: UserConfig, ) -> anyhow::Result<()> { let mut schedule = MonthlySchedule::new(); let mut tracker = WorkloadTracker::default(); let scheduler = Scheduler::new_with_config(manual_shifts_heavy_config.clone()); let solved = scheduler.run(&mut schedule, &mut tracker)?; println!("{}", schedule.report(&manual_shifts_heavy_config, &tracker)); assert!(solved); validate_all_constraints(&schedule, &tracker, &manual_shifts_heavy_config); Ok(()) } #[rstest] fn test_complex_config(complex_config: UserConfig) -> anyhow::Result<()> { let mut schedule = MonthlySchedule::new(); let mut tracker = WorkloadTracker::default(); let scheduler = Scheduler::new_with_config(complex_config.clone()); let solved = scheduler.run(&mut schedule, &mut tracker)?; println!("{}", schedule.report(&complex_config, &tracker)); assert!(solved); validate_all_constraints(&schedule, &tracker, &complex_config); Ok(()) } #[rstest] fn test_hard_config(hard_config: UserConfig) -> anyhow::Result<()> { let mut schedule = MonthlySchedule::new(); let mut tracker = WorkloadTracker::default(); let scheduler = Scheduler::new_with_config(hard_config.clone()); let solved = scheduler.run(&mut schedule, &mut tracker)?; println!("{}", schedule.report(&hard_config, &tracker)); assert!(solved); validate_all_constraints(&schedule, &tracker, &hard_config); Ok(()) } fn validate_all_constraints( schedule: &MonthlySchedule, tracker: &WorkloadTracker, config: &UserConfig, ) { assert_eq!(schedule.0.len() as u8, config.total_slots); for d in 2..=config.total_days { let current: Vec<_> = [ShiftPosition::First, ShiftPosition::Second] .iter() .filter_map(|&p| schedule.get_resident_id(&Slot::new(Day(d), p))) .collect(); let previous: Vec<_> = [ShiftPosition::First, ShiftPosition::Second] .iter() .filter_map(|&p| schedule.get_resident_id(&Slot::new(Day(d - 1), p))) .collect(); for r in current { assert!(!previous.contains(&r)); } } for d in 1..=config.total_days { let day = Day(d); if day.is_open_shift() { let r1 = schedule.get_resident_id(&Slot::new(day, ShiftPosition::First)); let r2 = schedule.get_resident_id(&Slot::new(day, ShiftPosition::Second)); assert_ne!(r1, r2); if let (Some(id1), Some(id2)) = (r1, r2) { let pair = ToxicPair::from((*id1, *id2)); assert!(config.toxic_pairs.iter().all(|t| !t.matches(&pair))); } } } let bounds = WorkloadBounds::new_with_config(config); for (slot, r_id) in &schedule.0 { let r = config .residents .iter() .find(|r| &r.id == r_id) .expect("Resident not found"); assert!(r.allowed_types.contains(&slot.shift_type())); assert!(!r.negative_shifts.contains(&slot.day)); } for resident in &config.residents { let workload = tracker.current_workload(&resident.id); let max = *bounds.max_workloads.get(&resident.id).unwrap(); assert!(workload <= max, "workload: {}, max: {}", workload, max); } } }