//! Acceptance tests for ADR-121 §2.6 — `SoulMatchOracle` Recalibrate exemption. use wifi_densepose_bfld::coherence_gate::DEBOUNCE_NS; use wifi_densepose_bfld::{ CoherenceGate, GateAction, MatchOutcome, NullOracle, SoulMatchOracle, }; /// Oracle that always claims an enrolled match. struct AlwaysMatch; impl SoulMatchOracle for AlwaysMatch { fn matches_enrolled(&self) -> MatchOutcome { MatchOutcome::Match { person_id: 0x4242_4242 } } } /// Oracle that reports suppressed (class-3 deployment). struct AlwaysSuppressed; impl SoulMatchOracle for AlwaysSuppressed { fn matches_enrolled(&self) -> MatchOutcome { MatchOutcome::Suppressed } } #[test] fn null_oracle_matches_default_evaluate_behavior() { let mut a = CoherenceGate::new(); let mut b = CoherenceGate::new(); let oracle = NullOracle; for (i, score) in [0.1, 0.4, 0.6, 0.8, 0.95].iter().enumerate() { let ts = (i as u64) * 2 * DEBOUNCE_NS; assert_eq!(a.evaluate(*score, ts), b.evaluate_with_oracle(*score, ts, &oracle)); } } #[test] fn match_outcome_downgrades_recalibrate_to_predict_only() { let mut g = CoherenceGate::new(); let oracle = AlwaysMatch; // Score = 0.95 would normally pend Recalibrate. With AlwaysMatch oracle, // it pends PredictOnly instead. g.evaluate_with_oracle(0.95, 0, &oracle); assert_eq!(g.pending(), Some(GateAction::PredictOnly)); } #[test] fn match_exemption_promotes_predict_only_after_debounce_not_recalibrate() { let mut g = CoherenceGate::new(); let oracle = AlwaysMatch; g.evaluate_with_oracle(0.95, 0, &oracle); let out = g.evaluate_with_oracle(0.95, DEBOUNCE_NS, &oracle); assert_eq!(out, GateAction::PredictOnly); assert_ne!(out, GateAction::Recalibrate, "Match must prevent Recalibrate"); } #[test] fn match_outcome_does_not_affect_lower_actions() { let mut g = CoherenceGate::new(); let oracle = AlwaysMatch; // Score in the Reject band — oracle exemption does NOT apply (only to Recalibrate). g.evaluate_with_oracle(0.8, 0, &oracle); assert_eq!(g.pending(), Some(GateAction::Reject)); // Run to debounce — current must become Reject, not PredictOnly. let out = g.evaluate_with_oracle(0.8, DEBOUNCE_NS, &oracle); assert_eq!(out, GateAction::Reject); } #[test] fn suppressed_outcome_does_not_exempt_recalibrate() { let mut g = CoherenceGate::new(); let oracle = AlwaysSuppressed; g.evaluate_with_oracle(0.95, 0, &oracle); // Suppressed is functionally equivalent to NotEnrolled — Recalibrate stays pending. assert_eq!(g.pending(), Some(GateAction::Recalibrate)); } #[test] fn not_enrolled_outcome_does_not_exempt_recalibrate() { let mut g = CoherenceGate::new(); let oracle = NullOracle; // always NotEnrolled g.evaluate_with_oracle(0.95, 0, &oracle); assert_eq!(g.pending(), Some(GateAction::Recalibrate)); } #[test] fn match_outcome_carries_person_id() { let outcome = AlwaysMatch.matches_enrolled(); match outcome { MatchOutcome::Match { person_id } => assert_eq!(person_id, 0x4242_4242), other => panic!("expected Match, got {other:?}"), } } #[test] fn null_oracle_default_constructor_works() { let oracle = NullOracle; assert_eq!(oracle.matches_enrolled(), MatchOutcome::NotEnrolled); }