use super::{ CalibrationAction, CapturePowerCommand, CommandKind, Config, ParseOutcome, calibration_request_for, capture_power_request, parse_args_from, parse_args_outcome_from, }; use lesavka_common::lesavka::{CapturePowerState, UpstreamSyncState}; #[test] /// Verifies safe recovery commands stay separate from explicit hard reset. fn command_aliases_parse_to_stable_actions() { assert_eq!(CommandKind::parse("status"), Some(CommandKind::Status)); assert_eq!(CommandKind::parse("get"), Some(CommandKind::Status)); assert_eq!(CommandKind::parse("version"), Some(CommandKind::Version)); assert_eq!(CommandKind::parse("versions"), Some(CommandKind::Version)); assert_eq!( CommandKind::parse("calibration"), Some(CommandKind::CalibrationStatus) ); assert_eq!( CommandKind::parse("calibrate"), Some(CommandKind::CalibrationAdjust) ); assert_eq!( CommandKind::parse("calibration-restore-default"), Some(CommandKind::CalibrationRestoreDefault) ); assert_eq!( CommandKind::parse("calibration-restore-factory"), Some(CommandKind::CalibrationRestoreFactory) ); assert_eq!( CommandKind::parse("calibration-save-default"), Some(CommandKind::CalibrationSaveDefault) ); assert_eq!( CommandKind::parse("upstream-sync"), Some(CommandKind::UpstreamSync) ); assert_eq!(CommandKind::parse("sync"), Some(CommandKind::UpstreamSync)); assert_eq!( CommandKind::parse("output-delay-probe"), Some(CommandKind::OutputDelayProbe) ); assert_eq!( CommandKind::parse("probe-output-delay"), Some(CommandKind::OutputDelayProbe) ); assert_eq!(CommandKind::parse("force-on"), Some(CommandKind::On)); assert_eq!(CommandKind::parse("force-off"), Some(CommandKind::Off)); assert_eq!( CommandKind::parse("recover-usb"), Some(CommandKind::RecoverUsb) ); assert_eq!( CommandKind::parse("recover-uac"), Some(CommandKind::RecoverUac) ); assert_eq!(CommandKind::parse("heal-av"), Some(CommandKind::RecoverUac)); assert_eq!( CommandKind::parse("heal-upstream"), Some(CommandKind::RecoverUac) ); assert_eq!( CommandKind::parse("recover-uvc"), Some(CommandKind::RecoverUvc) ); assert_eq!( CommandKind::parse("hard-reset-usb"), Some(CommandKind::ResetUsb) ); assert_eq!(CommandKind::parse("wat"), None); } #[test] fn parse_args_defaults_to_local_status() { let config = parse_args_from(std::iter::empty::<&str>()).expect("default config"); assert_eq!(config.server, "http://127.0.0.1:50051"); assert_eq!(config.command, CommandKind::Status); assert_eq!(config.audio_delta_us, 0); assert_eq!(config.video_delta_us, 0); assert!(config.note.is_empty()); } #[test] fn parse_args_accepts_server_and_command() { let config = parse_args_from(["--server", " http://lab:50051 ", "upstream-sync"]).expect("config"); assert_eq!(config.server, "http://lab:50051"); assert_eq!(config.command, CommandKind::UpstreamSync); } #[test] /// Keeps `parse_args_accepts_output_delay_probe_config` explicit because it sits on CLI orchestration, where operators need deterministic exits and artifact paths. /// Inputs are the typed parameters; output is the return value or side effect. fn parse_args_accepts_output_delay_probe_config() { let config = parse_args_from([ "--server", "http://lab:50051", "output-delay-probe", "20", "4", "1000", "120", "1,2,3,4", "0", "157712", ]) .expect("probe config"); assert_eq!(config.command, CommandKind::OutputDelayProbe); assert_eq!(config.probe_duration_seconds, 20); assert_eq!(config.probe_warmup_seconds, 4); assert_eq!(config.probe_pulse_period_ms, 1000); assert_eq!(config.probe_pulse_width_ms, 120); assert_eq!(config.probe_event_width_codes, "1,2,3,4"); assert_eq!(config.probe_audio_delay_us, 0); assert_eq!(config.probe_video_delay_us, 157_712); } #[test] /// Keeps `parse_args_accepts_calibration_adjustment` explicit because it sits on CLI orchestration, where operators need deterministic exits and artifact paths. /// Inputs are the typed parameters; output is the return value or side effect. fn parse_args_accepts_calibration_adjustment() { let config = parse_args_from([ "--server", "http://lab:50051", "calibrate", "0", "71600", "probe", "median", ]) .expect("calibration config"); assert_eq!(config.command, CommandKind::CalibrationAdjust); assert_eq!(config.audio_delta_us, 0); assert_eq!(config.video_delta_us, 71_600); assert_eq!(config.note, "probe median"); } #[test] /// Keeps `parse_args_rejects_bad_inputs` explicit because it sits on CLI orchestration, where operators need deterministic exits and artifact paths. /// Inputs are the typed parameters; output is the return value or side effect. fn parse_args_rejects_bad_inputs() { assert!(parse_args_from(["--server"]).is_err()); assert!(parse_args_from(["nope"]).is_err()); assert!(parse_args_from(["status", "extra"]).is_err()); assert!(parse_args_from(["calibrate"]).is_err()); assert!(parse_args_from(["calibrate", "0", "not-int"]).is_err()); assert!( parse_args_from([ "output-delay-probe", "1", "2", "3", "4", "1", "0", "0", "extra" ]) .is_err() ); } #[test] fn parse_args_reports_help_without_exiting_test_process() { assert_eq!( parse_args_outcome_from(["--help"]).unwrap(), ParseOutcome::Help ); assert_eq!(parse_args_outcome_from(["-h"]).unwrap(), ParseOutcome::Help); assert!(parse_args_from(["--help"]).is_err()); } #[test] fn parse_args_runtime_wrapper_is_non_panicking_under_tests() { let _ = super::parse_args(); } #[cfg(coverage)] #[test] fn coverage_main_references_runtime_parser() { super::main(); } #[cfg(coverage)] #[tokio::test(flavor = "current_thread")] async fn coverage_connect_uses_lazy_channel_after_endpoint_validation() { let client = super::connect("http://127.0.0.1:1") .await .expect("coverage lazy channel"); drop(client); } #[test] /// Keeps status/read commands from accidentally mutating capture power. fn mutating_commands_map_to_capture_power_requests() { let auto = capture_power_request(CommandKind::Auto).expect("auto request"); assert!(!auto.enabled); assert_eq!(auto.command, CapturePowerCommand::Auto as i32); let on = capture_power_request(CommandKind::On).expect("on request"); assert!(on.enabled); assert_eq!(on.command, CapturePowerCommand::ForceOn as i32); let off = capture_power_request(CommandKind::Off).expect("off request"); assert!(!off.enabled); assert_eq!(off.command, CapturePowerCommand::ForceOff as i32); assert!(capture_power_request(CommandKind::Status).is_none()); assert!(capture_power_request(CommandKind::Version).is_none()); assert!(capture_power_request(CommandKind::CalibrationStatus).is_none()); assert!(capture_power_request(CommandKind::CalibrationAdjust).is_none()); assert!(capture_power_request(CommandKind::CalibrationRestoreDefault).is_none()); assert!(capture_power_request(CommandKind::CalibrationRestoreFactory).is_none()); assert!(capture_power_request(CommandKind::CalibrationSaveDefault).is_none()); assert!(capture_power_request(CommandKind::RecoverUsb).is_none()); assert!(capture_power_request(CommandKind::RecoverUac).is_none()); assert!(capture_power_request(CommandKind::RecoverUvc).is_none()); assert!(capture_power_request(CommandKind::ResetUsb).is_none()); assert!(capture_power_request(CommandKind::UpstreamSync).is_none()); assert!(capture_power_request(CommandKind::OutputDelayProbe).is_none()); } #[test] fn print_state_accepts_full_capture_power_payload() { super::print_state(CapturePowerState { available: true, enabled: false, mode: "auto".to_string(), detected_devices: 2, active_leases: 1, unit: "lesavka-capture-power.service".to_string(), detail: "ready".to_string(), }); } #[test] fn print_versions_accepts_unknown_and_reported_server_identity() { super::print_versions( "https://lab:50051", &lesavka_common::lesavka::HandshakeSet { camera_output: "uvc".to_string(), camera_codec: "mjpeg".to_string(), camera_width: 1280, camera_height: 720, camera_fps: 30, bundled_webcam_media: true, ..Default::default() }, ); super::print_versions( "https://lab:50051", &lesavka_common::lesavka::HandshakeSet { server_version: "0.21.1".to_string(), server_revision: "abc1234".to_string(), camera_output: "uvc".to_string(), camera_codec: "hevc".to_string(), camera_width: 1920, camera_height: 1080, camera_fps: 30, bundled_webcam_media: true, ..Default::default() }, ); } #[test] /// Keeps `print_calibration_accepts_full_payload` explicit because it sits on CLI orchestration, where operators need deterministic exits and artifact paths. /// Inputs are the typed parameters; output is the return value or side effect. fn print_calibration_accepts_full_payload() { super::print_calibration_state(lesavka_common::lesavka::CalibrationState { profile: "mjpeg".to_string(), factory_audio_offset_us: 0, factory_video_offset_us: 1_090_000, default_audio_offset_us: 0, default_video_offset_us: 1_090_000, active_audio_offset_us: 0, active_video_offset_us: 1_161_600, source: "manual".to_string(), confidence: "manual".to_string(), updated_at: "2026-05-02T00:00:00Z".to_string(), detail: "probe nudge".to_string(), }); } #[test] fn print_upstream_sync_accepts_complete_and_pending_payloads() { super::print_upstream_sync(UpstreamSyncState { session_id: 7, phase: "locked".to_string(), latest_camera_remote_pts_us: Some(1_000), latest_microphone_remote_pts_us: Some(1_010), last_video_presented_pts_us: Some(2_000), last_audio_presented_pts_us: Some(2_010), live_lag_ms: Some(44.5), planner_skew_ms: Some(-3.25), stale_audio_drops: 1, stale_video_drops: 2, skew_video_drops: 3, freshness_reanchors: 4, startup_timeouts: 5, video_freezes: 6, last_reason: "healthy".to_string(), client_capture_skew_ms: Some(1.5), client_send_skew_ms: Some(-2.5), server_receive_skew_ms: Some(3.5), camera_client_queue_age_ms: Some(4.5), microphone_client_queue_age_ms: Some(5.5), camera_server_receive_age_ms: Some(6.5), microphone_server_receive_age_ms: Some(7.5), client_capture_abs_skew_p95_ms: Some(8.5), client_send_abs_skew_p95_ms: Some(9.5), server_receive_abs_skew_p95_ms: Some(10.5), camera_client_queue_age_p95_ms: Some(11.5), microphone_client_queue_age_p95_ms: Some(12.5), sink_handoff_skew_ms: Some(-13.5), sink_handoff_abs_skew_p95_ms: Some(14.5), camera_sink_late_ms: Some(15.5), microphone_sink_late_ms: Some(-16.5), camera_sink_late_p95_ms: Some(17.5), microphone_sink_late_p95_ms: Some(18.5), client_timing_window_samples: 19, sink_handoff_window_samples: 20, }); super::print_upstream_sync(UpstreamSyncState { session_id: 0, phase: "acquiring".to_string(), last_reason: "waiting".to_string(), ..UpstreamSyncState::default() }); } #[test] /// Keeps `calibration_requests_are_only_built_for_calibration_mutations` explicit because it sits on CLI orchestration, where operators need deterministic exits and artifact paths. /// Inputs are the typed parameters; output is the return value or side effect. fn calibration_requests_are_only_built_for_calibration_mutations() { let config = Config { server: "http://127.0.0.1:50051".to_string(), command: CommandKind::CalibrationAdjust, audio_delta_us: 0, video_delta_us: 71_600, note: "probe".to_string(), probe_duration_seconds: 0, probe_warmup_seconds: 0, probe_pulse_period_ms: 0, probe_pulse_width_ms: 0, probe_event_width_codes: String::new(), probe_audio_delay_us: 0, probe_video_delay_us: 0, }; let request = calibration_request_for(&config).expect("request"); assert_eq!(request.action, CalibrationAction::AdjustActive as i32); assert_eq!(request.audio_delta_us, 0); assert_eq!(request.video_delta_us, 71_600); assert_eq!(request.note, "probe"); for (command, action) in [ ( CommandKind::CalibrationRestoreDefault, CalibrationAction::RestoreDefault, ), ( CommandKind::CalibrationRestoreFactory, CalibrationAction::RestoreFactory, ), ( CommandKind::CalibrationSaveDefault, CalibrationAction::SaveActiveAsDefault, ), ] { let mutation = Config { server: config.server.clone(), command, audio_delta_us: config.audio_delta_us, video_delta_us: config.video_delta_us, note: config.note.clone(), probe_duration_seconds: config.probe_duration_seconds, probe_warmup_seconds: config.probe_warmup_seconds, probe_pulse_period_ms: config.probe_pulse_period_ms, probe_pulse_width_ms: config.probe_pulse_width_ms, probe_event_width_codes: config.probe_event_width_codes.clone(), probe_audio_delay_us: config.probe_audio_delay_us, probe_video_delay_us: config.probe_video_delay_us, }; let request = calibration_request_for(&mutation).expect("calibration mutation"); assert_eq!(request.action, action as i32); } let status = Config { command: CommandKind::CalibrationStatus, ..config }; assert!(calibration_request_for(&status).is_none()); }