use ifc_control::{create_control, ControlDraft, ControlError, ControlKind};
use ifc_model::{Model, Transaction, Value};
use ifc_schema::{ifc4, ifc4x3};
const GUID: &str = "0RSPnzHdf5hAmvCJDbRDzy";
fn named(name: &str) -> ControlDraft<'_> {
ControlDraft::new().name(name)
}
fn invalid(err: &ControlError) -> bool {
matches!(err, ControlError::AuthoringInvalid { .. })
}
#[test]
fn attributes_land_in_the_declared_slots() {
let mut model = Model::default();
let mut tx = Transaction::new(&model);
let draft = ControlDraft::new()
.name("Demolition consent")
.description("Phase 1")
.identification("PRM-2026-014")
.status("GRANTED")
.long_description("Covers the east wing only.");
let permit = create_control(
&mut tx,
ifc4(),
ControlKind::Permit,
GUID,
Some("BUILDING"),
draft,
)
.expect("permit");
tx.commit(&mut model).expect("commit");
let entity = model.get(permit).expect("entity");
assert_eq!(entity.type_name.as_ref(), "IFCPERMIT");
assert_eq!(entity.attributes.len(), 9, "declared arity");
assert_eq!(entity.attributes[0], Value::Text(GUID.into()));
assert_eq!(
entity.attributes[2],
Value::Text("Demolition consent".into())
);
assert_eq!(entity.attributes[5], Value::Text("PRM-2026-014".into()));
assert_eq!(entity.attributes[6], Value::Enum("BUILDING".into()));
assert_eq!(entity.attributes[7], Value::Text("GRANTED".into()));
}
#[test]
fn performance_history_has_its_own_tail() {
let mut model = Model::default();
let mut tx = Transaction::new(&model);
let draft = ControlDraft::new()
.name("Chiller COP")
.life_cycle_phase("OPERATION");
let history = create_control(
&mut tx,
ifc4(),
ControlKind::PerformanceHistory,
GUID,
Some("NOTDEFINED"),
draft,
)
.expect("history");
tx.commit(&mut model).expect("commit");
let entity = model.get(history).expect("entity");
assert_eq!(entity.attributes.len(), 8, "declared arity");
assert_eq!(entity.attributes[6], Value::Text("OPERATION".into()));
assert_eq!(entity.attributes[7], Value::Enum("NOTDEFINED".into()));
}
#[test]
fn a_performance_history_without_a_phase_is_refused() {
let model = Model::default();
let mut tx = Transaction::new(&model);
let err = create_control(
&mut tx,
ifc4(),
ControlKind::PerformanceHistory,
GUID,
None,
named("Chiller COP"),
)
.expect_err("LifeCyclePhase");
assert!(invalid(&err), "{err}");
assert!(tx.is_empty(), "nothing is staged when the rule fails");
}
#[test]
fn undeclared_attributes_are_refused() {
let model = Model::default();
let mut tx = Transaction::new(&model);
for draft in [
ControlDraft::new()
.name("H")
.life_cycle_phase("OPERATION")
.status("OPEN"),
ControlDraft::new()
.name("H")
.life_cycle_phase("OPERATION")
.long_description("..."),
] {
let err = create_control(
&mut tx,
ifc4(),
ControlKind::PerformanceHistory,
GUID,
None,
draft,
)
.expect_err("not declared");
assert!(invalid(&err), "{err}");
}
for kind in [
ControlKind::Permit,
ControlKind::ProjectOrder,
ControlKind::ActionRequest,
] {
let draft = ControlDraft::new().name("C").life_cycle_phase("OPERATION");
let err =
create_control(&mut tx, ifc4(), kind, GUID, None, draft).expect_err("not declared");
assert!(invalid(&err), "{err}");
}
assert!(tx.is_empty(), "nothing is staged when the rule fails");
}
#[test]
fn a_token_from_another_control_is_refused() {
let model = Model::default();
let mut tx = Transaction::new(&model);
for (kind, token) in [
(ControlKind::Permit, "WORKORDER"),
(ControlKind::Permit, "PHONE"),
(ControlKind::ActionRequest, "BUILDING"),
(ControlKind::ProjectOrder, "ACCESS"),
(ControlKind::PerformanceHistory, "WORKORDER"),
] {
let err = create_control(&mut tx, ifc4(), kind, GUID, Some(token), named("C"))
.expect_err("foreign token");
assert!(invalid(&err), "{err}");
}
for (kind, token) in [
(ControlKind::Permit, "BUILDING"),
(ControlKind::ProjectOrder, "WORKORDER"),
(ControlKind::ActionRequest, "PHONE"),
(ControlKind::PerformanceHistory, "NOTDEFINED"),
] {
let mut draft = ControlDraft::new().name("C");
if kind == ControlKind::PerformanceHistory {
draft = draft.life_cycle_phase("OPERATION");
}
create_control(&mut tx, ifc4(), kind, GUID, Some(token), draft).expect("own token");
}
}
#[test]
fn userdefined_without_an_object_type_is_refused() {
let model = Model::default();
let mut tx = Transaction::new(&model);
let err = create_control(
&mut tx,
ifc4(),
ControlKind::Permit,
GUID,
Some("USERDEFINED"),
named("Consent"),
)
.expect_err("USERDEFINED");
assert!(invalid(&err), "{err}");
let blank = ControlDraft::new().name("Consent").object_type(" ");
create_control(
&mut tx,
ifc4(),
ControlKind::Permit,
GUID,
Some("USERDEFINED"),
blank,
)
.expect_err("blank ObjectType");
assert!(tx.is_empty(), "nothing is staged when the rule fails");
let ok = ControlDraft::new()
.name("Consent")
.object_type("Heritage consent");
create_control(
&mut tx,
ifc4(),
ControlKind::Permit,
GUID,
Some("USERDEFINED"),
ok,
)
.expect("named USERDEFINED");
}
#[test]
fn identity_is_validated() {
let model = Model::default();
let mut tx = Transaction::new(&model);
create_control(
&mut tx,
ifc4(),
ControlKind::Permit,
"not-a-guid",
None,
named("Consent"),
)
.expect_err("GlobalId");
for name in ["", " "] {
create_control(
&mut tx,
ifc4(),
ControlKind::Permit,
GUID,
None,
named(name),
)
.expect_err("Name");
}
assert!(tx.is_empty(), "nothing is staged when the rule fails");
}
#[test]
fn the_writer_agrees_with_both_schemas() {
for schema in [ifc4(), ifc4x3()] {
for (kind, arity) in [
(ControlKind::Permit, 9),
(ControlKind::ProjectOrder, 9),
(ControlKind::ActionRequest, 9),
(ControlKind::PerformanceHistory, 8),
] {
let name = kind.type_name();
let declared = schema.attributes(name);
assert_eq!(
declared.len(),
arity,
"{name} arity drifted in {}",
schema.name()
);
assert_eq!(
declared[5].name.to_ascii_uppercase(),
"IDENTIFICATION",
"{name} slot 5"
);
let mut model = Model::default();
let mut tx = Transaction::new(&model);
for token in kind.members() {
let mut draft = ControlDraft::new().name("C").object_type("named");
if kind == ControlKind::PerformanceHistory {
draft = draft.life_cycle_phase("OPERATION");
}
create_control(&mut tx, schema, kind, GUID, Some(token), draft)
.expect("declared token");
}
tx.commit(&mut model).expect("commit");
}
}
}