203 lines
5.6 KiB
Rust
203 lines
5.6 KiB
Rust
//! Integration coverage for the camera runtime activation contract.
|
|
//!
|
|
//! Scope: validate activation error handling and configuration equality edges
|
|
//! through public camera-runtime APIs.
|
|
//! Targets: `server/src/camera_runtime.rs`.
|
|
//! Why: camera runtime generation and guardrails are core to safe stream
|
|
//! transitions, so they need direct integration-level assertions.
|
|
|
|
use lesavka_server::camera::{CameraCodec, CameraConfig, CameraOutput, HdmiConnector, HdmiMode};
|
|
use lesavka_server::camera_runtime::{CameraRuntime, camera_cfg_eq};
|
|
use serial_test::serial;
|
|
use temp_env::with_var;
|
|
use tokio::runtime::Runtime;
|
|
use tonic::Code;
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn activate_rejects_uvc_when_disabled_and_bumps_generation() {
|
|
let runtime = CameraRuntime::new();
|
|
let cfg = CameraConfig {
|
|
output: CameraOutput::Uvc,
|
|
codec: CameraCodec::Mjpeg,
|
|
width: 1280,
|
|
height: 720,
|
|
fps: 25,
|
|
hdmi: None,
|
|
};
|
|
|
|
with_var("LESAVKA_DISABLE_UVC", Some("1"), || {
|
|
let rt = Runtime::new().expect("runtime");
|
|
let result = rt.block_on(runtime.activate(&cfg));
|
|
match result {
|
|
Ok(_) => panic!("UVC should be disabled"),
|
|
Err(err) => assert_eq!(err.code(), Code::FailedPrecondition),
|
|
}
|
|
});
|
|
|
|
assert!(runtime.is_active(1));
|
|
assert!(!runtime.is_active(2));
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn activate_tracks_latest_generation_across_repeated_failures() {
|
|
let runtime = CameraRuntime::new();
|
|
let cfg = CameraConfig {
|
|
output: CameraOutput::Uvc,
|
|
codec: CameraCodec::Mjpeg,
|
|
width: 640,
|
|
height: 360,
|
|
fps: 30,
|
|
hdmi: None,
|
|
};
|
|
|
|
with_var("LESAVKA_DISABLE_UVC", Some("1"), || {
|
|
let rt = Runtime::new().expect("runtime");
|
|
for expected in [1_u64, 2_u64, 3_u64] {
|
|
let result = rt.block_on(runtime.activate(&cfg));
|
|
match result {
|
|
Ok(_) => panic!("UVC should remain disabled"),
|
|
Err(err) => assert_eq!(err.code(), Code::FailedPrecondition),
|
|
}
|
|
assert!(runtime.is_active(expected));
|
|
assert!(!runtime.is_active(expected + 1));
|
|
}
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(coverage)]
|
|
fn activate_non_uvc_returns_noop_relay_in_coverage_harness() {
|
|
let runtime = CameraRuntime::new();
|
|
let cfg = CameraConfig {
|
|
output: CameraOutput::Hdmi,
|
|
codec: CameraCodec::H264,
|
|
width: 1920,
|
|
height: 1080,
|
|
fps: 30,
|
|
hdmi: Some(HdmiConnector {
|
|
name: String::from("HDMI-A-1"),
|
|
id: Some(1),
|
|
modes: Vec::new(),
|
|
}),
|
|
};
|
|
|
|
let rt = Runtime::new().expect("runtime");
|
|
let result = rt.block_on(runtime.activate(&cfg));
|
|
let (session_id, relay, reused) = result.expect("coverage harness should create a no-op relay");
|
|
assert_eq!(session_id, 1);
|
|
assert!(!reused);
|
|
relay.feed(lesavka_common::lesavka::VideoPacket {
|
|
id: 2,
|
|
pts: 1,
|
|
data: vec![0, 0, 0, 1, 0x65],
|
|
..Default::default()
|
|
});
|
|
|
|
assert!(runtime.is_active(1));
|
|
assert!(!runtime.is_active(2));
|
|
}
|
|
|
|
#[test]
|
|
fn camera_cfg_eq_handles_none_and_hdmi_connector_edges() {
|
|
let uvc_a = CameraConfig {
|
|
output: CameraOutput::Uvc,
|
|
codec: CameraCodec::Mjpeg,
|
|
width: 640,
|
|
height: 360,
|
|
fps: 30,
|
|
hdmi: None,
|
|
};
|
|
let uvc_b = uvc_a.clone();
|
|
assert!(camera_cfg_eq(&uvc_a, &uvc_b));
|
|
|
|
let hdmi_a = CameraConfig {
|
|
output: CameraOutput::Hdmi,
|
|
codec: CameraCodec::H264,
|
|
width: 1920,
|
|
height: 1080,
|
|
fps: 30,
|
|
hdmi: Some(HdmiConnector {
|
|
name: String::from("HDMI-A-1"),
|
|
id: Some(7),
|
|
modes: Vec::new(),
|
|
}),
|
|
};
|
|
let hdmi_b = hdmi_a.clone();
|
|
assert!(camera_cfg_eq(&hdmi_a, &hdmi_b));
|
|
|
|
let mut different_id = hdmi_a.clone();
|
|
different_id.hdmi = Some(HdmiConnector {
|
|
name: String::from("HDMI-A-1"),
|
|
id: Some(8),
|
|
modes: Vec::new(),
|
|
});
|
|
assert!(!camera_cfg_eq(&hdmi_a, &different_id));
|
|
|
|
let mut missing_connector = hdmi_a;
|
|
missing_connector.hdmi = None;
|
|
assert!(!camera_cfg_eq(&missing_connector, &hdmi_b));
|
|
}
|
|
|
|
#[test]
|
|
fn camera_cfg_eq_rejects_hdmi_display_mode_changes() {
|
|
let base = CameraConfig {
|
|
output: CameraOutput::Hdmi,
|
|
codec: CameraCodec::H264,
|
|
width: 1280,
|
|
height: 720,
|
|
fps: 30,
|
|
hdmi: Some(HdmiConnector {
|
|
name: String::from("HDMI-A-2"),
|
|
id: Some(43),
|
|
modes: vec![HdmiMode {
|
|
width: 1920,
|
|
height: 1080,
|
|
}],
|
|
}),
|
|
};
|
|
let mut changed = base.clone();
|
|
changed.hdmi = Some(HdmiConnector {
|
|
name: String::from("HDMI-A-2"),
|
|
id: Some(43),
|
|
modes: vec![HdmiMode {
|
|
width: 1024,
|
|
height: 768,
|
|
}],
|
|
});
|
|
|
|
assert!(!camera_cfg_eq(&base, &changed));
|
|
}
|
|
|
|
#[test]
|
|
fn camera_cfg_eq_rejects_output_codec_resolution_and_fps_changes() {
|
|
let base = CameraConfig {
|
|
output: CameraOutput::Uvc,
|
|
codec: CameraCodec::Mjpeg,
|
|
width: 1280,
|
|
height: 720,
|
|
fps: 25,
|
|
hdmi: None,
|
|
};
|
|
let mut changed = base.clone();
|
|
changed.output = CameraOutput::Hdmi;
|
|
assert!(!camera_cfg_eq(&base, &changed));
|
|
|
|
changed = base.clone();
|
|
changed.codec = CameraCodec::H264;
|
|
assert!(!camera_cfg_eq(&base, &changed));
|
|
|
|
changed = base.clone();
|
|
changed.width = 1920;
|
|
assert!(!camera_cfg_eq(&base, &changed));
|
|
|
|
changed = base.clone();
|
|
changed.height = 1080;
|
|
assert!(!camera_cfg_eq(&base, &changed));
|
|
|
|
changed = base.clone();
|
|
changed.fps = 30;
|
|
assert!(!camera_cfg_eq(&base, &changed));
|
|
}
|