bacnet-server 0.15.0

BACnet server: APDU dispatch and service handlers
Documentation
use super::*;

#[test]
fn pics_loop_program_notification_class_property_metadata_is_exact() {
    use PropertyIdentifier as P;
    use bacnet_objects::notification_class::NotificationClass;
    use bacnet_objects::{loop_obj::LoopObject, program::ProgramObject, traits::BACnetObject};
    use bacnet_types::enums::EngineeringUnits;
    use bacnet_types::primitives::PropertyValue;

    let loop_rows = [
        (P::OBJECT_IDENTIFIER, false, false),
        (P::OBJECT_NAME, false, false),
        (P::OBJECT_TYPE, false, false),
        // Writable while Out_Of_Service is TRUE (#985).
        (P::PRESENT_VALUE, false, true),
        (P::DESCRIPTION, true, true),
        (P::STATUS_FLAGS, false, false),
        (P::EVENT_STATE, false, false),
        (P::RELIABILITY, true, true),
        (P::OUT_OF_SERVICE, false, true),
        (P::UPDATE_INTERVAL, true, true),
        (P::OUTPUT_UNITS, false, false),
        (P::MANIPULATED_VARIABLE_REFERENCE, false, true),
        (P::CONTROLLED_VARIABLE_REFERENCE, false, true),
        (P::CONTROLLED_VARIABLE_VALUE, false, false),
        // #1062: the required rows the Loop used to leave out, and the units
        // row paired with each gain constant.
        (P::CONTROLLED_VARIABLE_UNITS, false, false),
        (P::SETPOINT_REFERENCE, false, true),
        (P::SETPOINT, false, true),
        (P::ACTION, false, true),
        (P::PROPORTIONAL_CONSTANT, true, true),
        (P::PROPORTIONAL_CONSTANT_UNITS, true, false),
        (P::INTEGRAL_CONSTANT, true, true),
        (P::INTEGRAL_CONSTANT_UNITS, true, false),
        (P::DERIVATIVE_CONSTANT, true, true),
        (P::DERIVATIVE_CONSTANT_UNITS, true, false),
        (P::PRIORITY_FOR_WRITING, false, false),
        (P::COV_INCREMENT, true, true),
        (P::PROPERTY_LIST, false, false),
    ];
    let program_rows = [
        (P::OBJECT_IDENTIFIER, false, false),
        (P::OBJECT_NAME, false, false),
        (P::DESCRIPTION, true, true),
        (P::OBJECT_TYPE, false, false),
        (P::PROGRAM_STATE, false, true),
        (P::PROGRAM_CHANGE, false, true),
        (P::REASON_FOR_HALT, true, false),
        (P::STATUS_FLAGS, false, false),
        (P::OUT_OF_SERVICE, false, true),
        (P::RELIABILITY, true, false),
        (P::PROPERTY_LIST, false, false),
    ];
    let notification_class_rows = [
        (P::OBJECT_IDENTIFIER, false, false),
        (P::OBJECT_NAME, false, false),
        (P::DESCRIPTION, true, true),
        (P::OBJECT_TYPE, false, false),
        (P::STATUS_FLAGS, true, false),
        (P::EVENT_STATE, true, false),
        // Table 12-24 has no Out_Of_Service (#1064).
        (P::RELIABILITY, true, false),
        (P::NOTIFICATION_CLASS, false, true),
        (P::PRIORITY, false, false),
        (P::ACK_REQUIRED, false, false),
        (P::RECIPIENT_LIST, false, true),
        (P::PROPERTY_LIST, false, false),
    ];
    for out_of_service in [false, true] {
        let objects: [Box<dyn BACnetObject>; 3] = [
            Box::new(LoopObject::new(1, "LOOP-1", EngineeringUnits::DEGREES_CELSIUS).unwrap()),
            Box::new(ProgramObject::new(1, "PRG-1").unwrap()),
            Box::new(NotificationClass::new(1, "NC-1").unwrap()),
        ];
        for mut object in objects {
            let kind = object.object_identifier().object_type();
            if kind != ObjectType::NOTIFICATION_CLASS {
                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();
            let expected = if kind == ObjectType::LOOP {
                loop_rows.as_slice()
            } else if kind == ObjectType::PROGRAM {
                program_rows.as_slice()
            } else {
                notification_class_rows.as_slice()
            };
            assert_eq!(
                rows,
                sorted_rows(expected),
                "{kind:?}, OOS={out_of_service}"
            );
            assert_eq!(
                rows.iter()
                    .filter_map(|&(p, optional, _)| (!optional).then_some(p))
                    .collect::<Vec<_>>(),
                sorted_required(required.as_ref())
            );
        }
    }
}