lesavka/testing/tests/server_audio_include_contract.rs

93 lines
3.0 KiB
Rust
Raw Normal View History

//! Integration coverage for server audio capture/sink plumbing.
//!
//! Scope: compile `server/src/audio.rs` as a module and exercise public audio
//! constructors/helpers across deterministic error and smoke paths.
//! Targets: `server/src/audio.rs`.
//! Why: audio pipeline setup is branchy and should stay stable without requiring
//! physical ALSA/UAC hardware in CI.
#[path = "../../server/src/audio.rs"]
#[allow(warnings)]
mod server_audio_contract;
mod tests {
use super::server_audio_contract::{ClipTap, Voice, ear};
#[cfg(coverage)]
use futures_util::StreamExt;
use lesavka_common::lesavka::AudioPacket;
use serial_test::serial;
#[test]
#[serial]
fn ear_rejects_malformed_pipeline_device_string() {
let rt = tokio::runtime::Runtime::new().expect("runtime");
let result = rt.block_on(ear("hw:UAC2Gadget,0\" ! broken-pipe", 0));
assert!(result.is_err(), "malformed device string should fail parse");
}
#[test]
#[serial]
fn ear_missing_device_path_is_stable() {
let rt = tokio::runtime::Runtime::new().expect("runtime");
let result = rt.block_on(ear("hw:DefinitelyMissingDevice,0", 0));
if let Err(err) = result {
assert!(!err.to_string().trim().is_empty());
}
}
#[test]
#[serial]
fn ear_existing_non_audio_node_reaches_runtime_paths() {
let rt = tokio::runtime::Runtime::new().expect("runtime");
let result = rt.block_on(async {
tokio::time::timeout(std::time::Duration::from_millis(250), ear("/dev/null", 0)).await
});
match result {
Ok(Ok(stream)) => drop(stream),
Ok(Err(_)) | Err(_) => {}
}
}
#[test]
fn clip_tap_feed_flush_and_drop_are_stable() {
let mut tap = ClipTap::new("audio-contract", std::time::Duration::from_millis(1));
tap.feed(&[1, 2, 3, 4, 5]);
tap.feed(&vec![9u8; 300_000]);
tap.flush();
tap.flush(); // empty flush should be a no-op
drop(tap);
}
#[test]
#[serial]
fn voice_constructor_and_push_finish_are_stable() {
let rt = tokio::runtime::Runtime::new().expect("runtime");
let result = rt.block_on(Voice::new("hw:DefinitelyMissingDevice,0"));
match result {
Ok(mut voice) => {
voice.push(&AudioPacket {
id: 0,
pts: 77,
data: vec![0xFF, 0xF1, 0x50, 0x80, 0x00, 0x1F, 0xFC],
});
voice.finish();
}
Err(err) => {
assert!(!err.to_string().trim().is_empty());
}
}
}
#[cfg(coverage)]
#[test]
#[serial]
fn audio_stream_poll_next_is_stable_when_channel_is_closed() {
let rt = tokio::runtime::Runtime::new().expect("runtime");
let polled = rt.block_on(async {
let mut stream = ear("/dev/null", 0).await.expect("coverage ear stream");
stream.next().await
});
assert!(polled.is_none(), "closed stream should yield None");
}
}