// Integration coverage for client app support helpers. // // Scope: exercise server-address, camera-capability, and retry-delay helpers // against deterministic local types. // Targets: `client/src/app_support.rs`. // Why: these startup decisions shape live camera codec negotiation and should // stay covered without adding source-file LOC to the hygiene ratchet. mod handshake { #[allow(dead_code)] #[derive(Clone, Debug)] pub struct PeerCaps { pub camera: bool, pub microphone: bool, pub bundled_webcam_media: bool, pub server_version: Option, pub camera_output: Option, pub camera_codec: Option, pub camera_width: Option, pub camera_height: Option, pub camera_fps: Option, pub eye_width: Option, pub eye_height: Option, pub eye_fps: Option, } } mod input { pub mod camera { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum CameraCodec { Mjpeg, H264, Hevc, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct CameraConfig { pub codec: CameraCodec, pub width: u32, pub height: u32, pub fps: u32, } } } #[path = "../../../../client/src/app_support.rs"] mod app_support; use app_support::{ DEFAULT_SERVER_ADDR, camera_config_from_caps, next_delay, resolve_server_addr, sanitize_video_queue, }; use handshake::PeerCaps; use input::camera::CameraCodec; use serial_test::serial; use std::time::Duration; fn peer_caps(codec: &str) -> PeerCaps { PeerCaps { camera: true, microphone: false, bundled_webcam_media: false, server_version: None, camera_output: Some(String::from("uvc")), camera_codec: Some(codec.to_string()), camera_width: Some(1280), camera_height: Some(720), camera_fps: Some(25), eye_width: None, eye_height: None, eye_fps: None, } } #[test] fn resolve_server_addr_prefers_cli_then_env_then_default() { assert_eq!( resolve_server_addr(&[String::from("http://cli:1")], Some("http://env:2")), "http://cli:1" ); assert_eq!( resolve_server_addr( &[ String::from("--no-launcher"), String::from("--server"), String::from("http://cli-flag:3"), ], Some("http://env:2"), ), "http://cli-flag:3" ); assert_eq!( resolve_server_addr(&[String::from("--launcher")], Some("http://env:2")), "http://env:2" ); assert_eq!( resolve_server_addr(&[], Some("http://env:2")), "http://env:2" ); assert_eq!(resolve_server_addr(&[], None), DEFAULT_SERVER_ADDR); } #[test] #[serial] fn camera_config_from_caps_requires_complete_profile() { temp_env::with_var("LESAVKA_CAM_CODEC", None::<&str>, || { let mut caps = peer_caps("mjpeg"); let config = camera_config_from_caps(&caps).expect("complete caps should map"); assert!(matches!(config.codec, CameraCodec::Mjpeg)); assert_eq!(config.width, 1280); caps.camera_codec = Some(String::from("h265")); let config = camera_config_from_caps(&caps).expect("h265 alias should map"); assert!(matches!(config.codec, CameraCodec::Hevc)); caps.camera_codec = Some(String::from("vp9")); assert!(camera_config_from_caps(&caps).is_none()); }); } #[test] #[serial] fn camera_config_from_caps_uses_negotiated_codec_over_launcher_default() { temp_env::with_var("LESAVKA_CAM_CODEC", Some("mjpeg"), || { let config = camera_config_from_caps(&peer_caps("hevc")).expect("caps codec should map"); assert!(matches!(config.codec, CameraCodec::Hevc)); }); } #[test] #[serial] fn camera_config_from_caps_allows_explicit_forced_codec_override() { temp_env::with_vars( [ ("LESAVKA_CAM_CODEC", Some("mjpeg")), ("LESAVKA_CAM_CODEC_FORCE", Some("1")), ], || { let config = camera_config_from_caps(&peer_caps("hevc")).expect("forced override"); assert!(matches!(config.codec, CameraCodec::Mjpeg)); }, ); } #[test] #[serial] fn camera_config_force_flag_ignores_unknown_override() { temp_env::with_vars( [ ("LESAVKA_CAM_CODEC", Some("vp9")), ("LESAVKA_CAM_CODEC_FORCE", Some("1")), ], || { let config = camera_config_from_caps(&peer_caps("hevc")).expect("fallback codec"); assert!(matches!(config.codec, CameraCodec::Hevc)); }, ); } #[test] fn sanitize_video_queue_enforces_floor() { assert_eq!(sanitize_video_queue(None), 8); assert_eq!(sanitize_video_queue(Some(1)), 4); assert_eq!(sanitize_video_queue(Some(32)), 32); } #[test] fn next_delay_doubles_until_capped() { assert_eq!(next_delay(Duration::from_secs(1)), Duration::from_secs(2)); assert_eq!(next_delay(Duration::from_secs(15)), Duration::from_secs(30)); assert_eq!(next_delay(Duration::from_secs(31)), Duration::from_secs(30)); }