use ifc_control::{
assign_to_control, create_control, ControlAssignmentDraft, ControlDraft, ControlError,
ControlKind,
};
use ifc_model::{Entity, EntityId, Model, Transaction, Value};
use ifc_schema::{ifc4, ifc4x3, Schema};
const CONTROL_GUID: &str = "0RSPnzHdf5hAmvCJDbRDzy";
const REL_GUID: &str = "1kTvXnbbzCWw8lcMd1dR4o";
const RELATED: usize = 4;
const RELATED_TYPE: usize = 5;
const RELATING: usize = 6;
fn model_with(types: &[&str]) -> (Model, Vec<EntityId>) {
let mut model = Model::default();
let ids = (1..)
.zip(types)
.map(|(n, type_name)| {
let id = EntityId(n);
model.insert(id, Entity::new(*type_name, vec![Value::Null]));
id
})
.collect();
(model, ids)
}
fn stage_control(tx: &mut Transaction, schema: &Schema, kind: ControlKind) -> EntityId {
let mut draft = ControlDraft::new().name("Control");
draft.life_cycle_phase = (kind == ControlKind::PerformanceHistory).then_some("OPERATION");
create_control(tx, schema, kind, CONTROL_GUID, None, draft).expect("control")
}
fn draft(control: EntityId, related: &[EntityId]) -> ControlAssignmentDraft<'_> {
ControlAssignmentDraft::new(REL_GUID, control, related).name("Governed work")
}
fn invalid_attribute(err: &ControlError) -> Option<&'static str> {
match err {
ControlError::AuthoringInvalid { attribute, .. } => Some(attribute),
_ => None,
}
}
#[test]
fn slot_constants_agree_with_both_schemas() {
for schema in [ifc4(), ifc4x3()] {
let names = schema.attribute_names("IFCRELASSIGNSTOCONTROL");
assert_eq!(names.len(), 7, "{}", schema.name());
assert_eq!(names[RELATED], "RelatedObjects", "{}", schema.name());
assert_eq!(
names[RELATED_TYPE],
"RelatedObjectsType",
"{}",
schema.name()
);
assert_eq!(names[RELATING], "RelatingControl", "{}", schema.name());
}
}
#[test]
fn every_owned_control_is_assigned_in_both_schemas() {
for schema in [ifc4(), ifc4x3()] {
for kind in ControlKind::ALL {
let (mut model, ids) = model_with(&["IFCTASK", "IFCWALL"]);
let mut tx = Transaction::new(&model);
let control = stage_control(&mut tx, schema, kind);
let rel = assign_to_control(&mut tx, &model, schema, draft(control, &ids))
.unwrap_or_else(|e| panic!("{kind:?} in {}: {e}", schema.name()));
tx.commit(&mut model).expect("commit");
let record = model.get(rel).expect("relationship");
assert_eq!(record.type_name.as_ref(), "IFCRELASSIGNSTOCONTROL");
assert_eq!(record.attributes.len(), 7);
assert_eq!(record.attributes[0], Value::Text(REL_GUID.into()));
assert_eq!(record.attributes[2], Value::Text("Governed work".into()));
assert_eq!(
record.attributes[RELATED],
Value::List(vec![Value::Ref(ids[0]), Value::Ref(ids[1])])
);
assert_eq!(record.attributes[RELATING], Value::Ref(control));
}
}
}
#[test]
fn a_committed_control_is_assignable() {
let (mut model, ids) = model_with(&["IFCTASK"]);
let mut tx = Transaction::new(&model);
let permit = stage_control(&mut tx, ifc4(), ControlKind::Permit);
tx.commit(&mut model).expect("commit");
let mut tx = Transaction::new(&model);
assign_to_control(&mut tx, &model, ifc4(), draft(permit, &ids)).expect("assignment");
assert_eq!(tx.len(), 1);
}
#[test]
fn related_objects_type_is_left_unset() {
for schema in [ifc4(), ifc4x3()] {
let (mut model, ids) = model_with(&["IFCTASK"]);
let mut tx = Transaction::new(&model);
let order = stage_control(&mut tx, schema, ControlKind::ProjectOrder);
let rel = assign_to_control(&mut tx, &model, schema, draft(order, &ids)).expect("ok");
tx.commit(&mut model).expect("commit");
assert_eq!(
model.get(rel).expect("rel").attributes[RELATED_TYPE],
Value::Null,
"{}",
schema.name()
);
}
}
#[test]
fn a_control_owned_elsewhere_is_refused() {
let foreign = [
"IFCCOSTSCHEDULE",
"IFCCOSTITEM",
"IFCWORKSCHEDULE",
"IFCWORKPLAN",
"IFCWORKCALENDAR",
"IFCTASK",
];
let (model, ids) = model_with(&[&foreign[..], &["IFCWALL"]].concat());
let wall = [ids[foreign.len()]];
for (control, type_name) in ids.iter().zip(foreign) {
let mut tx = Transaction::new(&model);
let err = assign_to_control(&mut tx, &model, ifc4(), draft(*control, &wall))
.expect_err(type_name);
assert_eq!(
err,
ControlError::ForeignControl {
id: *control,
actual: type_name.into(),
}
);
assert!(tx.is_empty(), "{type_name}: refusal staged nothing");
}
}
#[test]
fn missing_references_are_refused() {
let (model, ids) = model_with(&["IFCTASK"]);
let task = ids[0];
let mut tx = Transaction::new(&model);
let permit = stage_control(&mut tx, ifc4(), ControlKind::Permit);
let ghost = EntityId(9_999);
let err = assign_to_control(&mut tx, &model, ifc4(), draft(ghost, &[task])).expect_err("ghost");
assert_eq!(err, ControlError::UnknownEntity { id: ghost });
let err =
assign_to_control(&mut tx, &model, ifc4(), draft(permit, &[task, ghost])).expect_err("x");
assert_eq!(err, ControlError::UnknownEntity { id: ghost });
tx.remove(task);
let err = assign_to_control(&mut tx, &model, ifc4(), draft(permit, &[task])).expect_err("rm");
assert_eq!(err, ControlError::UnknownEntity { id: task });
}
#[test]
fn empty_or_duplicate_related_objects_are_refused() {
let (model, ids) = model_with(&["IFCTASK"]);
let task = ids[0];
let mut tx = Transaction::new(&model);
let request = stage_control(&mut tx, ifc4(), ControlKind::ActionRequest);
let before = tx.len();
let err = assign_to_control(&mut tx, &model, ifc4(), draft(request, &[])).expect_err("empty");
assert_eq!(invalid_attribute(&err), Some("RelatedObjects"));
let err =
assign_to_control(&mut tx, &model, ifc4(), draft(request, &[task, task])).expect_err("dup");
assert_eq!(invalid_attribute(&err), Some("RelatedObjects"));
assert_eq!(tx.len(), before, "refusals staged nothing");
}
#[test]
fn the_control_among_its_related_objects_is_refused() {
let (model, ids) = model_with(&["IFCTASK"]);
let mut tx = Transaction::new(&model);
let history = stage_control(&mut tx, ifc4x3(), ControlKind::PerformanceHistory);
let err = assign_to_control(
&mut tx,
&model,
ifc4x3(),
draft(history, &[ids[0], history]),
)
.expect_err("self");
assert_eq!(invalid_attribute(&err), Some("RelatedObjects"));
}
#[test]
fn a_related_object_must_be_an_object_definition() {
let (model, ids) = model_with(&["IFCCARTESIANPOINT", "IFCWALLTYPE"]);
let mut tx = Transaction::new(&model);
let permit = stage_control(&mut tx, ifc4(), ControlKind::Permit);
let err =
assign_to_control(&mut tx, &model, ifc4(), draft(permit, &[ids[0]])).expect_err("point");
assert_eq!(invalid_attribute(&err), Some("RelatedObjects"));
assign_to_control(&mut tx, &model, ifc4(), draft(permit, &[ids[1]])).expect("type object");
}
#[test]
fn a_malformed_guid_is_refused() {
let (model, ids) = model_with(&["IFCTASK"]);
let mut tx = Transaction::new(&model);
let permit = stage_control(&mut tx, ifc4(), ControlKind::Permit);
let mut bad = draft(permit, &ids);
bad.global_id = "not-a-guid";
let err = assign_to_control(&mut tx, &model, ifc4(), bad).expect_err("guid");
assert_eq!(invalid_attribute(&err), Some("GlobalId"));
}