99 lines
3.3 KiB
Rust
99 lines
3.3 KiB
Rust
//! 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);
|
|
}
|