use super::*;
use bacnet_types::enums::{ErrorClass, ErrorCode};
#[test]
fn group_create() {
let g = GroupObject::new(1, "Group-1").unwrap();
assert_eq!(g.object_identifier().object_type(), ObjectType::GROUP);
assert_eq!(g.object_identifier().instance_number(), 1);
assert_eq!(g.object_name(), "Group-1");
}
#[test]
fn group_object_type() {
let g = GroupObject::new(1, "G").unwrap();
let val = g
.read_property(PropertyIdentifier::OBJECT_TYPE, None)
.unwrap();
assert_eq!(val, PropertyValue::Enumerated(ObjectType::GROUP.to_raw()));
}
#[test]
fn group_property_list() {
let g = GroupObject::new(1, "G").unwrap();
let props = g.property_list();
assert!(props.contains(&PropertyIdentifier::LIST_OF_GROUP_MEMBERS));
assert!(props.contains(&PropertyIdentifier::PRESENT_VALUE));
assert!(!props.contains(&PropertyIdentifier::STATUS_FLAGS));
}
#[test]
fn global_group_create() {
let gg = GlobalGroupObject::new(1, "GG-1").unwrap();
assert_eq!(
gg.object_identifier().object_type(),
ObjectType::GLOBAL_GROUP
);
assert_eq!(gg.object_identifier().instance_number(), 1);
assert_eq!(gg.object_name(), "GG-1");
}
#[test]
fn global_group_object_type() {
let gg = GlobalGroupObject::new(1, "GG").unwrap();
let val = gg
.read_property(PropertyIdentifier::OBJECT_TYPE, None)
.unwrap();
assert_eq!(
val,
PropertyValue::Enumerated(ObjectType::GLOBAL_GROUP.to_raw())
);
}
#[test]
fn global_group_members_empty() {
let gg = GlobalGroupObject::new(1, "GG").unwrap();
let val = gg
.read_property(PropertyIdentifier::GROUP_MEMBERS, None)
.unwrap();
if let PropertyValue::List(items) = val {
assert!(items.is_empty());
} else {
panic!("Expected List");
}
}
#[test]
fn global_group_member_names() {
let mut gg = GlobalGroupObject::new(1, "GG").unwrap();
gg.group_member_names.push("Temp Sensor".into());
gg.group_member_names.push("Humidity".into());
let val = gg
.read_property(PropertyIdentifier::GROUP_MEMBER_NAMES, None)
.unwrap();
if let PropertyValue::List(items) = val {
assert_eq!(items.len(), 2);
assert_eq!(
items[0],
PropertyValue::CharacterString("Temp Sensor".into())
);
assert_eq!(items[1], PropertyValue::CharacterString("Humidity".into()));
} else {
panic!("Expected List");
}
}
#[test]
fn global_group_property_list() {
let gg = GlobalGroupObject::new(1, "GG").unwrap();
let props = gg.property_list();
assert!(props.contains(&PropertyIdentifier::GROUP_MEMBERS));
assert!(props.contains(&PropertyIdentifier::PRESENT_VALUE));
assert!(props.contains(&PropertyIdentifier::GROUP_MEMBER_NAMES));
assert!(props.contains(&PropertyIdentifier::EVENT_STATE));
assert!(props.contains(&PropertyIdentifier::MEMBER_STATUS_FLAGS));
}
#[test]
fn structured_view_create() {
let sv = StructuredViewObject::new(1, "SV-1").unwrap();
assert_eq!(
sv.object_identifier().object_type(),
ObjectType::STRUCTURED_VIEW
);
assert_eq!(sv.object_identifier().instance_number(), 1);
assert_eq!(sv.object_name(), "SV-1");
}
#[test]
fn structured_view_object_type() {
let sv = StructuredViewObject::new(1, "SV").unwrap();
let val = sv
.read_property(PropertyIdentifier::OBJECT_TYPE, None)
.unwrap();
assert_eq!(
val,
PropertyValue::Enumerated(ObjectType::STRUCTURED_VIEW.to_raw())
);
}
#[test]
fn structured_view_add_subordinates() {
let mut sv = StructuredViewObject::new(1, "SV").unwrap();
let ai1 = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
let bi1 = ObjectIdentifier::new(ObjectType::BINARY_INPUT, 1).unwrap();
let device = ObjectIdentifier::new(ObjectType::DEVICE, 9).unwrap();
sv.add_subordinate(ai1, "Temperature").unwrap();
sv.add_subordinate(
BACnetDeviceObjectReference {
device_identifier: Some(device),
object_identifier: bi1,
},
"Occupancy",
)
.unwrap();
let subordinates = [
PropertyValue::ApplicationData(vec![0x1C, 0x00, 0x00, 0x00, 0x01]),
PropertyValue::ApplicationData(vec![
0x0C, 0x02, 0x00, 0x00, 0x09, 0x1C, 0x00, 0xC0, 0x00, 0x01,
]),
];
let annotations = [
PropertyValue::CharacterString("Temperature".into()),
PropertyValue::CharacterString("Occupancy".into()),
];
for (property, elements) in [
(PropertyIdentifier::SUBORDINATE_LIST, &subordinates),
(PropertyIdentifier::SUBORDINATE_ANNOTATIONS, &annotations),
] {
assert_eq!(
sv.read_property(property, None).unwrap(),
PropertyValue::List(elements.to_vec()),
"{property:?}"
);
assert_eq!(
sv.read_property(property, Some(0)).unwrap(),
PropertyValue::Unsigned(2),
"{property:?}[0]"
);
for (index, element) in (1..).zip(elements) {
assert_eq!(
&sv.read_property(property, Some(index)).unwrap(),
element,
"{property:?}[{index}]"
);
}
for index in [3, u32::MAX] {
let error = sv.read_property(property, Some(index)).unwrap_err();
assert!(
matches!(error, Error::Protocol { class, code }
if class == ErrorClass::PROPERTY.to_raw() as u32
&& code == ErrorCode::INVALID_ARRAY_INDEX.to_raw() as u32),
"{property:?}[{index}]: {error:?}"
);
}
for index in [None, Some(0), Some(1)] {
let error = sv
.write_property(property, index, elements[0].clone(), None)
.unwrap_err();
assert!(
matches!(error, Error::Protocol { class, code }
if class == ErrorClass::PROPERTY.to_raw() as u32
&& code == ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32),
"{property:?}[{index:?}] write: {error:?}"
);
}
assert_eq!(
sv.read_property(property, None).unwrap(),
PropertyValue::List(elements.to_vec())
);
}
}
#[test]
fn structured_view_add_subordinate_refuses_a_non_device_device_identifier() {
let mut sv = StructuredViewObject::new(1, "SV").unwrap();
let ai1 = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
sv.add_subordinate(ai1, "Temperature").unwrap();
for object_type in [ObjectType::ANALOG_VALUE, ObjectType::STRUCTURED_VIEW] {
let result = sv.add_subordinate(
BACnetDeviceObjectReference {
device_identifier: Some(ObjectIdentifier::new(object_type, 9).unwrap()),
object_identifier: ai1,
},
"Elsewhere",
);
assert!(
matches!(result, Err(Error::Protocol { class, code })
if class == ErrorClass::PROPERTY.to_raw() as u32
&& code == ErrorCode::VALUE_OUT_OF_RANGE.to_raw() as u32),
"{object_type:?}: {result:?}"
);
for property in [
PropertyIdentifier::SUBORDINATE_LIST,
PropertyIdentifier::SUBORDINATE_ANNOTATIONS,
] {
assert_eq!(
sv.read_property(property, Some(0)).unwrap(),
PropertyValue::Unsigned(1),
"{property:?}"
);
}
}
}
#[test]
fn structured_view_set_subordinates_replaces_both_arrays_or_nothing() {
let mut sv = StructuredViewObject::new(1, "SV").unwrap();
let ai1 = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
let bi2 = ObjectIdentifier::new(ObjectType::BINARY_INPUT, 2).unwrap();
let device = ObjectIdentifier::new(ObjectType::DEVICE, 9).unwrap();
sv.add_subordinate(ai1, "Temperature").unwrap();
let remote = BACnetDeviceObjectReference {
device_identifier: Some(device),
object_identifier: bi2,
};
sv.set_subordinates(vec![
(bi2.into(), "Occupancy".to_string()),
(remote.clone(), "Remote".to_string()),
])
.unwrap();
let served: Vec<_> = sv
.subordinates()
.map(|(reference, annotation)| (reference.clone(), annotation.to_string()))
.collect();
assert_eq!(
served,
[
(bi2.into(), "Occupancy".to_string()),
(remote, "Remote".to_string())
]
);
let size = |sv: &StructuredViewObject, property| sv.read_property(property, Some(0)).unwrap();
for property in [
PropertyIdentifier::SUBORDINATE_LIST,
PropertyIdentifier::SUBORDINATE_ANNOTATIONS,
] {
assert_eq!(
size(&sv, property),
PropertyValue::Unsigned(2),
"{property:?}"
);
}
let not_a_device = BACnetDeviceObjectReference {
device_identifier: Some(ObjectIdentifier::new(ObjectType::ANALOG_VALUE, 9).unwrap()),
object_identifier: ai1,
};
let result = sv.set_subordinates(vec![
(ai1.into(), "Temperature".to_string()),
(not_a_device, "Elsewhere".to_string()),
]);
assert!(
matches!(result, Err(Error::Protocol { class, code })
if class == ErrorClass::PROPERTY.to_raw() as u32
&& code == ErrorCode::VALUE_OUT_OF_RANGE.to_raw() as u32),
"{result:?}"
);
assert_eq!(sv.subordinates().count(), 2);
assert_eq!(sv.subordinates().next().unwrap().1, "Occupancy");
sv.set_subordinates(Vec::new()).unwrap();
for property in [
PropertyIdentifier::SUBORDINATE_LIST,
PropertyIdentifier::SUBORDINATE_ANNOTATIONS,
] {
assert_eq!(
size(&sv, property),
PropertyValue::Unsigned(0),
"{property:?}"
);
}
}
#[test]
fn structured_view_node_type() {
let sv = StructuredViewObject::new(1, "SV").unwrap();
let val = sv
.read_property(PropertyIdentifier::NODE_TYPE, None)
.unwrap();
assert_eq!(val, PropertyValue::Enumerated(0));
}
#[test]
fn structured_view_node_subtype() {
let sv = StructuredViewObject::new(1, "SV").unwrap();
let val = sv
.read_property(PropertyIdentifier::NODE_SUBTYPE, None)
.unwrap();
assert_eq!(val, PropertyValue::CharacterString(String::new()));
}
#[test]
fn structured_view_property_list() {
let sv = StructuredViewObject::new(1, "SV").unwrap();
let props = sv.property_list();
assert!(props.contains(&PropertyIdentifier::NODE_TYPE));
assert!(props.contains(&PropertyIdentifier::NODE_SUBTYPE));
assert!(props.contains(&PropertyIdentifier::SUBORDINATE_LIST));
assert!(props.contains(&PropertyIdentifier::SUBORDINATE_ANNOTATIONS));
}