2026-04-12 18:41:13 -03:00
|
|
|
//! Integration smoke coverage for client runtime constructors.
|
|
|
|
|
//!
|
|
|
|
|
//! Scope: execute public client startup/media constructors through stable APIs
|
|
|
|
|
//! without requiring hardware-specific assertions.
|
|
|
|
|
//! Targets: `client/src/app.rs`, `client/src/input/camera.rs`,
|
|
|
|
|
//! `client/src/input/microphone.rs`, and `client/src/output/audio.rs`.
|
|
|
|
|
//! Why: these paths are operationally important but often depend on runtime
|
|
|
|
|
//! host capabilities; smoke contracts keep them exercised in CI.
|
|
|
|
|
|
|
|
|
|
use lesavka_client::LesavkaClientApp;
|
|
|
|
|
use lesavka_client::input::camera::{CameraCapture, CameraCodec, CameraConfig};
|
|
|
|
|
use lesavka_client::input::inputs::InputAggregator;
|
|
|
|
|
use lesavka_client::input::microphone::MicrophoneCapture;
|
|
|
|
|
use lesavka_client::output::audio::AudioOut;
|
|
|
|
|
use lesavka_client::output::video::MonitorWindow;
|
|
|
|
|
use lesavka_common::lesavka::AudioPacket;
|
|
|
|
|
use lesavka_common::lesavka::{KeyboardReport, MouseReport, VideoPacket};
|
|
|
|
|
use serial_test::serial;
|
|
|
|
|
use temp_env::with_var;
|
|
|
|
|
use tokio::sync::{broadcast, mpsc};
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn client_app_new_supports_headless_mode() {
|
|
|
|
|
with_var("LESAVKA_HEADLESS", Some("1"), || {
|
|
|
|
|
with_var(
|
|
|
|
|
"LESAVKA_SERVER_ADDR",
|
|
|
|
|
Some("http://127.0.0.1:50051"),
|
|
|
|
|
|| {
|
|
|
|
|
let app = LesavkaClientApp::new();
|
|
|
|
|
assert!(app.is_ok(), "expected headless app construction to succeed");
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn camera_capture_stub_pull_returns_none() {
|
|
|
|
|
gstreamer::init().ok();
|
|
|
|
|
let cam = CameraCapture::new_stub();
|
|
|
|
|
assert!(
|
|
|
|
|
cam.pull().is_none(),
|
|
|
|
|
"stub capture should not produce real frames"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn camera_capture_test_pattern_constructor_is_stable() {
|
|
|
|
|
with_var("LESAVKA_CAM_TEST_PATTERN", Some("ball"), || {
|
|
|
|
|
let result = CameraCapture::new(
|
|
|
|
|
Some("test"),
|
|
|
|
|
Some(CameraConfig {
|
|
|
|
|
codec: CameraCodec::Mjpeg,
|
|
|
|
|
width: 320,
|
|
|
|
|
height: 240,
|
|
|
|
|
fps: 5,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
match result {
|
|
|
|
|
Ok(_cam) => {}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
assert!(
|
|
|
|
|
!err.to_string().trim().is_empty(),
|
|
|
|
|
"camera constructor returned an empty error"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn microphone_capture_constructor_is_stable_with_missing_source_hint() {
|
|
|
|
|
with_var(
|
|
|
|
|
"LESAVKA_MIC_SOURCE",
|
|
|
|
|
Some("definitely-missing-source"),
|
|
|
|
|
|| {
|
|
|
|
|
let result = MicrophoneCapture::new();
|
|
|
|
|
match result {
|
|
|
|
|
Ok(_mic) => {}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
assert!(
|
|
|
|
|
!err.to_string().trim().is_empty(),
|
|
|
|
|
"microphone constructor returned an empty error"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn audio_out_constructor_and_push_are_stable() {
|
|
|
|
|
with_var("LESAVKA_AUDIO_SINK", Some("autoaudiosink"), || {
|
|
|
|
|
with_var("LESAVKA_TAP_AUDIO", Some("0"), || match AudioOut::new() {
|
|
|
|
|
Ok(out) => {
|
|
|
|
|
out.push(AudioPacket {
|
|
|
|
|
id: 0,
|
|
|
|
|
pts: 0,
|
|
|
|
|
data: Vec::new(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
assert!(
|
|
|
|
|
!err.to_string().trim().is_empty(),
|
|
|
|
|
"audio output constructor returned an empty error"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn input_aggregator_constructor_is_stable() {
|
|
|
|
|
let (kbd_tx, _) = broadcast::channel::<KeyboardReport>(8);
|
|
|
|
|
let (mou_tx, _) = broadcast::channel::<MouseReport>(8);
|
|
|
|
|
let (paste_tx, _paste_rx) = mpsc::unbounded_channel::<String>();
|
|
|
|
|
let _agg = InputAggregator::new(false, kbd_tx, mou_tx, Some(paste_tx));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn monitor_window_constructor_and_push_are_stable() {
|
|
|
|
|
match MonitorWindow::new(0) {
|
|
|
|
|
Ok(win) => {
|
|
|
|
|
win.push_packet(VideoPacket {
|
|
|
|
|
id: 0,
|
|
|
|
|
pts: 0,
|
|
|
|
|
data: vec![0, 0, 0, 1, 0x65],
|
2026-04-16 21:18:34 -03:00
|
|
|
..Default::default()
|
2026-04-12 18:41:13 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
assert!(
|
|
|
|
|
!err.to_string().trim().is_empty(),
|
|
|
|
|
"monitor window constructor returned an empty error"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|