use super::*;
use bacnet_objects::schedule::{
CalendarObject, ScheduleObject, ScheduleTargetOutcome, ScheduleWrite,
};
use bacnet_types::calendar::SpecificDate;
use bacnet_types::constructed::{BACnetCalendarEntry, BACnetObjectPropertyReference};
use bacnet_types::enums::Reliability;
use bacnet_types::primitives::Time;
#[test]
fn source_reporter_forwards_complete_schedule_targets() {
let refs = vec![
BACnetObjectPropertyReference::new(oid(ObjectType::ANALOG_OUTPUT, 2), 85),
BACnetObjectPropertyReference::new_indexed(oid(ObjectType::MULTI_STATE_OUTPUT, 7), 110, 2),
];
let mut schedule =
ScheduleObject::new(3, "wrapped schedule", PropertyValue::Unsigned(1)).unwrap();
for reference in &refs {
schedule
.add_object_property_reference(reference.clone())
.unwrap();
}
schedule
.write_property(
PropertyIdentifier::SCHEDULE_DEFAULT,
None,
PropertyValue::Unsigned(2),
None,
)
.unwrap();
let mut object: Box<dyn BACnetObject> = Box::new(schedule);
let owner = bacnet_objects::database::AuditOwnership::for_source(
oid(ObjectType::DEVICE, 123),
selected(),
);
source_reporter::install(&mut object, &owner).unwrap();
let today = SpecificDate::new(2026, 9, 14).unwrap();
let noon = Time {
hour: 12,
minute: 0,
second: 0,
hundredths: 0,
};
let no_calendars = |_: ObjectIdentifier| false;
assert_eq!(
object.tick_schedule(today, noon, &no_calendars),
Some(ScheduleWrite {
value: PropertyValue::Unsigned(2),
priority: 16,
references: refs.clone(),
retry: false,
})
);
assert!(object.tick_schedule(today, noon, &no_calendars).is_none());
for (property, value) in [
(
PropertyIdentifier::OUT_OF_SERVICE,
PropertyValue::Boolean(true),
),
(
PropertyIdentifier::PRESENT_VALUE,
PropertyValue::Unsigned(5),
),
] {
object.write_property(property, None, value, None).unwrap();
}
let simulated = ScheduleWrite {
value: PropertyValue::Unsigned(5),
priority: 16,
references: refs.clone(),
retry: false,
};
assert_eq!(object.take_owed_schedule_writes(), [simulated]);
assert!(object.take_owed_schedule_writes().is_empty());
let kept = vec![0x0C, 0x00, 0x40, 0x00, 0x02, 0x19, 85];
object
.write_property(
PropertyIdentifier::LIST_OF_OBJECT_PROPERTY_REFERENCES,
None,
PropertyValue::ApplicationData(kept),
None,
)
.unwrap();
assert_eq!(
object.take_owed_schedule_writes(),
[
ScheduleWrite {
value: PropertyValue::Null,
priority: 16,
references: vec![refs[1].clone()],
retry: false,
},
ScheduleWrite {
value: PropertyValue::Unsigned(5),
priority: 16,
references: vec![refs[0].clone()],
retry: false,
},
]
);
object
.write_property(
PropertyIdentifier::OUT_OF_SERVICE,
None,
PropertyValue::Boolean(false),
None,
)
.unwrap();
assert!(object.complete_schedule_write(
&ScheduleWrite {
value: PropertyValue::Unsigned(2),
priority: 16,
references: vec![refs[0].clone()],
retry: false,
},
&[ScheduleTargetOutcome::DatatypeRefused],
));
assert_eq!(
object
.read_property(PropertyIdentifier::RELIABILITY, None)
.unwrap(),
PropertyValue::Enumerated(Reliability::CONFIGURATION_ERROR.to_raw())
);
}
#[test]
fn source_reporter_forwards_calendar_state() {
let mut calendar = CalendarObject::new(4, "wrapped calendar").unwrap();
calendar
.add_date_entry(BACnetCalendarEntry::Date(
SpecificDate::new(2026, 12, 25).unwrap().to_date(),
))
.unwrap();
let mut object: Box<dyn BACnetObject> = Box::new(calendar);
let owner = bacnet_objects::database::AuditOwnership::for_source(
oid(ObjectType::DEVICE, 123),
selected(),
);
source_reporter::install(&mut object, &owner).unwrap();
for (day, expected) in [(25, true), (24, false)] {
let day = SpecificDate::new(2026, 12, day).unwrap();
assert_eq!(object.calendar_state_internal(day), Some(expected));
}
}