//! Integration coverage for the server camera-selection contract. //! //! Scope: exercise environment-driven camera mode selection and stable enum //! string mappings via the public camera module API. //! Targets: `server/src/camera.rs`. //! Why: camera policy decides the entire stream transport path, so these //! behavior contracts belong in centralized testing. use lesavka_server::camera::{ CameraCodec, CameraOutput, current_camera_config, update_camera_config, }; use serial_test::serial; use temp_env::with_var; #[test] fn camera_enum_strings_are_stable() { assert_eq!(CameraOutput::Uvc.as_str(), "uvc"); assert_eq!(CameraOutput::Hdmi.as_str(), "hdmi"); assert_eq!(CameraCodec::Mjpeg.as_str(), "mjpeg"); assert_eq!(CameraCodec::H264.as_str(), "h264"); } #[test] #[serial] fn camera_config_uses_interval_when_fps_is_unset_or_invalid() { with_var("LESAVKA_CAM_OUTPUT", Some("uvc"), || { with_var("LESAVKA_UVC_WIDTH", Some("640"), || { with_var("LESAVKA_UVC_HEIGHT", Some("360"), || { with_var("LESAVKA_UVC_FPS", None::<&str>, || { with_var("LESAVKA_UVC_INTERVAL", Some("250000"), || { let cfg = update_camera_config(); assert_eq!(cfg.output, CameraOutput::Uvc); assert_eq!(cfg.codec, CameraCodec::Mjpeg); assert_eq!(cfg.width, 640); assert_eq!(cfg.height, 360); assert_eq!(cfg.fps, 40); }); }); with_var("LESAVKA_UVC_FPS", Some("not-a-number"), || { with_var("LESAVKA_UVC_INTERVAL", Some("250000"), || { let cfg = update_camera_config(); assert_eq!(cfg.output, CameraOutput::Uvc); assert_eq!(cfg.codec, CameraCodec::Mjpeg); assert_eq!(cfg.width, 640); assert_eq!(cfg.height, 360); assert_eq!(cfg.fps, 40); }); }); }); }); }); } #[test] #[serial] fn camera_config_zero_interval_falls_back_to_default_fps() { with_var("LESAVKA_CAM_OUTPUT", Some("uvc"), || { with_var("LESAVKA_UVC_WIDTH", Some("800"), || { with_var("LESAVKA_UVC_HEIGHT", Some("600"), || { with_var("LESAVKA_UVC_FPS", None::<&str>, || { with_var("LESAVKA_UVC_INTERVAL", Some("0"), || { let cfg = update_camera_config(); assert_eq!(cfg.output, CameraOutput::Uvc); assert_eq!(cfg.codec, CameraCodec::Mjpeg); assert_eq!(cfg.width, 800); assert_eq!(cfg.height, 600); assert_eq!(cfg.fps, 30); }); }); }); }); }); } #[test] #[serial] fn camera_config_forced_hdmi_defaults_to_mjpeg_without_hardware_decode() { with_var("LESAVKA_CAM_OUTPUT", Some("hdmi"), || { with_var("LESAVKA_HW_H264", None::<&str>, || { with_var("LESAVKA_CAM_CODEC", None::<&str>, || { let cfg = update_camera_config(); assert_eq!(cfg.output, CameraOutput::Hdmi); assert_eq!(cfg.codec, CameraCodec::Mjpeg); assert_eq!(cfg.width, 1280); assert_eq!(cfg.height, 720); assert_eq!(cfg.fps, 30); let cached = current_camera_config(); assert_eq!(cached.output, CameraOutput::Hdmi); assert_eq!(cached.codec, CameraCodec::Mjpeg); assert_eq!(cached.width, 1280); assert_eq!(cached.height, 720); assert_eq!(cached.fps, 30); }); }); }); } #[test] #[serial] fn camera_config_forced_hdmi_honors_1080p_uplink_override() { with_var("LESAVKA_CAM_OUTPUT", Some("hdmi"), || { with_var("LESAVKA_HW_H264", None::<&str>, || { with_var("LESAVKA_CAM_WIDTH", Some("1920"), || { with_var("LESAVKA_CAM_HEIGHT", Some("1080"), || { with_var("LESAVKA_CAM_FPS", Some("30"), || { with_var("LESAVKA_CAM_CODEC", None::<&str>, || { let cfg = update_camera_config(); assert_eq!(cfg.output, CameraOutput::Hdmi); assert_eq!(cfg.codec, CameraCodec::Mjpeg); assert_eq!(cfg.width, 1920); assert_eq!(cfg.height, 1080); assert_eq!(cfg.fps, 30); }); }); }); }); }); }); } #[test] #[serial] fn camera_config_forced_hdmi_honors_explicit_h264_override() { with_var("LESAVKA_CAM_OUTPUT", Some("hdmi"), || { with_var("LESAVKA_HW_H264", None::<&str>, || { with_var("LESAVKA_CAM_CODEC", Some("h264"), || { let cfg = update_camera_config(); assert_eq!(cfg.output, CameraOutput::Hdmi); assert_eq!(cfg.codec, CameraCodec::H264); assert_eq!(cfg.width, 1280); assert_eq!(cfg.height, 720); assert_eq!(cfg.fps, 30); }); }); }); } #[test] #[serial] fn camera_config_output_override_is_case_insensitive() { with_var("LESAVKA_CAM_OUTPUT", Some(" UVC "), || { with_var("LESAVKA_UVC_WIDTH", Some("1024"), || { with_var("LESAVKA_UVC_HEIGHT", Some("576"), || { with_var("LESAVKA_UVC_FPS", Some("12"), || { let cfg = update_camera_config(); assert_eq!(cfg.output, CameraOutput::Uvc); assert_eq!(cfg.codec, CameraCodec::Mjpeg); assert_eq!(cfg.width, 1024); assert_eq!(cfg.height, 576); assert_eq!(cfg.fps, 12); }); }); }); }); } #[test] #[serial] fn camera_config_defaults_when_uvc_dimensions_and_rate_are_missing() { with_var("LESAVKA_CAM_OUTPUT", Some("uvc"), || { with_var("LESAVKA_UVC_WIDTH", None::<&str>, || { with_var("LESAVKA_UVC_HEIGHT", None::<&str>, || { with_var("LESAVKA_UVC_FPS", None::<&str>, || { with_var("LESAVKA_UVC_INTERVAL", None::<&str>, || { let cfg = update_camera_config(); assert_eq!(cfg.output, CameraOutput::Uvc); assert_eq!(cfg.codec, CameraCodec::Mjpeg); assert_eq!(cfg.width, 1280); assert_eq!(cfg.height, 720); assert_eq!(cfg.fps, 30); }); }); }); }); }); } #[test] #[serial] fn camera_config_prefers_explicit_fps_over_interval() { with_var("LESAVKA_CAM_OUTPUT", Some("uvc"), || { with_var("LESAVKA_UVC_WIDTH", Some("800"), || { with_var("LESAVKA_UVC_HEIGHT", Some("600"), || { with_var("LESAVKA_UVC_FPS", Some("48"), || { with_var("LESAVKA_UVC_INTERVAL", Some("250000"), || { let cfg = update_camera_config(); assert_eq!(cfg.output, CameraOutput::Uvc); assert_eq!(cfg.codec, CameraCodec::Mjpeg); assert_eq!(cfg.width, 800); assert_eq!(cfg.height, 600); assert_eq!(cfg.fps, 48); }); }); }); }); }); } #[test] #[serial] fn camera_config_invalid_output_falls_back_to_detected_policy() { with_var("LESAVKA_CAM_OUTPUT", Some("not-a-real-mode"), || { let cfg = update_camera_config(); match cfg.output { CameraOutput::Uvc => { assert_eq!(cfg.codec, CameraCodec::Mjpeg); assert!(cfg.fps > 0); } CameraOutput::Hdmi => { assert!(matches!(cfg.codec, CameraCodec::Mjpeg | CameraCodec::H264)); assert_eq!(cfg.fps, 30); } } }); }