use super::*;
use PropertyIdentifier as P;
use bacnet_objects::access_control::{
AccessCredentialObject, AccessRightsObject, AccessUserObject, CredentialDataInputObject,
};
use bacnet_types::primitives::PropertyValue;
fn expected_rows(kind: ObjectType) -> Vec<PropertyRow> {
match kind {
ObjectType::ACCESS_CREDENTIAL => vec![
(P::OBJECT_IDENTIFIER, false, false),
(P::OBJECT_NAME, false, false),
(P::DESCRIPTION, true, true),
(P::OBJECT_TYPE, false, false),
(P::CREDENTIAL_STATUS, false, false),
(P::ASSIGNED_ACCESS_RIGHTS, false, false),
(P::AUTHENTICATION_FACTORS, false, false),
(P::STATUS_FLAGS, false, false),
(P::RELIABILITY, false, false),
(P::GLOBAL_IDENTIFIER, false, true),
(P::REASON_FOR_DISABLE, false, false),
(P::ACTIVATION_TIME, false, true),
(P::EXPIRATION_TIME, false, true),
(P::CREDENTIAL_DISABLE, false, true),
(P::PROPERTY_LIST, false, false),
],
ObjectType::ACCESS_USER => vec![
(P::OBJECT_IDENTIFIER, false, false),
(P::OBJECT_NAME, false, false),
(P::DESCRIPTION, true, true),
(P::OBJECT_TYPE, false, false),
(P::USER_TYPE, false, true),
(P::CREDENTIALS, false, false),
(P::STATUS_FLAGS, false, false),
(P::RELIABILITY, false, false),
(P::MEMBERS, true, false),
(P::MEMBER_OF, true, false),
(P::GLOBAL_IDENTIFIER, false, true),
(P::PROPERTY_LIST, false, false),
],
ObjectType::ACCESS_RIGHTS => vec![
(P::OBJECT_IDENTIFIER, false, false),
(P::OBJECT_NAME, false, false),
(P::DESCRIPTION, true, true),
(P::OBJECT_TYPE, false, false),
(P::GLOBAL_IDENTIFIER, false, true),
(P::POSITIVE_ACCESS_RULES, false, true),
(P::NEGATIVE_ACCESS_RULES, false, true),
(P::STATUS_FLAGS, false, false),
(P::RELIABILITY, false, false),
(P::LOG_ENABLE, false, true),
(P::PROPERTY_LIST, false, false),
],
_ => vec![
(P::OBJECT_IDENTIFIER, false, false),
(P::OBJECT_NAME, false, false),
(P::DESCRIPTION, true, true),
(P::OBJECT_TYPE, false, false),
(P::PRESENT_VALUE, false, true),
(P::UPDATE_TIME, false, false),
(P::SUPPORTED_FORMATS, false, false),
(P::SUPPORTED_FORMAT_CLASSES, true, false),
(P::STATUS_FLAGS, false, false),
(P::OUT_OF_SERVICE, false, true),
(P::RELIABILITY, false, true),
(P::PROPERTY_LIST, false, false),
],
}
}
#[test]
fn pics_access_identity_property_metadata_is_exact() {
let fresh: [FreshObject; 4] = [
|| {
(
Box::new(AccessCredentialObject::new(7, "CRED-7").unwrap()),
ObjectType::ACCESS_CREDENTIAL,
)
},
|| {
(
Box::new(AccessUserObject::new(7, "USER-7").unwrap()),
ObjectType::ACCESS_USER,
)
},
|| {
(
Box::new(AccessRightsObject::new(7, "AR-7").unwrap()),
ObjectType::ACCESS_RIGHTS,
)
},
|| {
(
Box::new(CredentialDataInputObject::new(7, "CDI-7").unwrap()),
ObjectType::CREDENTIAL_DATA_INPUT,
)
},
];
for make in fresh {
let expected = expected_rows(make().1);
for configured in [false, true] {
for out_of_service in [false, true] {
let (mut object, kind) = make();
if configured {
object
.write_property(
P::DESCRIPTION,
None,
PropertyValue::CharacterString("long access label".repeat(100)),
None,
)
.unwrap();
}
if kind == ObjectType::CREDENTIAL_DATA_INPUT {
object
.write_property(
P::OUT_OF_SERVICE,
None,
PropertyValue::Boolean(out_of_service),
None,
)
.unwrap();
}
let required = object.required_properties();
let mut db = ObjectDatabase::new();
db.add(object).unwrap();
let pics = generate_pics(&db, &ServerConfig::default(), &PicsConfig::default());
assert_eq!(pics.supported_object_types.len(), 1);
let support = &pics.supported_object_types[0];
assert_eq!(support.object_type, kind);
assert!(!support.createable);
assert!(support.deleteable);
let rows: Vec<_> = support
.supported_properties
.iter()
.map(|row| {
assert!(row.access.readable);
(row.property_id, row.access.optional, row.access.writable)
})
.collect();
assert_eq!(
rows,
sorted_rows(&expected),
"{kind:?}, configured={configured}, OOS={out_of_service}"
);
assert_eq!(
rows.iter()
.filter_map(|&(p, optional, _)| (!optional).then_some(p))
.collect::<Vec<_>>(),
sorted_required(required.as_ref())
);
}
}
}
}
#[test]
fn pics_access_rights_lists_accompaniment_once_an_object_serves_it() {
let row = |pics: &Pics| {
pics.supported_object_types[0]
.supported_properties
.iter()
.find(|row| row.property_id == P::ACCOMPANIMENT)
.map(|row| {
(
row.access.readable,
row.access.optional,
row.access.writable,
)
})
};
let mut db = ObjectDatabase::new();
db.add(Box::new(AccessRightsObject::new(1, "AR-1").unwrap()))
.unwrap();
let pics = generate_pics(&db, &ServerConfig::default(), &PicsConfig::default());
assert_eq!(row(&pics), None);
let mut rights = AccessRightsObject::new(2, "AR-2").unwrap();
let credential = ObjectIdentifier::new(ObjectType::ACCESS_CREDENTIAL, 5).unwrap();
rights.set_accompaniment(Some(credential.into())).unwrap();
db.add(Box::new(rights)).unwrap();
let pics = generate_pics(&db, &ServerConfig::default(), &PicsConfig::default());
assert_eq!(pics.supported_object_types.len(), 1);
assert_eq!(row(&pics), Some((true, true, true)));
}
#[test]
fn pics_access_credential_lists_authorization_exemptions_once_an_object_serves_it() {
let row = |pics: &Pics| {
pics.supported_object_types[0]
.supported_properties
.iter()
.find(|row| row.property_id == P::AUTHORIZATION_EXEMPTIONS)
.map(|row| {
(
row.access.readable,
row.access.optional,
row.access.writable,
)
})
};
let mut db = ObjectDatabase::new();
db.add(Box::new(AccessCredentialObject::new(1, "CRED-1").unwrap()))
.unwrap();
let pics = generate_pics(&db, &ServerConfig::default(), &PicsConfig::default());
assert_eq!(row(&pics), None);
let mut credential = AccessCredentialObject::new(2, "CRED-2").unwrap();
credential
.set_authorization_exemptions(Some(vec![
bacnet_types::enums::AuthorizationExemption::ACCESS_RIGHTS,
]))
.unwrap();
db.add(Box::new(credential)).unwrap();
let pics = generate_pics(&db, &ServerConfig::default(), &PicsConfig::default());
assert_eq!(pics.supported_object_types.len(), 1);
assert_eq!(row(&pics), Some((true, true, false)));
}