test(ptt): acceptance tests for press-on / release-off shape

Two new tests covering the user-acceptance criterion 'press → ptt
on, release → ptt off' through the full pipeline (backend press_gate
→ edge watcher → release tail → selector → real gate, identical to
what audioStats.pttActive reads in production):

  * press_on_release_off_zero_tail — release_tail_ms = 0, transitions
    are synchronous modulo one tokio tick.
  * press_on_release_off_default_tail — release_tail_ms = 200,
    transmit stays on briefly past key-up then transitions off.

cargo test --workspace --lib: 76 passed / 0 failed / 1 ignored.
This commit is contained in:
EdisonJwa
2026-05-16 00:21:58 +08:00
parent 82b99ebcff
commit 87cff52c0a
+46
View File
@@ -373,4 +373,50 @@ mod tests {
tokio::time::sleep(Duration::from_millis(15)).await;
assert!(!real_gate.load(), "stop must drop transmit cleanly");
}
/// User-acceptance shape: "press → ptt on, release → ptt off".
/// Exercises the full chain on the same surface
/// `audioStats.pttActive` reads from in production:
/// backend press_gate → edge-watcher → release-tail → selector
/// → real gate. With `release_tail_ms = 0` the transition is
/// synchronous (modulo one tokio tick).
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn press_on_release_off_zero_tail() {
let (real_gate, _sel, tail) = setup(0);
let controller = PttController::new(tail);
tokio::time::sleep(Duration::from_millis(10)).await;
assert!(!real_gate.load(), "idle baseline must be off");
controller.press_gate().set(true);
tokio::time::sleep(Duration::from_millis(10)).await;
assert!(real_gate.load(), "press: pttActive must be true");
controller.press_gate().set(false);
tokio::time::sleep(Duration::from_millis(15)).await;
assert!(!real_gate.load(), "release: pttActive must be false");
}
/// Same acceptance shape but with the default 200 ms tail —
/// verifies that the gate stays "on" briefly past release and
/// then transitions to "off" within a bounded time, matching
/// the SDD-096 spec.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn press_on_release_off_default_tail() {
let (real_gate, _sel, tail) = setup(200);
let controller = PttController::new(tail);
tokio::time::sleep(Duration::from_millis(10)).await;
controller.press_gate().set(true);
tokio::time::sleep(Duration::from_millis(10)).await;
assert!(real_gate.load(), "press: pttActive must be true");
controller.press_gate().set(false);
// Mid-tail: still on.
tokio::time::sleep(Duration::from_millis(50)).await;
assert!(real_gate.load(), "tail window: pttActive must still be true");
// Past the tail: off.
tokio::time::sleep(Duration::from_millis(220)).await;
assert!(!real_gate.load(), "post-tail: pttActive must be false");
}
}