//! Acceptance tests for ADR-121 §2.1 / ADR-122 §2.1 — `BfldEvent` privacy gating. #![cfg(feature = "std")] use wifi_densepose_bfld::{BfldEvent, PrivacyClass}; fn sample_at(class: PrivacyClass) -> BfldEvent { BfldEvent::with_privacy_gating( "seed-01".to_string(), 1_700_000_000_000_000_000, true, 0.72, 1, 0.91, Some("living_room".to_string()), class, Some(0.84), Some([0xAB; 32]), ) } #[test] fn anonymous_event_retains_identity_risk_and_hash() { let e = sample_at(PrivacyClass::Anonymous); assert!(e.identity_risk_score.is_some()); assert!(e.rf_signature_hash.is_some()); } #[test] fn restricted_event_strips_identity_fields() { let e = sample_at(PrivacyClass::Restricted); assert!(e.identity_risk_score.is_none(), "risk score must be None at class 3"); assert!(e.rf_signature_hash.is_none(), "rf hash must be None at class 3"); // Sensing fields still present. assert!(e.presence); assert_eq!(e.person_count, 1); assert_eq!(e.zone_id.as_deref(), Some("living_room")); } #[test] fn apply_privacy_gating_is_idempotent() { let mut e = sample_at(PrivacyClass::Restricted); e.apply_privacy_gating(); e.apply_privacy_gating(); assert!(e.identity_risk_score.is_none()); } #[test] fn event_type_is_always_bfld_update() { for c in [ PrivacyClass::Anonymous, PrivacyClass::Restricted, PrivacyClass::Derived, ] { assert_eq!(sample_at(c).event_type, "bfld_update"); } } #[cfg(feature = "serde-json")] mod json { use super::sample_at; use wifi_densepose_bfld::PrivacyClass; #[test] fn json_round_trip_emits_type_field_first_or_last_but_present() { let json = sample_at(PrivacyClass::Anonymous).to_json().unwrap(); assert!(json.contains(r#""type":"bfld_update""#), "JSON: {json}"); assert!(json.contains(r#""node_id":"seed-01""#)); assert!(json.contains(r#""presence":true"#)); assert!(json.contains(r#""privacy_class":"anonymous""#)); } #[test] fn anonymous_json_includes_identity_fields() { let json = sample_at(PrivacyClass::Anonymous).to_json().unwrap(); assert!(json.contains("identity_risk_score")); assert!(json.contains("rf_signature_hash")); } #[test] fn restricted_json_omits_identity_fields_entirely() { let json = sample_at(PrivacyClass::Restricted).to_json().unwrap(); assert!( !json.contains("identity_risk_score"), "JSON must omit identity_risk_score at class 3, got: {json}", ); assert!( !json.contains("rf_signature_hash"), "JSON must omit rf_signature_hash at class 3, got: {json}", ); // Sensing fields still emitted. assert!(json.contains("presence")); assert!(json.contains("motion")); assert!(json.contains(r#""privacy_class":"restricted""#)); } #[test] fn privacy_class_serializes_to_lowercase_name() { for (class, name) in [ (PrivacyClass::Anonymous, "anonymous"), (PrivacyClass::Restricted, "restricted"), ] { let json = sample_at(class).to_json().unwrap(); let needle = format!(r#""privacy_class":"{name}""#); assert!(json.contains(&needle), "missing {needle} in: {json}"); } } #[test] fn zone_id_none_is_omitted_from_json() { let mut e = sample_at(PrivacyClass::Anonymous); e.zone_id = None; let json = e.to_json().unwrap(); assert!(!json.contains("zone_id"), "None zone_id must be omitted: {json}"); } }