187 lines
7.1 KiB
Rust
187 lines
7.1 KiB
Rust
#[cfg(test)]
|
|
mod integration_tests {
|
|
use rota_lib::{
|
|
config::{ToxicPair, UserConfig},
|
|
resident::Resident,
|
|
schedule::{MonthlySchedule, ShiftType},
|
|
scheduler::Scheduler,
|
|
slot::{Day, ShiftPosition, Slot},
|
|
workload::{WorkloadBounds, WorkloadTracker},
|
|
};
|
|
use rstest::{fixture, rstest};
|
|
|
|
#[fixture]
|
|
fn minimal_config() -> UserConfig {
|
|
UserConfig::new(2).with_residents(vec![
|
|
Resident::new("1", "R1"),
|
|
Resident::new("2", "R2"),
|
|
Resident::new("3", "R3"),
|
|
Resident::new("4", "R4"),
|
|
])
|
|
}
|
|
|
|
#[fixture]
|
|
fn maximal_config() -> UserConfig {
|
|
UserConfig::new(2)
|
|
.with_holidays(vec![2, 3, 10, 11, 12, 25])
|
|
.with_residents(vec![
|
|
Resident::new("1", "R1").with_max_shifts(3),
|
|
Resident::new("2", "R2").with_max_shifts(4),
|
|
Resident::new("3", "R3").with_reduced_load(),
|
|
Resident::new("4", "R4").with_allowed_types(vec![ShiftType::Closed]),
|
|
Resident::new("5", "R5")
|
|
.with_allowed_types(vec![ShiftType::OpenFirst, ShiftType::OpenSecond]),
|
|
Resident::new("6", "R6").with_negative_shifts(vec![Day(5), Day(15), Day(25)]),
|
|
Resident::new("7", "R7"),
|
|
Resident::new("8", "R8"),
|
|
Resident::new("9", "R9"),
|
|
Resident::new("10", "R10"),
|
|
])
|
|
.with_toxic_pairs(vec![
|
|
ToxicPair::new("1", "2"),
|
|
ToxicPair::new("3", "4"),
|
|
ToxicPair::new("7", "8"),
|
|
])
|
|
}
|
|
|
|
#[fixture]
|
|
fn manual_shifts_heavy_config() -> UserConfig {
|
|
UserConfig::new(2).with_residents(vec![
|
|
Resident::new("1", "R1").with_manual_shifts(vec![
|
|
Slot::new(Day(1), ShiftPosition::First),
|
|
Slot::new(Day(3), ShiftPosition::First),
|
|
Slot::new(Day(5), ShiftPosition::Second),
|
|
]),
|
|
Resident::new("2", "R2").with_manual_shifts(vec![
|
|
Slot::new(Day(2), ShiftPosition::First),
|
|
Slot::new(Day(4), ShiftPosition::First),
|
|
]),
|
|
Resident::new("3", "R3"),
|
|
Resident::new("4", "R4"),
|
|
Resident::new("5", "R5"),
|
|
Resident::new("6", "R6"),
|
|
])
|
|
}
|
|
|
|
#[fixture]
|
|
fn complex_config() -> UserConfig {
|
|
UserConfig::new(2)
|
|
.with_holidays(vec![5, 12, 19, 26])
|
|
.with_residents(vec![
|
|
Resident::new("1", "R1")
|
|
.with_max_shifts(3)
|
|
.with_negative_shifts(vec![Day(1), Day(2), Day(3)]),
|
|
Resident::new("2", "R2")
|
|
.with_max_shifts(3)
|
|
.with_negative_shifts(vec![Day(4), Day(5), Day(6)]),
|
|
Resident::new("3", "R3")
|
|
.with_max_shifts(3)
|
|
.with_negative_shifts(vec![Day(7), Day(8), Day(9)]),
|
|
Resident::new("4", "R4").with_allowed_types(vec![ShiftType::Closed]),
|
|
Resident::new("5", "R5")
|
|
.with_allowed_types(vec![ShiftType::OpenFirst, ShiftType::OpenSecond]),
|
|
Resident::new("6", "R6"),
|
|
Resident::new("7", "R7"),
|
|
Resident::new("8", "R8"),
|
|
])
|
|
.with_toxic_pairs(vec![
|
|
ToxicPair::new("1", "2"),
|
|
ToxicPair::new("2", "3"),
|
|
ToxicPair::new("5", "6"),
|
|
ToxicPair::new("6", "7"),
|
|
])
|
|
}
|
|
|
|
#[rstest]
|
|
fn test_minimal_config(minimal_config: UserConfig) {
|
|
let mut schedule = MonthlySchedule::new();
|
|
let mut tracker = WorkloadTracker::default();
|
|
let scheduler = Scheduler::new_with_config(minimal_config.clone());
|
|
|
|
assert!(scheduler.run(&mut schedule, &mut tracker));
|
|
validate_all_constraints(&schedule, &tracker, &minimal_config);
|
|
}
|
|
|
|
#[rstest]
|
|
fn test_maximal_config(maximal_config: UserConfig) {
|
|
let mut schedule = MonthlySchedule::new();
|
|
let mut tracker = WorkloadTracker::default();
|
|
let scheduler = Scheduler::new_with_config(maximal_config.clone());
|
|
|
|
assert!(scheduler.run(&mut schedule, &mut tracker));
|
|
validate_all_constraints(&schedule, &tracker, &maximal_config);
|
|
}
|
|
|
|
#[rstest]
|
|
fn test_manual_shifts_heavy_config(manual_shifts_heavy_config: UserConfig) {
|
|
let mut schedule = MonthlySchedule::new();
|
|
let mut tracker = WorkloadTracker::default();
|
|
let scheduler = Scheduler::new_with_config(manual_shifts_heavy_config.clone());
|
|
|
|
assert!(scheduler.run(&mut schedule, &mut tracker));
|
|
validate_all_constraints(&schedule, &tracker, &manual_shifts_heavy_config);
|
|
}
|
|
|
|
#[rstest]
|
|
fn test_complex_config(complex_config: UserConfig) {
|
|
let mut schedule = MonthlySchedule::new();
|
|
let mut tracker = WorkloadTracker::default();
|
|
let scheduler = Scheduler::new_with_config(complex_config.clone());
|
|
|
|
assert!(scheduler.run(&mut schedule, &mut tracker));
|
|
validate_all_constraints(&schedule, &tracker, &complex_config);
|
|
}
|
|
|
|
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 res in current {
|
|
assert!(!previous.contains(&res));
|
|
}
|
|
}
|
|
|
|
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.clone(), id2.clone()));
|
|
assert!(config.toxic_pairs.iter().all(|t| !t.matches(&pair)));
|
|
}
|
|
}
|
|
}
|
|
|
|
let bounds = WorkloadBounds::new_with_config(config);
|
|
for (slot, res_id) in &schedule.0 {
|
|
let res = config
|
|
.residents
|
|
.iter()
|
|
.find(|r| &r.id == res_id)
|
|
.expect("Resident not found");
|
|
assert!(res.allowed_types.contains(&slot.shift_type()));
|
|
assert!(!res.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);
|
|
}
|
|
}
|
|
}
|