//! 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::(8); let (mou_tx, _) = broadcast::channel::(8); let (paste_tx, _paste_rx) = mpsc::unbounded_channel::(); 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], }); } Err(err) => { assert!( !err.to_string().trim().is_empty(), "monitor window constructor returned an empty error" ); } } }