//! 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}" ), } }); }); }); } }