//! Acceptance tests for ADR-122 §2.1 — HA auto-discovery payloads. #![cfg(feature = "std")] use wifi_densepose_bfld::{render_discovery_payloads, PrivacyClass}; fn topics(class: PrivacyClass) -> Vec { render_discovery_payloads("seed-01", class) .into_iter() .map(|m| m.topic) .collect() } #[test] fn raw_and_derived_classes_produce_no_discovery_payloads() { for class in [PrivacyClass::Raw, PrivacyClass::Derived] { assert!( render_discovery_payloads("seed-01", class).is_empty(), "class {class:?} must not emit HA discovery", ); } } #[test] fn anonymous_class_produces_six_discovery_payloads() { let ts = topics(PrivacyClass::Anonymous); assert_eq!(ts.len(), 6); } #[test] fn restricted_class_omits_identity_risk_discovery() { let ts = topics(PrivacyClass::Restricted); assert_eq!(ts.len(), 5, "Restricted: 5 entities, no identity_risk"); assert!( !ts.iter().any(|t| t.contains("identity_risk")), "Restricted must not advertise identity_risk entity to HA", ); } #[test] fn discovery_topic_format_matches_ha_convention() { let ts = topics(PrivacyClass::Anonymous); assert!(ts.contains(&"homeassistant/binary_sensor/seed-01_bfld_presence/config".into())); assert!(ts.contains(&"homeassistant/sensor/seed-01_bfld_motion/config".into())); assert!(ts.contains(&"homeassistant/sensor/seed-01_bfld_person_count/config".into())); assert!(ts.contains(&"homeassistant/sensor/seed-01_bfld_zone_activity/config".into())); assert!(ts.contains(&"homeassistant/sensor/seed-01_bfld_confidence/config".into())); assert!(ts.contains(&"homeassistant/sensor/seed-01_bfld_identity_risk/config".into())); } #[test] fn presence_payload_carries_occupancy_device_class() { let msgs = render_discovery_payloads("seed-01", PrivacyClass::Anonymous); let pres = msgs .iter() .find(|m| m.topic.contains("presence")) .expect("presence config"); assert!(pres.payload.contains("\"device_class\":\"occupancy\"")); } #[test] fn motion_payload_marked_as_diagnostic() { let msgs = render_discovery_payloads("seed-01", PrivacyClass::Anonymous); let motion = msgs .iter() .find(|m| m.topic.contains("motion")) .expect("motion config"); assert!(motion.payload.contains("\"entity_category\":\"diagnostic\"")); } #[test] fn person_count_payload_carries_unit_of_measurement() { let msgs = render_discovery_payloads("seed-01", PrivacyClass::Anonymous); let pc = msgs .iter() .find(|m| m.topic.contains("person_count")) .expect("person_count config"); assert!(pc.payload.contains("\"unit_of_measurement\":\"people\"")); } #[test] fn every_payload_contains_unique_id_and_state_topic_pointing_at_correct_state_topic() { let msgs = render_discovery_payloads("seed-99", PrivacyClass::Anonymous); for msg in &msgs { // unique_id is required for HA to dedupe entity creation. assert!( msg.payload.contains("\"unique_id\":\""), "missing unique_id in {msg:?}", ); // state_topic must point back at the BFLD `ruview//bfld//state` path. assert!( msg.payload.contains("\"state_topic\":\"ruview/seed-99/bfld/"), "state_topic wrong in {msg:?}", ); // Device block ties all six entities to one HA device. assert!(msg.payload.contains("\"device\":{")); assert!(msg.payload.contains("\"identifiers\":\"seed-99\"")); assert!(msg.payload.contains("\"manufacturer\":\"RuView\"")); } } #[test] fn unique_id_matches_topic_segment() { let msgs = render_discovery_payloads("seed-01", PrivacyClass::Anonymous); for msg in &msgs { // topic is homeassistant///config — the unique_id segment // must appear in the payload too. let parts: Vec<&str> = msg.topic.split('/').collect(); assert_eq!(parts.len(), 4, "topic shape wrong: {}", msg.topic); assert_eq!(parts[0], "homeassistant"); assert_eq!(parts[3], "config"); let unique_id_from_topic = parts[2]; let needle = format!("\"unique_id\":\"{unique_id_from_topic}\""); assert!( msg.payload.contains(&needle), "unique_id mismatch between topic and payload: {msg:?}", ); } } #[test] fn class_2_discovery_includes_identity_risk_explicitly() { let msgs = render_discovery_payloads("seed-01", PrivacyClass::Anonymous); let risk = msgs .iter() .find(|m| m.topic.contains("identity_risk")) .expect("identity_risk config must be present at class 2"); assert!(risk.payload.contains("\"entity_category\":\"diagnostic\"")); }