lesavka/client/src/launcher/tests/utility_actions.rs

196 lines
6.4 KiB
Rust

use super::super::{clipboard::send_clipboard_text_to_remote, power::reset_usb_gadget};
use futures::stream;
use lesavka_common::lesavka::{
AudioPacket, CalibrationRequest, CalibrationState, CapturePowerState, Empty, KeyboardReport,
MonitorRequest, MouseReport, PasteReply, PasteRequest, ResetUsbReply, SetCapturePowerRequest,
VideoPacket,
relay_server::{Relay, RelayServer},
};
use serial_test::serial;
use std::{
pin::Pin,
sync::{Arc, Mutex},
time::Duration,
};
use tonic::{Request, Response, Status};
type KeyboardStream = Pin<Box<dyn futures::Stream<Item = Result<KeyboardReport, Status>> + Send>>;
type MouseStream = Pin<Box<dyn futures::Stream<Item = Result<MouseReport, Status>> + Send>>;
type VideoStream = Pin<Box<dyn futures::Stream<Item = Result<VideoPacket, Status>> + Send>>;
type AudioStream = Pin<Box<dyn futures::Stream<Item = Result<AudioPacket, Status>> + Send>>;
type EmptyStream = Pin<Box<dyn futures::Stream<Item = Result<Empty, Status>> + Send>>;
#[derive(Clone)]
struct UtilityRelay {
paste_count: Arc<Mutex<usize>>,
reset_count: Arc<Mutex<usize>>,
reset_ok: bool,
}
impl UtilityRelay {
fn new(reset_ok: bool) -> Self {
Self {
paste_count: Arc::new(Mutex::new(0)),
reset_count: Arc::new(Mutex::new(0)),
reset_ok,
}
}
}
#[tonic::async_trait]
impl Relay for UtilityRelay {
type StreamKeyboardStream = KeyboardStream;
type StreamMouseStream = MouseStream;
type CaptureVideoStream = VideoStream;
type CaptureAudioStream = AudioStream;
type StreamMicrophoneStream = EmptyStream;
type StreamCameraStream = EmptyStream;
async fn stream_keyboard(
&self,
_request: Request<tonic::Streaming<KeyboardReport>>,
) -> Result<Response<Self::StreamKeyboardStream>, Status> {
Ok(Response::new(Box::pin(stream::empty())))
}
async fn stream_mouse(
&self,
_request: Request<tonic::Streaming<MouseReport>>,
) -> Result<Response<Self::StreamMouseStream>, Status> {
Ok(Response::new(Box::pin(stream::empty())))
}
async fn capture_video(
&self,
_request: Request<MonitorRequest>,
) -> Result<Response<Self::CaptureVideoStream>, Status> {
Ok(Response::new(Box::pin(stream::empty())))
}
async fn capture_audio(
&self,
_request: Request<MonitorRequest>,
) -> Result<Response<Self::CaptureAudioStream>, Status> {
Ok(Response::new(Box::pin(stream::empty())))
}
async fn stream_microphone(
&self,
_request: Request<tonic::Streaming<AudioPacket>>,
) -> Result<Response<Self::StreamMicrophoneStream>, Status> {
Ok(Response::new(Box::pin(stream::empty())))
}
async fn stream_camera(
&self,
_request: Request<tonic::Streaming<VideoPacket>>,
) -> Result<Response<Self::StreamCameraStream>, Status> {
Ok(Response::new(Box::pin(stream::empty())))
}
async fn paste_text(
&self,
_request: Request<PasteRequest>,
) -> Result<Response<PasteReply>, Status> {
*self.paste_count.lock().expect("paste count") += 1;
Ok(Response::new(PasteReply {
ok: true,
error: String::new(),
}))
}
async fn reset_usb(&self, _request: Request<Empty>) -> Result<Response<ResetUsbReply>, Status> {
*self.reset_count.lock().expect("reset count") += 1;
Ok(Response::new(ResetUsbReply { ok: self.reset_ok }))
}
async fn get_capture_power(
&self,
_request: Request<Empty>,
) -> Result<Response<CapturePowerState>, Status> {
Ok(Response::new(CapturePowerState::default()))
}
async fn set_capture_power(
&self,
_request: Request<SetCapturePowerRequest>,
) -> Result<Response<CapturePowerState>, Status> {
Ok(Response::new(CapturePowerState::default()))
}
async fn get_calibration(
&self,
_request: Request<Empty>,
) -> Result<Response<CalibrationState>, Status> {
Ok(Response::new(CalibrationState::default()))
}
async fn calibrate(
&self,
_request: Request<CalibrationRequest>,
) -> Result<Response<CalibrationState>, Status> {
Ok(Response::new(CalibrationState::default()))
}
}
fn serve(relay: UtilityRelay) -> (tokio::runtime::Runtime, String) {
let rt = tokio::runtime::Runtime::new().expect("runtime");
let addr = rt.block_on(async {
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind relay");
let addr = listener.local_addr().expect("relay addr");
drop(listener);
tokio::spawn(async move {
let _ = tonic::transport::Server::builder()
.add_service(RelayServer::new(relay))
.serve(addr)
.await;
});
addr
});
std::thread::sleep(Duration::from_millis(50));
(rt, format!("http://{addr}"))
}
#[test]
#[serial]
fn clipboard_action_delivers_text_over_rpc_when_key_is_available() {
temp_env::with_vars(
[
(
"LESAVKA_PASTE_KEY",
Some("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"),
),
("LESAVKA_PASTE_KEY_FILE", None::<&str>),
("LESAVKA_CLIPBOARD_TIMEOUT_MS", Some("1500")),
],
|| {
let relay = UtilityRelay::new(true);
let paste_count = Arc::clone(&relay.paste_count);
let (_rt, addr) = serve(relay);
let result =
send_clipboard_text_to_remote(&addr, "hello from launcher").expect("clipboard RPC");
assert_eq!(result, "Clipboard delivered to remote");
assert_eq!(*paste_count.lock().expect("paste count"), 1);
},
);
}
#[test]
#[serial]
fn recover_usb_action_reports_relay_success_and_failure() {
let relay = UtilityRelay::new(true);
let reset_count = Arc::clone(&relay.reset_count);
let (_rt, addr) = serve(relay);
reset_usb_gadget(&addr).expect("successful reset");
assert_eq!(*reset_count.lock().expect("reset count"), 1);
let relay = UtilityRelay::new(false);
let reset_count = Arc::clone(&relay.reset_count);
let (_rt, addr) = serve(relay);
let err = reset_usb_gadget(&addr).expect_err("reset failure");
assert!(format!("{err:#}").contains("relay reported USB reset failure"));
assert_eq!(*reset_count.lock().expect("reset count"), 1);
}