use ifc_model::codec::Codec;
use ifc_model::{Model, Transaction};
use ifc_schedule::schedule::{work_schedules, WorkControlKind};
use ifc_schedule::{
assign_tasks_to_control, create_task, create_work_calendar, create_work_control,
create_work_time, nest_tasks, subtasks_of, tasks_of_schedule, work_calendars, TaskDraft,
WorkControlDraft,
};
fn task(tx: &mut Transaction, guid: &str, name: &str) -> ifc_model::EntityId {
create_task(tx, TaskDraft::new(guid).name(name)).expect("authored task")
}
#[test]
fn an_authored_programme_reads_back_through_the_queries() {
let mut model = Model::default();
let mut tx = Transaction::new(&model);
let schedule = create_work_control(
&mut tx,
WorkControlKind::Schedule,
WorkControlDraft::new(
"0aBcDeFgHiJkLmNoPqRsTu",
"2026-01-05T08:00:00",
"2026-01-05T08:00:00",
)
.name("Programme")
.finish_time("2026-06-30T17:00:00"),
)
.expect("authored schedule");
let parent = task(&mut tx, "1aBcDeFgHiJkLmNoPqRsTu", "Substructure");
let first = task(&mut tx, "2aBcDeFgHiJkLmNoPqRsTu", "Excavate");
let second = task(&mut tx, "3aBcDeFgHiJkLmNoPqRsTu", "Pour");
assign_tasks_to_control(&mut tx, "04BcDeFgHiJkLmNoPqRsTu", schedule, &[parent])
.expect("assigned");
nest_tasks(&mut tx, "05BcDeFgHiJkLmNoPqRsTu", parent, &[first, second]).expect("nested");
tx.commit(&mut model).expect("commit");
let found = work_schedules(&model).expect("bound");
assert_eq!(found.len(), 1, "one schedule");
assert_eq!(found[0].name(), Some("Programme"));
assert_eq!(
found[0].start_time().and_then(|d| d.text()),
Some("2026-01-05T08:00:00")
);
assert_eq!(
tasks_of_schedule(&model, schedule).expect("bound"),
vec![parent]
);
assert_eq!(
subtasks_of(&model, parent).expect("bound"),
vec![first, second]
);
}
#[test]
fn an_authored_calendar_separates_working_from_exception_time() {
let mut model = Model::default();
let mut tx = Transaction::new(&model);
let shift = create_work_time(
&mut tx,
Some("Weekday shift"),
None,
Some("2026-01-05T08:00:00"),
Some("2026-12-18T17:00:00"),
)
.expect("authored working time");
let holiday = create_work_time(
&mut tx,
Some("Shutdown"),
None,
Some("2026-08-03T00:00:00"),
Some("2026-08-14T23:59:59"),
)
.expect("authored exception time");
let calendar = create_work_calendar(
&mut tx,
"06BcDeFgHiJkLmNoPqRsTu",
Some("Site calendar"),
&[shift],
&[holiday],
Some("FIRSTSHIFT"),
)
.expect("authored calendar");
tx.commit(&mut model).expect("commit");
let found = work_calendars(&model);
assert_eq!(found.len(), 1, "one calendar");
assert_eq!(found[0].id(), calendar);
let working: Vec<_> = found[0]
.working_times(&model)
.into_iter()
.map(|t| t.id)
.collect();
let exceptions: Vec<_> = found[0]
.exception_times(&model)
.into_iter()
.map(|t| t.id)
.collect();
assert_eq!(working, vec![shift]);
assert_eq!(exceptions, vec![holiday]);
}
#[test]
fn values_that_would_parse_but_mean_nothing_are_refused() {
let model = Model::default();
let mut tx = Transaction::new(&model);
let anchor = task(&mut tx, "1aBcDeFgHiJkLmNoPqRsTu", "Anchor");
assert!(create_work_control(
&mut tx,
WorkControlKind::Schedule,
WorkControlDraft::new("0aBcDeFgHiJkLmNoPqRsTu", "2026-01-05T08:00:00", " "),
)
.is_err());
assert!(assign_tasks_to_control(&mut tx, "04BcDeFgHiJkLmNoPqRsTu", anchor, &[]).is_err());
assert!(nest_tasks(&mut tx, "05BcDeFgHiJkLmNoPqRsTu", anchor, &[anchor]).is_err());
assert!(create_work_calendar(&mut tx, "06BcDeFgHiJkLmNoPqRsTu", None, &[], &[], None).is_err());
assert!(create_work_calendar(&mut tx, "not-a-guid", None, &[anchor], &[], None).is_err());
}
#[test]
fn an_authored_programme_survives_step_text() {
let mut model = Model::default();
*model.header_mut() = ifc_model::Header {
schema: vec!["IFC4X3_ADD2".to_owned()],
..ifc_model::Header::default()
};
let mut tx = Transaction::new(&model);
let schedule = create_work_control(
&mut tx,
WorkControlKind::Schedule,
WorkControlDraft::new(
"0aBcDeFgHiJkLmNoPqRsTu",
"2026-01-05T08:00:00",
"2026-01-05T08:00:00",
)
.name("Programme"),
)
.expect("authored schedule");
let parent = task(&mut tx, "1aBcDeFgHiJkLmNoPqRsTu", "Substructure");
let child = task(&mut tx, "2aBcDeFgHiJkLmNoPqRsTu", "Excavate");
assign_tasks_to_control(&mut tx, "04BcDeFgHiJkLmNoPqRsTu", schedule, &[parent])
.expect("assigned");
nest_tasks(&mut tx, "05BcDeFgHiJkLmNoPqRsTu", parent, &[child]).expect("nested");
tx.commit(&mut model).expect("commit");
let mut bytes = Vec::new();
ifc_step::StepCodec
.write(&model, &mut bytes)
.expect("written");
let reparsed = ifc_step::StepCodec.read_bytes(&bytes).expect("reparsed");
let found = work_schedules(&reparsed).expect("bound");
assert_eq!(found.len(), 1, "one schedule after the round trip");
assert_eq!(found[0].name(), Some("Programme"));
let tasks = tasks_of_schedule(&reparsed, found[0].id()).expect("bound");
assert_eq!(tasks.len(), 1, "the assignment survived");
assert_eq!(
subtasks_of(&reparsed, tasks[0]).expect("bound").len(),
1,
"nesting survived"
);
}