use super::super::*;
use crate::command_source::test_origin;
use PropertyIdentifier as P;
fn code(result: Result<(), Error>) -> ErrorCode {
match result {
Err(Error::Protocol { class, code }) => {
assert_eq!(class, ErrorClass::PROPERTY.to_raw() as u32);
ErrorCode::from_raw(code as u16)
}
other => panic!("expected a PROPERTY refusal, got {other:?}"),
}
}
fn labels(count: usize) -> PropertyValue {
PropertyValue::List(
(1..=count)
.map(|state| PropertyValue::CharacterString(format!("Label {state}")))
.collect(),
)
}
fn count(object: &dyn BACnetObject) -> PropertyValue {
object.read_property(P::NUMBER_OF_STATES, None).unwrap()
}
fn snapshot(object: &dyn BACnetObject) -> (PropertyValue, PropertyValue) {
(
count(object),
object.read_property(P::STATE_TEXT, None).unwrap(),
)
}
fn all_three(number_of_states: u32) -> [Box<dyn BACnetObject>; 3] {
[
Box::new(MultiStateInputObject::new(1, "MSI-1", number_of_states).unwrap()),
Box::new(MultiStateOutputObject::new(1, "MSO-1", number_of_states).unwrap()),
Box::new(MultiStateValueObject::new(1, "MSV-1", number_of_states).unwrap()),
]
}
#[test]
fn a_whole_state_text_sets_the_count_both_ways() {
for mut object in all_three(3) {
let kind = object.object_identifier().object_type();
object
.write_property(P::STATE_TEXT, None, labels(3), None)
.unwrap();
assert_eq!(snapshot(&*object), (PropertyValue::Unsigned(3), labels(3)));
for states in [5, 2, 4] {
object
.write_property(P::STATE_TEXT, None, labels(states), None)
.unwrap();
assert_eq!(
snapshot(&*object),
(PropertyValue::Unsigned(states as u64), labels(states)),
"{kind:?} {states}"
);
}
object
.write_property(
P::STATE_TEXT,
None,
PropertyValue::CharacterString("Only".into()),
None,
)
.unwrap();
assert_eq!(count(&*object), PropertyValue::Unsigned(1), "{kind:?}");
let cap = MAX_NUMBER_OF_STATES as usize;
object
.write_property(P::STATE_TEXT, None, labels(cap), None)
.unwrap();
assert_eq!(count(&*object), PropertyValue::Unsigned(cap as u64));
}
}
#[test]
fn a_bad_whole_state_text_is_refused_and_changes_nothing() {
for mut object in all_three(3) {
let kind = object.object_identifier().object_type();
let before = snapshot(&*object);
for (value, expected) in [
(labels(0), ErrorCode::VALUE_OUT_OF_RANGE),
(
labels(MAX_NUMBER_OF_STATES as usize + 1),
ErrorCode::VALUE_OUT_OF_RANGE,
),
(
PropertyValue::List(vec![
PropertyValue::CharacterString("a".into()),
PropertyValue::Unsigned(2),
]),
ErrorCode::INVALID_DATA_TYPE,
),
(PropertyValue::Unsigned(2), ErrorCode::INVALID_DATA_TYPE),
(PropertyValue::Null, ErrorCode::INVALID_DATA_TYPE),
] {
assert_eq!(
code(object.write_property(P::STATE_TEXT, None, value.clone(), None)),
expected,
"{kind:?} {value:?}"
);
assert_eq!(snapshot(&*object), before, "{kind:?}");
}
}
}
fn holders() -> Vec<(&'static str, Box<dyn BACnetObject>)> {
let three = PropertyValue::Unsigned(3);
let commanded = |mut object: Box<dyn BACnetObject>| {
object
.write_property_from(
P::PRESENT_VALUE,
None,
three.clone(),
Some(8),
&test_origin(),
)
.unwrap();
object
};
let mut simulated = MultiStateInputObject::new(1, "MSI-1", 4).unwrap();
simulated.set_present_value(3);
let mut input_alarms = MultiStateInputObject::new(2, "MSI-2", 4).unwrap();
input_alarms.set_alarm_values(vec![1, 3]);
let mut value_alarms = MultiStateValueObject::new(2, "MSV-2", 4).unwrap();
value_alarms.set_alarm_values(vec![3]);
let mut output_default = MultiStateOutputObject::new(2, "MSO-2", 4).unwrap();
output_default.set_relinquish_default(3).unwrap();
let mut value_default = MultiStateValueObject::new(3, "MSV-3", 4).unwrap();
value_default.set_relinquish_default(3).unwrap();
vec![
("input Present_Value", Box::new(simulated)),
("input Alarm_Values", Box::new(input_alarms)),
(
"output command",
commanded(Box::new(
MultiStateOutputObject::new(1, "MSO-1", 4).unwrap(),
)),
),
("output Relinquish_Default", Box::new(output_default)),
(
"value command",
commanded(Box::new(MultiStateValueObject::new(1, "MSV-1", 4).unwrap())),
),
("value Relinquish_Default", Box::new(value_default)),
("value Alarm_Values", Box::new(value_alarms)),
]
}
#[test]
fn a_shrink_that_would_strand_a_held_state_is_refused() {
for (what, mut object) in holders() {
let before = snapshot(&*object);
assert_eq!(
code(object.write_property(P::STATE_TEXT, None, labels(2), None)),
ErrorCode::VALUE_OUT_OF_RANGE,
"{what}"
);
assert_eq!(snapshot(&*object), before, "{what}");
object
.write_property(P::STATE_TEXT, None, labels(3), None)
.unwrap();
assert_eq!(count(&*object), PropertyValue::Unsigned(3), "{what}");
}
}
#[test]
fn feedback_and_already_stranded_states_do_not_block_a_count() {
let mut output = MultiStateOutputObject::new(1, "MSO-1", 4).unwrap();
output
.write_property(P::FEEDBACK_VALUE, None, PropertyValue::Unsigned(4), None)
.unwrap();
output
.write_property(P::STATE_TEXT, None, labels(2), None)
.unwrap();
assert_eq!(
output.read_property(P::RELIABILITY, None).unwrap(),
PropertyValue::Enumerated(Reliability::CONFIGURATION_ERROR.to_raw())
);
let mut value = MultiStateValueObject::new(1, "MSV-1", 4).unwrap();
value.set_relinquish_default(4).unwrap();
value.set_number_of_states(2).unwrap();
assert_eq!(
value.read_property(P::RELIABILITY, None).unwrap(),
PropertyValue::Enumerated(Reliability::CONFIGURATION_ERROR.to_raw())
);
value
.write_property(P::STATE_TEXT, None, labels(1), None)
.unwrap();
value
.write_property(P::STATE_TEXT, None, labels(4), None)
.unwrap();
assert_eq!(
value.read_property(P::RELIABILITY, None).unwrap(),
PropertyValue::Enumerated(Reliability::NO_FAULT_DETECTED.to_raw())
);
}
fn resize(object: &mut dyn BACnetObject, count: u64) -> Result<(), Error> {
object.write_property(P::STATE_TEXT, Some(0), PropertyValue::Unsigned(count), None)
}
#[test]
fn a_count_at_index_0_resizes_state_text_and_the_count() {
for mut object in all_three(3) {
let kind = object.object_identifier().object_type();
object
.write_property(P::STATE_TEXT, None, labels(3), None)
.unwrap();
resize(&mut *object, 5).unwrap();
let mut grown = match labels(3) {
PropertyValue::List(labels) => labels,
_ => unreachable!(),
};
grown.extend(
["State 4", "State 5"].map(|label| PropertyValue::CharacterString(label.into())),
);
assert_eq!(
snapshot(&*object),
(PropertyValue::Unsigned(5), PropertyValue::List(grown)),
"{kind:?}"
);
resize(&mut *object, 2).unwrap();
assert_eq!(
snapshot(&*object),
(PropertyValue::Unsigned(2), labels(2)),
"{kind:?}"
);
let before = snapshot(&*object);
for (count, expected) in [
(0, ErrorCode::VALUE_OUT_OF_RANGE),
(
u64::from(MAX_NUMBER_OF_STATES) + 1,
ErrorCode::VALUE_OUT_OF_RANGE,
),
] {
assert_eq!(
code(resize(&mut *object, count)),
expected,
"{kind:?} {count}"
);
assert_eq!(snapshot(&*object), before, "{kind:?}");
}
assert_eq!(
code(object.write_property(
P::STATE_TEXT,
Some(0),
PropertyValue::CharacterString("3".into()),
None
)),
ErrorCode::INVALID_DATA_TYPE
);
resize(&mut *object, u64::from(MAX_NUMBER_OF_STATES)).unwrap();
}
}
#[test]
fn a_count_at_index_0_that_would_strand_a_held_state_is_refused() {
for (what, mut object) in holders() {
let before = snapshot(&*object);
assert_eq!(
code(resize(&mut *object, 2)),
ErrorCode::VALUE_OUT_OF_RANGE,
"{what}"
);
assert_eq!(snapshot(&*object), before, "{what}");
resize(&mut *object, 3).unwrap();
assert_eq!(count(&*object), PropertyValue::Unsigned(3), "{what}");
}
}