//! Integration coverage for server state-oriented RPC handler branches. //! //! Scope: include `server/src/main.rs` and exercise calibration, capture-power, //! and upstream-sync RPC surfaces. //! Targets: `server/src/main.rs`. //! Why: these RPCs expose live operational state, so tests should guard reply //! shapes without requiring gadget, HID, or capture hardware. #[allow(warnings)] mod server_main_state_rpc { include!(env!("LESAVKA_SERVER_MAIN_SRC")); use serial_test::serial; use temp_env::with_var; use tempfile::tempdir; fn build_handler_for_tests_with_modes( kb_writable: bool, ms_writable: bool, ) -> (tempfile::TempDir, Handler) { let dir = tempdir().expect("tempdir"); let kb_path = dir.path().join("hidg0.bin"); let ms_path = dir.path().join("hidg1.bin"); std::fs::write(&kb_path, []).expect("create kb file"); std::fs::write(&ms_path, []).expect("create ms file"); let kb = tokio::fs::File::from_std( std::fs::OpenOptions::new() .read(true) .write(kb_writable) .create(kb_writable) .truncate(kb_writable) .open(&kb_path) .expect("open kb"), ); let ms = tokio::fs::File::from_std( std::fs::OpenOptions::new() .read(true) .write(ms_writable) .create(ms_writable) .truncate(ms_writable) .open(&ms_path) .expect("open ms"), ); let handler = with_var("LESAVKA_CAPTURE_POWER_UNIT", Some("none"), || Handler { kb: std::sync::Arc::new(tokio::sync::Mutex::new(Some(kb))), ms: std::sync::Arc::new(tokio::sync::Mutex::new(Some(ms))), gadget: UsbGadget::new("lesavka"), did_cycle: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), camera_rt: std::sync::Arc::new(CameraRuntime::new()), upstream_media_rt: std::sync::Arc::new(UpstreamMediaRuntime::new()), calibration: std::sync::Arc::new(CalibrationStore::load(std::sync::Arc::new( UpstreamMediaRuntime::new(), ))), capture_power: CapturePowerManager::new(), eye_hubs: std::sync::Arc::new( tokio::sync::Mutex::new(std::collections::HashMap::new()), ), }); (dir, handler) } fn build_handler_for_tests() -> (tempfile::TempDir, Handler) { build_handler_for_tests_with_modes(true, true) } #[test] #[cfg(coverage)] #[serial] fn capture_power_rpcs_surface_stub_snapshot_and_manual_modes() { let (_dir, handler) = build_handler_for_tests(); let rt = tokio::runtime::Runtime::new().expect("runtime"); with_var( "LESAVKA_TEST_UDEV_CAPTURE_DEVICES", Some("not-a-number"), || { assert_eq!(Handler::detected_capture_devices_from_udev(), 0); }, ); with_var("LESAVKA_TEST_UDEV_CAPTURE_DEVICES", Some("9"), || { assert_eq!(Handler::detected_capture_devices_from_udev(), 2); }); let snapshot = rt .block_on(async { handler .get_capture_power(tonic::Request::new(Empty {})) .await }) .expect("capture power snapshot") .into_inner(); assert!(snapshot.available); assert!(!snapshot.enabled); assert_eq!(snapshot.mode, "auto"); let forced_on = rt .block_on(async { handler .set_capture_power(tonic::Request::new(SetCapturePowerRequest { enabled: true, command: CapturePowerCommand::ForceOn as i32, })) .await }) .expect("force capture power on") .into_inner(); assert!(forced_on.available); assert!(forced_on.enabled); assert_eq!(forced_on.mode, "forced-on"); let forced_off = rt .block_on(async { handler .set_capture_power(tonic::Request::new(SetCapturePowerRequest { enabled: false, command: CapturePowerCommand::ForceOff as i32, })) .await }) .expect("force capture power off") .into_inner(); assert!(forced_off.available); assert!(!forced_off.enabled); assert_eq!(forced_off.mode, "forced-off"); let auto = rt .block_on(async { handler .set_capture_power(tonic::Request::new(SetCapturePowerRequest { enabled: false, command: CapturePowerCommand::Auto as i32, })) .await }) .expect("return capture power to auto") .into_inner(); assert!(auto.available); assert!(!auto.enabled); assert_eq!(auto.mode, "auto"); let legacy_fallback = rt .block_on(async { handler .set_capture_power(tonic::Request::new(SetCapturePowerRequest { enabled: true, command: CapturePowerCommand::Unspecified as i32, })) .await }) .expect("legacy bool fallback") .into_inner(); assert!(legacy_fallback.available); assert!(legacy_fallback.enabled); assert_eq!(legacy_fallback.mode, "forced-on"); } #[test] #[cfg(coverage)] #[serial] fn calibration_rpcs_surface_current_state_and_apply_updates() { let dir = tempdir().expect("calibration dir"); let calibration_path = dir.path().join("calibration.toml"); with_var( "LESAVKA_CALIBRATION_PATH", Some(calibration_path.to_string_lossy().to_string()), || { let (_dir, handler) = build_handler_for_tests(); let rt = tokio::runtime::Runtime::new().expect("runtime"); let initial = rt .block_on(async { handler.get_calibration(tonic::Request::new(Empty {})).await }) .expect("initial calibration") .into_inner(); assert_eq!(initial.profile, "mjpeg"); assert_eq!(initial.active_audio_offset_us, 0); let adjusted = rt .block_on(async { handler .calibrate(tonic::Request::new(CalibrationRequest { action: lesavka_common::lesavka::CalibrationAction::BlindEstimate as i32, audio_delta_us: 10_000, video_delta_us: 2_000, observed_delivery_skew_ms: 42.0, observed_enqueue_skew_ms: 2.5, note: "coverage estimate".to_string(), })) .await }) .expect("calibrate") .into_inner(); assert_eq!(adjusted.source, "blind"); assert_eq!(adjusted.active_audio_offset_us, 10_000); assert_eq!(adjusted.active_video_offset_us, 132_000); assert!( std::fs::read_to_string(calibration_path) .expect("persisted") .contains("active_audio_offset_us=-35000") ); }, ); } #[test] #[cfg(coverage)] #[serial] fn upstream_sync_rpc_surfaces_planner_snapshot() { let (_dir, handler) = build_handler_for_tests(); let rt = tokio::runtime::Runtime::new().expect("runtime"); let lease_camera = handler.upstream_media_rt.activate_camera(); let lease_microphone = handler.upstream_media_rt.activate_microphone(); assert_eq!(lease_camera.session_id, lease_microphone.session_id); let initial = rt .block_on(async { handler .get_upstream_sync(tonic::Request::new(Empty {})) .await }) .expect("planner sync state") .into_inner(); assert_eq!(initial.phase, "acquiring"); assert_eq!(initial.session_id, lease_camera.session_id); } }