use super::super::*;
use super::make_time;
use bacnet_types::constructed::{BACnetDestination, BACnetRecipient};
use bacnet_types::primitives::Time;
fn make_dest(
device_instance: u32,
valid_days: DaysOfWeek,
from: Time,
to: Time,
confirmed: bool,
transitions: EventTransitionBits,
) -> BACnetDestination {
let dev_oid = ObjectIdentifier::new(ObjectType::DEVICE, device_instance).unwrap();
BACnetDestination {
valid_days,
from_time: from,
to_time: to,
recipient: BACnetRecipient::Device(dev_oid),
process_identifier: device_instance,
issue_confirmed_notifications: confirmed,
transitions,
}
}
#[test]
fn get_recipients_filters_by_transition() {
let mut db = ObjectDatabase::new();
let mut nc = NotificationClass::new(1, "NC-1").unwrap();
nc.add_destination(make_dest(
10,
DaysOfWeek::all(),
make_time(0, 0),
make_time(23, 59),
false,
EventTransitionBits::TO_OFFNORMAL,
))
.unwrap();
nc.add_destination(make_dest(
20,
DaysOfWeek::all(),
make_time(0, 0),
make_time(23, 59),
true,
EventTransitionBits::TO_NORMAL,
))
.unwrap();
nc.add_destination(make_dest(
30,
DaysOfWeek::all(),
make_time(0, 0),
make_time(23, 59),
false,
EventTransitionBits::all(),
))
.unwrap();
db.add(Box::new(nc)).unwrap();
let now = make_time(12, 0);
let monday = DaysOfWeek::MONDAY;
let r = get_notification_recipients(&db, 1, EventTransition::ToOffnormal, monday, &now);
assert_eq!(r.len(), 2);
assert_eq!(r[0].1, 10); assert_eq!(r[1].1, 30);
let r = get_notification_recipients(&db, 1, EventTransition::ToNormal, monday, &now);
assert_eq!(r.len(), 2);
assert_eq!(r[0].1, 20);
assert!(r[0].2); assert_eq!(r[1].1, 30);
let r = get_notification_recipients(&db, 1, EventTransition::ToFault, monday, &now);
assert_eq!(r.len(), 1);
assert_eq!(r[0].1, 30);
}
#[test]
fn get_recipients_filters_by_day() {
let mut db = ObjectDatabase::new();
let mut nc = NotificationClass::new(2, "NC-2").unwrap();
nc.add_destination(make_dest(
10,
DaysOfWeek::MONDAY
| DaysOfWeek::TUESDAY
| DaysOfWeek::WEDNESDAY
| DaysOfWeek::THURSDAY
| DaysOfWeek::FRIDAY,
make_time(0, 0),
make_time(23, 59),
false,
EventTransitionBits::all(),
))
.unwrap();
db.add(Box::new(nc)).unwrap();
let now = make_time(12, 0);
let r = get_notification_recipients(
&db,
2,
EventTransition::ToOffnormal,
DaysOfWeek::MONDAY,
&now,
);
assert_eq!(r.len(), 1);
let r = get_notification_recipients(
&db,
2,
EventTransition::ToOffnormal,
DaysOfWeek::FRIDAY,
&now,
);
assert_eq!(r.len(), 1);
let r = get_notification_recipients(
&db,
2,
EventTransition::ToOffnormal,
DaysOfWeek::SUNDAY,
&now,
);
assert!(r.is_empty());
let r = get_notification_recipients(
&db,
2,
EventTransition::ToOffnormal,
DaysOfWeek::SATURDAY,
&now,
);
assert!(r.is_empty());
}
#[test]
fn get_recipients_filters_by_time_window() {
let mut db = ObjectDatabase::new();
let mut nc = NotificationClass::new(3, "NC-3").unwrap();
nc.add_destination(make_dest(
10,
DaysOfWeek::all(),
make_time(8, 0),
make_time(17, 0),
false,
EventTransitionBits::all(),
))
.unwrap();
db.add(Box::new(nc)).unwrap();
let monday = DaysOfWeek::MONDAY;
let r = get_notification_recipients(
&db,
3,
EventTransition::ToOffnormal,
monday,
&make_time(12, 0),
);
assert_eq!(r.len(), 1);
let r = get_notification_recipients(
&db,
3,
EventTransition::ToOffnormal,
monday,
&make_time(7, 0),
);
assert!(r.is_empty());
let r = get_notification_recipients(
&db,
3,
EventTransition::ToOffnormal,
monday,
&make_time(18, 0),
);
assert!(r.is_empty());
}
#[test]
fn get_recipients_time_window_boundary_inclusive() {
let mut db = ObjectDatabase::new();
let mut nc = NotificationClass::new(4, "NC-4").unwrap();
nc.add_destination(make_dest(
10,
DaysOfWeek::all(),
make_time(8, 0),
make_time(17, 0),
false,
EventTransitionBits::all(),
))
.unwrap();
db.add(Box::new(nc)).unwrap();
let monday = DaysOfWeek::MONDAY;
let r = get_notification_recipients(
&db,
4,
EventTransition::ToOffnormal,
monday,
&make_time(8, 0),
);
assert_eq!(r.len(), 1);
let r = get_notification_recipients(
&db,
4,
EventTransition::ToOffnormal,
monday,
&make_time(17, 0),
);
assert_eq!(r.len(), 1);
let r = get_notification_recipients(
&db,
4,
EventTransition::ToOffnormal,
monday,
&make_time(7, 59),
);
assert!(r.is_empty());
}
#[test]
fn get_recipients_overnight_window_crosses_midnight() {
let mut db = ObjectDatabase::new();
let mut nc = NotificationClass::new(5, "NC-5").unwrap();
nc.add_destination(make_dest(
10,
DaysOfWeek::all(),
make_time(22, 0),
make_time(2, 0),
false,
EventTransitionBits::all(),
))
.unwrap();
db.add(Box::new(nc)).unwrap();
let monday = DaysOfWeek::MONDAY;
let r = get_notification_recipients(
&db,
5,
EventTransition::ToOffnormal,
monday,
&make_time(23, 0),
);
assert_eq!(r.len(), 1);
let r = get_notification_recipients(
&db,
5,
EventTransition::ToOffnormal,
monday,
&make_time(1, 0),
);
assert_eq!(r.len(), 1);
let r = get_notification_recipients(
&db,
5,
EventTransition::ToOffnormal,
monday,
&make_time(12, 0),
);
assert!(r.is_empty());
let r = get_notification_recipients(
&db,
5,
EventTransition::ToOffnormal,
monday,
&make_time(3, 0),
);
assert!(r.is_empty());
}
#[test]
fn get_recipients_day_filter_sunday_and_weekend_bits() {
let mut db = ObjectDatabase::new();
let mut nc = NotificationClass::new(6, "NC-6").unwrap();
nc.add_destination(make_dest(
10,
DaysOfWeek::SUNDAY,
make_time(0, 0),
make_time(23, 59),
false,
EventTransitionBits::all(),
))
.unwrap();
db.add(Box::new(nc)).unwrap();
let now = make_time(12, 0);
let r = get_notification_recipients(
&db,
6,
EventTransition::ToOffnormal,
DaysOfWeek::SUNDAY,
&now,
);
assert_eq!(r.len(), 1);
let r = get_notification_recipients(
&db,
6,
EventTransition::ToOffnormal,
DaysOfWeek::MONDAY,
&now,
);
assert!(r.is_empty());
let r = get_notification_recipients(
&db,
6,
EventTransition::ToOffnormal,
DaysOfWeek::SATURDAY,
&now,
);
assert!(r.is_empty());
}
#[test]
fn local_day_and_time_day_bits_match_bacnet_days_of_week() {
let thursday = local_day_and_time(0, 0).0;
assert_eq!(thursday, DaysOfWeek::THURSDAY, "epoch Thursday");
assert_eq!(thursday.bits(), 0x08, "Thursday = bit 3");
assert_eq!(local_day_and_time(3 * 86400, 0).0, DaysOfWeek::SUNDAY);
assert_eq!(local_day_and_time(4 * 86400, 0).0, DaysOfWeek::MONDAY);
}
#[test]
fn local_day_and_time_offset_shifts_day_and_time() {
let (day, t) = local_day_and_time(23 * 3600, -60);
assert_eq!(
day,
DaysOfWeek::FRIDAY,
"UTC 23:00 Thu +60min rolls to Friday"
);
assert_eq!((t.hour, t.minute, t.second), (0, 0, 0));
let (day, t) = local_day_and_time(86400 + 30 * 60, 60);
assert_eq!(
day,
DaysOfWeek::THURSDAY,
"UTC 00:30 next day -60min rolls back to Thursday"
);
assert_eq!((t.hour, t.minute), (23, 30));
}
#[test]
fn local_day_and_time_time_of_day_preserved_under_zero_offset() {
let (_, t) = local_day_and_time(12 * 3600 + 34 * 60 + 56, 0);
assert_eq!((t.hour, t.minute, t.second), (12, 34, 56));
}
#[test]
fn get_recipients_returns_empty_for_missing_class() {
let db = ObjectDatabase::new();
let r = get_notification_recipients(
&db,
99,
EventTransition::ToOffnormal,
DaysOfWeek::MONDAY,
&make_time(12, 0),
);
assert!(r.is_empty());
}
#[test]
fn get_recipients_returns_empty_for_empty_list() {
let mut db = ObjectDatabase::new();
let nc = NotificationClass::new(1, "NC-1").unwrap();
db.add(Box::new(nc)).unwrap();
let r = get_notification_recipients(
&db,
1,
EventTransition::ToOffnormal,
DaysOfWeek::MONDAY,
&make_time(12, 0),
);
assert!(r.is_empty());
}
#[test]
fn event_state_change_transition_mapping() {
use crate::event::EventStateChange;
use bacnet_types::enums::EventState;
let to_normal = EventStateChange {
from: EventState::HIGH_LIMIT,
to: EventState::NORMAL,
};
assert_eq!(to_normal.transition(), EventTransition::ToNormal);
let to_fault = EventStateChange {
from: EventState::NORMAL,
to: EventState::FAULT,
};
assert_eq!(to_fault.transition(), EventTransition::ToFault);
let to_high = EventStateChange {
from: EventState::NORMAL,
to: EventState::HIGH_LIMIT,
};
assert_eq!(to_high.transition(), EventTransition::ToOffnormal);
let to_low = EventStateChange {
from: EventState::NORMAL,
to: EventState::LOW_LIMIT,
};
assert_eq!(to_low.transition(), EventTransition::ToOffnormal);
}