lesavka/testing/tests/handshake_camera_contract.rs

127 lines
5.1 KiB
Rust
Raw Normal View History

2026-04-11 03:36:42 -03:00
//! Integration coverage for the handshake and camera-selection contract.
//!
//! Scope: exercise the real client handshake against a local server and the
//! server camera selection logic that feeds the handshake response.
//! Targets: `client/src/handshake.rs`, `server/src/handshake.rs`, and
//! `server/src/camera.rs`.
//! Why: the handshake path is the narrow entrypoint that decides whether the
//! client and server agree on output mode, so it deserves a centralized
//! contract test.
use lesavka_client::handshake::{PeerCaps, negotiate};
use serial_test::serial;
use std::net::TcpListener;
use std::time::Duration;
use temp_env::with_var;
use tokio::runtime::Runtime;
use tokio::sync::oneshot;
use tonic::transport::Server;
async fn negotiate_against_local_server() -> PeerCaps {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind local handshake listener");
let addr = listener.local_addr().expect("listener addr");
drop(listener);
let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
let server = tokio::spawn(async move {
Server::builder()
.add_service(lesavka_server::handshake::HandshakeSvc::server())
.serve_with_shutdown(addr, async move {
let _ = shutdown_rx.await;
})
.await
.expect("serve handshake server");
});
tokio::time::sleep(Duration::from_millis(50)).await;
let caps = negotiate(&format!("http://{addr}")).await;
let _ = shutdown_tx.send(());
let _ = server.await;
caps
}
#[test]
#[serial]
fn handshake_returns_uvc_caps_with_explicit_dimensions_and_fps() {
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 rt = Runtime::new().expect("create runtime");
let caps = rt.block_on(negotiate_against_local_server());
assert!(caps.camera);
assert!(caps.microphone);
assert_eq!(caps.camera_output, Some(String::from("uvc")));
assert_eq!(caps.camera_codec, Some(String::from("mjpeg")));
assert_eq!(caps.camera_width, Some(1024));
assert_eq!(caps.camera_height, Some(576));
assert_eq!(caps.camera_fps, Some(12));
});
});
});
});
}
#[test]
#[serial]
fn handshake_uses_uvc_interval_when_fps_is_unset() {
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_INTERVAL", Some("250000"), || {
let rt = Runtime::new().expect("create runtime");
let caps = rt.block_on(negotiate_against_local_server());
assert_eq!(caps.camera_output, Some(String::from("uvc")));
assert_eq!(caps.camera_codec, Some(String::from("mjpeg")));
assert_eq!(caps.camera_width, Some(640));
assert_eq!(caps.camera_height, Some(360));
assert_eq!(caps.camera_fps, Some(40));
assert!(caps.camera);
assert!(caps.microphone);
});
});
});
});
}
#[test]
#[serial]
fn handshake_returns_hdmi_caps_with_h264_codec() {
with_var("LESAVKA_CAM_OUTPUT", Some("hdmi"), || {
with_var("LESAVKA_DISABLE_UAC", Some("1"), || {
let rt = Runtime::new().expect("create runtime");
let caps = rt.block_on(negotiate_against_local_server());
assert_eq!(caps.camera_output, Some(String::from("hdmi")));
assert_eq!(caps.camera_codec, Some(String::from("h264")));
assert_eq!(caps.camera_fps, Some(30));
assert!(!caps.microphone);
assert!(caps.camera);
assert!(matches!(caps.camera_width, Some(1280) | Some(1920)));
assert!(matches!(caps.camera_height, Some(720) | Some(1080)));
});
});
}
#[test]
#[serial]
fn handshake_auto_mode_falls_back_to_a_valid_camera_configuration() {
with_var("LESAVKA_CAM_OUTPUT", Some("auto"), || {
let rt = Runtime::new().expect("create runtime");
let caps = rt.block_on(negotiate_against_local_server());
match caps.camera_output.as_deref() {
Some("uvc") => {
assert_eq!(caps.camera_codec.as_deref(), Some("mjpeg"));
assert!(matches!(caps.camera_width, Some(1280) | Some(640)));
assert!(matches!(caps.camera_height, Some(720) | Some(360)));
}
Some("hdmi") => {
assert_eq!(caps.camera_codec.as_deref(), Some("h264"));
assert!(matches!(caps.camera_width, Some(1280) | Some(1920)));
assert!(matches!(caps.camera_height, Some(720) | Some(1080)));
}
other => panic!("unexpected camera output: {other:?}"),
}
assert!(caps.camera);
});
}