lesavka/testing/tests/client_microphone_requested_source_contract.rs

142 lines
4.8 KiB
Rust
Raw Normal View History

//! Contract tests for requested microphone source selection.
//!
//! Scope: include `client/src/input/microphone.rs` and exercise requested-source
//! success, fallback, and strict-probe failure behavior.
//! Targets: `client/src/input/microphone.rs`.
//! Why: transport probes should fail clearly when the named microphone is absent
//! instead of silently measuring the wrong host source.
#[allow(warnings)]
mod live_capture_clock {
include!("support/live_capture_clock_shim.rs");
}
#[allow(warnings)]
mod microphone_include_contract {
include!(env!("LESAVKA_CLIENT_MICROPHONE_SRC"));
use serial_test::serial;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use temp_env::with_var;
use tempfile::tempdir;
fn write_executable(dir: &Path, name: &str, body: &str) {
let path = dir.join(name);
fs::write(&path, body).expect("write script");
let mut perms = fs::metadata(&path).expect("metadata").permissions();
perms.set_mode(0o755);
fs::set_permissions(path, perms).expect("chmod");
}
fn with_fake_command(name: &str, script_body: &str, f: impl FnOnce()) {
let dir = tempdir().expect("tempdir");
write_executable(dir.path(), name, script_body);
let prior = std::env::var("PATH").unwrap_or_default();
let merged = if prior.is_empty() {
dir.path().display().to_string()
} else {
format!("{}:{prior}", dir.path().display())
};
with_var("PATH", Some(merged), f);
}
fn with_fake_pactl(script_body: &str, f: impl FnOnce()) {
with_fake_command("pactl", script_body, f);
}
fn with_fake_pw_dump(script_body: &str, f: impl FnOnce()) {
with_fake_command("pw-dump", script_body, f);
}
#[test]
#[serial]
fn new_uses_requested_source_fragment_when_available() {
let script = r#"#!/usr/bin/env sh
if [ "$1" = "list" ] && [ "$2" = "short" ] && [ "$3" = "sources" ]; then
echo "1 alsa_input.usb-LavMic_abc-00.analog-stereo module-alsa-card.c s16le 2ch 48000Hz RUNNING"
exit 0
fi
exit 0
"#;
with_fake_pactl(script, || {
with_var("LESAVKA_MIC_SOURCE", Some("LavMic_abc"), || {
let result = MicrophoneCapture::new();
if let Err(err) = result {
assert!(!err.to_string().trim().is_empty());
}
});
});
}
#[test]
#[serial]
fn resolve_source_desc_prefers_pipewire_named_source_when_available() {
if !MicrophoneCapture::pipewire_source_available() {
return;
}
let script = r#"#!/usr/bin/env sh
cat <<'JSON'
[
{"info":{"props":{"media.class":"Audio/Source","node.name":"alsa_input.usb-UpstreamMic"}}}
]
JSON
"#;
with_fake_pw_dump(script, || {
let desc =
MicrophoneCapture::resolve_source_desc("UpstreamMic").expect("pipewire source");
assert!(desc.contains("pipewiresrc target-object=alsa_input.usb-UpstreamMic"));
});
}
#[test]
#[serial]
fn new_falls_back_to_default_source_when_requested_fragment_is_missing() {
let script = r#"#!/usr/bin/env sh
if [ "$1" = "list" ] && [ "$2" = "short" ] && [ "$3" = "sources" ]; then
echo "0 alsa_input.pci.monitor module-alsa-card.c s16le 2ch 48000Hz RUNNING"
echo "1 alsa_input.usb-DeskMic_777-00.analog-stereo module-alsa-card.c s16le 2ch 48000Hz IDLE"
exit 0
fi
exit 0
"#;
with_fake_pactl(script, || {
with_var("LESAVKA_MIC_SOURCE", Some("missing-fragment"), || {
let result = MicrophoneCapture::new();
if let Err(err) = result {
assert!(!err.to_string().trim().is_empty());
}
});
});
}
#[test]
#[serial]
fn strict_probe_mode_rejects_missing_requested_source() {
let script = r#"#!/usr/bin/env sh
if [ "$1" = "list" ] && [ "$2" = "short" ] && [ "$3" = "sources" ]; then
echo "0 alsa_input.pci.monitor module-alsa-card.c s16le 2ch 48000Hz RUNNING"
echo "1 alsa_input.usb-DeskMic_777-00.analog-stereo module-alsa-card.c s16le 2ch 48000Hz IDLE"
exit 0
fi
exit 0
"#;
with_fake_pactl(script, || {
with_var("LESAVKA_MIC_SOURCE", Some("missing-fragment"), || {
with_var("LESAVKA_REQUIRE_EXPLICIT_MEDIA_SOURCES", Some("1"), || {
match MicrophoneCapture::new() {
Ok(_) => panic!("missing mic should fail"),
Err(err) => assert!(
err.to_string()
.contains("requested mic 'missing-fragment' was not found"),
"unexpected error: {err}"
),
}
});
});
});
}
}