2026-04-13 02:52:32 -03:00
|
|
|
//! Integration coverage for server main RPC handler branches.
|
|
|
|
|
//!
|
|
|
|
|
//! Scope: include `server/src/main.rs` and exercise additional RPC paths that
|
|
|
|
|
//! are awkward to hit from process-level tests.
|
|
|
|
|
//! Targets: `server/src/main.rs`.
|
|
|
|
|
//! Why: keep handler-side error/reply behavior stable without HID hardware.
|
|
|
|
|
|
|
|
|
|
#[allow(warnings)]
|
|
|
|
|
mod server_main_rpc {
|
|
|
|
|
include!(env!("LESAVKA_SERVER_MAIN_SRC"));
|
|
|
|
|
|
|
|
|
|
use serial_test::serial;
|
|
|
|
|
use temp_env::with_var;
|
|
|
|
|
use tempfile::tempdir;
|
|
|
|
|
|
2026-04-14 23:35:29 -03:00
|
|
|
fn build_handler_for_tests_with_modes(
|
|
|
|
|
kb_writable: bool,
|
|
|
|
|
ms_writable: bool,
|
|
|
|
|
) -> (tempfile::TempDir, Handler) {
|
2026-04-13 02:52:32 -03:00
|
|
|
let dir = tempdir().expect("tempdir");
|
|
|
|
|
let kb_path = dir.path().join("hidg0.bin");
|
|
|
|
|
let ms_path = dir.path().join("hidg1.bin");
|
|
|
|
|
std::fs::write(&kb_path, []).expect("create kb file");
|
|
|
|
|
std::fs::write(&ms_path, []).expect("create ms file");
|
|
|
|
|
|
|
|
|
|
let kb = tokio::fs::File::from_std(
|
|
|
|
|
std::fs::OpenOptions::new()
|
|
|
|
|
.read(true)
|
2026-04-14 23:35:29 -03:00
|
|
|
.write(kb_writable)
|
|
|
|
|
.create(kb_writable)
|
|
|
|
|
.truncate(kb_writable)
|
2026-04-13 02:52:32 -03:00
|
|
|
.open(&kb_path)
|
|
|
|
|
.expect("open kb"),
|
|
|
|
|
);
|
|
|
|
|
let ms = tokio::fs::File::from_std(
|
|
|
|
|
std::fs::OpenOptions::new()
|
|
|
|
|
.read(true)
|
2026-04-14 23:35:29 -03:00
|
|
|
.write(ms_writable)
|
|
|
|
|
.create(ms_writable)
|
|
|
|
|
.truncate(ms_writable)
|
2026-04-13 02:52:32 -03:00
|
|
|
.open(&ms_path)
|
|
|
|
|
.expect("open ms"),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
(
|
|
|
|
|
dir,
|
|
|
|
|
Handler {
|
2026-04-21 13:08:20 -03:00
|
|
|
kb: std::sync::Arc::new(tokio::sync::Mutex::new(Some(kb))),
|
|
|
|
|
ms: std::sync::Arc::new(tokio::sync::Mutex::new(Some(ms))),
|
2026-04-13 02:52:32 -03:00
|
|
|
gadget: UsbGadget::new("lesavka"),
|
|
|
|
|
did_cycle: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
|
|
|
|
|
camera_rt: std::sync::Arc::new(CameraRuntime::new()),
|
2026-04-14 23:03:18 -03:00
|
|
|
capture_power: CapturePowerManager::new(),
|
2026-04-19 04:24:27 -03:00
|
|
|
eye_hubs: std::sync::Arc::new(tokio::sync::Mutex::new(
|
|
|
|
|
std::collections::HashMap::new(),
|
|
|
|
|
)),
|
2026-04-13 02:52:32 -03:00
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 23:35:29 -03:00
|
|
|
fn build_handler_for_tests() -> (tempfile::TempDir, Handler) {
|
|
|
|
|
build_handler_for_tests_with_modes(true, true)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 02:52:32 -03:00
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn reopen_hid_returns_error_without_hid_endpoints() {
|
|
|
|
|
let (_dir, handler) = build_handler_for_tests();
|
|
|
|
|
let rt = tokio::runtime::Runtime::new().expect("runtime");
|
|
|
|
|
let result = rt.block_on(handler.reopen_hid());
|
|
|
|
|
assert!(result.is_err(), "reopen_hid should fail without /dev/hidg*");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn capture_video_valid_monitor_surfaces_internal_error_without_device() {
|
|
|
|
|
let (_dir, handler) = build_handler_for_tests();
|
|
|
|
|
let rt = tokio::runtime::Runtime::new().expect("runtime");
|
|
|
|
|
let result = rt.block_on(async {
|
|
|
|
|
handler
|
|
|
|
|
.capture_video(tonic::Request::new(MonitorRequest {
|
|
|
|
|
id: 0,
|
|
|
|
|
max_bitrate: 3_000,
|
2026-04-16 12:58:05 -03:00
|
|
|
requested_width: 0,
|
|
|
|
|
requested_height: 0,
|
|
|
|
|
requested_fps: 0,
|
2026-04-19 03:28:23 -03:00
|
|
|
source_id: None,
|
2026-04-13 02:52:32 -03:00
|
|
|
}))
|
|
|
|
|
.await
|
|
|
|
|
});
|
|
|
|
|
let err = match result {
|
|
|
|
|
Ok(_) => panic!("missing camera device should fail"),
|
|
|
|
|
Err(err) => err,
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(err.code(), tonic::Code::Internal);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 23:35:29 -03:00
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn capture_video_right_eye_surfaces_internal_error_without_device() {
|
|
|
|
|
let (_dir, handler) = build_handler_for_tests();
|
|
|
|
|
let rt = tokio::runtime::Runtime::new().expect("runtime");
|
|
|
|
|
let result = rt.block_on(async {
|
|
|
|
|
handler
|
|
|
|
|
.capture_video(tonic::Request::new(MonitorRequest {
|
|
|
|
|
id: 1,
|
|
|
|
|
max_bitrate: 3_000,
|
2026-04-16 12:58:05 -03:00
|
|
|
requested_width: 0,
|
|
|
|
|
requested_height: 0,
|
|
|
|
|
requested_fps: 0,
|
2026-04-19 03:28:23 -03:00
|
|
|
source_id: None,
|
2026-04-14 23:35:29 -03:00
|
|
|
}))
|
|
|
|
|
.await
|
|
|
|
|
});
|
|
|
|
|
let err = match result {
|
|
|
|
|
Ok(_) => panic!("missing right-eye camera device should fail"),
|
|
|
|
|
Err(err) => err,
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(err.code(), tonic::Code::Internal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[cfg(coverage)]
|
|
|
|
|
#[serial]
|
2026-04-19 04:24:27 -03:00
|
|
|
fn capture_video_returns_stream_when_coverage_source_is_overridden() {
|
2026-04-14 23:35:29 -03:00
|
|
|
let (_dir, handler) = build_handler_for_tests();
|
|
|
|
|
let rt = tokio::runtime::Runtime::new().expect("runtime");
|
2026-04-15 01:20:51 -03:00
|
|
|
with_var(
|
|
|
|
|
"LESAVKA_TEST_VIDEO_SOURCE",
|
|
|
|
|
Some("/dev/lesavka_l_eye"),
|
|
|
|
|
|| {
|
|
|
|
|
let mut stream = rt
|
|
|
|
|
.block_on(async {
|
|
|
|
|
handler
|
|
|
|
|
.capture_video(tonic::Request::new(MonitorRequest {
|
|
|
|
|
id: 0,
|
|
|
|
|
max_bitrate: 3_000,
|
2026-04-16 12:58:05 -03:00
|
|
|
requested_width: 0,
|
|
|
|
|
requested_height: 0,
|
|
|
|
|
requested_fps: 0,
|
2026-04-19 03:28:23 -03:00
|
|
|
source_id: None,
|
2026-04-15 01:20:51 -03:00
|
|
|
}))
|
|
|
|
|
.await
|
|
|
|
|
})
|
|
|
|
|
.expect("coverage video stream should succeed")
|
|
|
|
|
.into_inner();
|
|
|
|
|
let packet = rt
|
|
|
|
|
.block_on(async { stream.next().await })
|
|
|
|
|
.expect("stream item")
|
|
|
|
|
.expect("packet");
|
|
|
|
|
assert_eq!(packet.id, 0);
|
|
|
|
|
assert!(!packet.data.is_empty());
|
|
|
|
|
},
|
|
|
|
|
);
|
2026-04-14 23:35:29 -03:00
|
|
|
}
|
|
|
|
|
|
2026-04-19 04:24:27 -03:00
|
|
|
#[test]
|
|
|
|
|
#[cfg(coverage)]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn mirrored_capture_requests_share_one_source_hub_but_keep_logical_ids() {
|
|
|
|
|
let (_dir, handler) = build_handler_for_tests();
|
|
|
|
|
let rt = tokio::runtime::Runtime::new().expect("runtime");
|
|
|
|
|
with_var(
|
|
|
|
|
"LESAVKA_TEST_VIDEO_SOURCE",
|
|
|
|
|
Some("/dev/lesavka_r_eye"),
|
|
|
|
|
|| {
|
|
|
|
|
let (left_packet, right_packet, hub_count) = rt.block_on(async {
|
|
|
|
|
let mut left = handler
|
|
|
|
|
.capture_video(tonic::Request::new(MonitorRequest {
|
|
|
|
|
id: 0,
|
|
|
|
|
max_bitrate: 3_000,
|
|
|
|
|
requested_width: 1920,
|
|
|
|
|
requested_height: 1080,
|
|
|
|
|
requested_fps: 60,
|
|
|
|
|
source_id: Some(1),
|
|
|
|
|
}))
|
|
|
|
|
.await
|
|
|
|
|
.expect("left stream")
|
|
|
|
|
.into_inner();
|
|
|
|
|
let mut right = handler
|
|
|
|
|
.capture_video(tonic::Request::new(MonitorRequest {
|
|
|
|
|
id: 1,
|
|
|
|
|
max_bitrate: 3_000,
|
|
|
|
|
requested_width: 1920,
|
|
|
|
|
requested_height: 1080,
|
|
|
|
|
requested_fps: 60,
|
|
|
|
|
source_id: Some(1),
|
|
|
|
|
}))
|
|
|
|
|
.await
|
|
|
|
|
.expect("right stream")
|
|
|
|
|
.into_inner();
|
|
|
|
|
let left_packet = left.next().await.expect("left item").expect("left packet");
|
|
|
|
|
let right_packet = right
|
|
|
|
|
.next()
|
|
|
|
|
.await
|
|
|
|
|
.expect("right item")
|
|
|
|
|
.expect("right packet");
|
|
|
|
|
let hub_count = handler.eye_hub_count().await;
|
|
|
|
|
(left_packet, right_packet, hub_count)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
assert_eq!(left_packet.id, 0);
|
|
|
|
|
assert_eq!(right_packet.id, 1);
|
|
|
|
|
assert!(!left_packet.data.is_empty());
|
|
|
|
|
assert!(!right_packet.data.is_empty());
|
|
|
|
|
assert_eq!(hub_count, 1);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 02:52:32 -03:00
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn paste_text_accepts_encrypted_payload_and_returns_reply() {
|
|
|
|
|
let (_dir, handler) = build_handler_for_tests();
|
|
|
|
|
with_var(
|
|
|
|
|
"LESAVKA_PASTE_KEY",
|
|
|
|
|
Some("hex:00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"),
|
|
|
|
|
|| {
|
|
|
|
|
with_var("LESAVKA_PASTE_DELAY_MS", Some("0"), || {
|
|
|
|
|
let req =
|
|
|
|
|
lesavka_client::paste::build_paste_request("hello").expect("build request");
|
|
|
|
|
let rt = tokio::runtime::Runtime::new().expect("runtime");
|
|
|
|
|
let reply = rt
|
|
|
|
|
.block_on(async { handler.paste_text(tonic::Request::new(req)).await })
|
|
|
|
|
.expect("paste rpc should return reply")
|
|
|
|
|
.into_inner();
|
|
|
|
|
assert!(
|
|
|
|
|
reply.ok || !reply.error.is_empty(),
|
|
|
|
|
"paste path should execute and return a structured reply"
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 23:35:29 -03:00
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn paste_text_returns_structured_error_when_hid_write_fails() {
|
|
|
|
|
let (_dir, handler) = build_handler_for_tests_with_modes(false, true);
|
|
|
|
|
with_var(
|
|
|
|
|
"LESAVKA_PASTE_KEY",
|
|
|
|
|
Some("hex:00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"),
|
|
|
|
|
|| {
|
|
|
|
|
with_var("LESAVKA_PASTE_DELAY_MS", Some("0"), || {
|
|
|
|
|
let req =
|
|
|
|
|
lesavka_client::paste::build_paste_request("hello").expect("build request");
|
|
|
|
|
let rt = tokio::runtime::Runtime::new().expect("runtime");
|
|
|
|
|
let reply = rt
|
|
|
|
|
.block_on(async { handler.paste_text(tonic::Request::new(req)).await })
|
|
|
|
|
.expect("paste rpc should return structured reply")
|
|
|
|
|
.into_inner();
|
|
|
|
|
assert!(!reply.ok);
|
|
|
|
|
assert!(!reply.error.is_empty());
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 02:52:32 -03:00
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn capture_audio_accepts_secondary_monitor_id_and_fails_internally_without_sink() {
|
|
|
|
|
let (_dir, handler) = build_handler_for_tests();
|
|
|
|
|
let req = MonitorRequest {
|
|
|
|
|
id: 1,
|
|
|
|
|
max_bitrate: 0,
|
2026-04-16 12:58:05 -03:00
|
|
|
requested_width: 0,
|
|
|
|
|
requested_height: 0,
|
|
|
|
|
requested_fps: 0,
|
2026-04-19 03:28:23 -03:00
|
|
|
source_id: None,
|
2026-04-13 02:52:32 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let rt = tokio::runtime::Runtime::new().expect("runtime");
|
|
|
|
|
let result = rt.block_on(async { handler.capture_audio(tonic::Request::new(req)).await });
|
|
|
|
|
let err = match result {
|
|
|
|
|
Ok(_) => panic!("missing ALSA source should fail"),
|
|
|
|
|
Err(err) => err,
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(err.code(), tonic::Code::Internal);
|
|
|
|
|
}
|
2026-04-14 23:35:29 -03:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[cfg(coverage)]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn capture_power_rpcs_surface_stub_snapshot_and_manual_modes() {
|
|
|
|
|
let (_dir, handler) = build_handler_for_tests();
|
|
|
|
|
let rt = tokio::runtime::Runtime::new().expect("runtime");
|
|
|
|
|
|
|
|
|
|
let snapshot = rt
|
2026-04-15 01:20:51 -03:00
|
|
|
.block_on(async {
|
|
|
|
|
handler
|
|
|
|
|
.get_capture_power(tonic::Request::new(Empty {}))
|
|
|
|
|
.await
|
|
|
|
|
})
|
2026-04-14 23:35:29 -03:00
|
|
|
.expect("capture power snapshot")
|
|
|
|
|
.into_inner();
|
|
|
|
|
assert!(snapshot.available);
|
|
|
|
|
assert!(!snapshot.enabled);
|
|
|
|
|
assert_eq!(snapshot.mode, "auto");
|
|
|
|
|
|
|
|
|
|
let forced_on = rt
|
|
|
|
|
.block_on(async {
|
|
|
|
|
handler
|
|
|
|
|
.set_capture_power(tonic::Request::new(SetCapturePowerRequest {
|
|
|
|
|
enabled: true,
|
2026-04-15 01:20:51 -03:00
|
|
|
command: CapturePowerCommand::ForceOn as i32,
|
2026-04-14 23:35:29 -03:00
|
|
|
}))
|
|
|
|
|
.await
|
|
|
|
|
})
|
|
|
|
|
.expect("force capture power on")
|
|
|
|
|
.into_inner();
|
|
|
|
|
assert!(forced_on.available);
|
|
|
|
|
assert!(forced_on.enabled);
|
|
|
|
|
assert_eq!(forced_on.mode, "forced-on");
|
|
|
|
|
|
|
|
|
|
let forced_off = rt
|
|
|
|
|
.block_on(async {
|
|
|
|
|
handler
|
|
|
|
|
.set_capture_power(tonic::Request::new(SetCapturePowerRequest {
|
|
|
|
|
enabled: false,
|
2026-04-15 01:20:51 -03:00
|
|
|
command: CapturePowerCommand::ForceOff as i32,
|
2026-04-14 23:35:29 -03:00
|
|
|
}))
|
|
|
|
|
.await
|
|
|
|
|
})
|
|
|
|
|
.expect("force capture power off")
|
|
|
|
|
.into_inner();
|
|
|
|
|
assert!(forced_off.available);
|
|
|
|
|
assert!(!forced_off.enabled);
|
|
|
|
|
assert_eq!(forced_off.mode, "forced-off");
|
2026-04-15 01:20:51 -03:00
|
|
|
|
|
|
|
|
let auto = rt
|
|
|
|
|
.block_on(async {
|
|
|
|
|
handler
|
|
|
|
|
.set_capture_power(tonic::Request::new(SetCapturePowerRequest {
|
|
|
|
|
enabled: false,
|
|
|
|
|
command: CapturePowerCommand::Auto as i32,
|
|
|
|
|
}))
|
|
|
|
|
.await
|
|
|
|
|
})
|
|
|
|
|
.expect("return capture power to auto")
|
|
|
|
|
.into_inner();
|
|
|
|
|
assert!(auto.available);
|
|
|
|
|
assert!(!auto.enabled);
|
|
|
|
|
assert_eq!(auto.mode, "auto");
|
|
|
|
|
|
|
|
|
|
let legacy_fallback = rt
|
|
|
|
|
.block_on(async {
|
|
|
|
|
handler
|
|
|
|
|
.set_capture_power(tonic::Request::new(SetCapturePowerRequest {
|
|
|
|
|
enabled: true,
|
|
|
|
|
command: CapturePowerCommand::Unspecified as i32,
|
|
|
|
|
}))
|
|
|
|
|
.await
|
|
|
|
|
})
|
|
|
|
|
.expect("legacy bool fallback")
|
|
|
|
|
.into_inner();
|
|
|
|
|
assert!(legacy_fallback.available);
|
|
|
|
|
assert!(legacy_fallback.enabled);
|
|
|
|
|
assert_eq!(legacy_fallback.mode, "forced-on");
|
2026-04-14 23:35:29 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[cfg(coverage)]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn reset_usb_returns_internal_error_when_reopen_fails_after_successful_cycle() {
|
|
|
|
|
let dir = tempdir().expect("tempdir");
|
|
|
|
|
std::fs::write(dir.path().join("hidg0.bin"), "").expect("create kb file");
|
|
|
|
|
std::fs::write(dir.path().join("hidg1.bin"), "").expect("create ms file");
|
|
|
|
|
std::fs::create_dir_all(dir.path().join("sys/class/udc/fake-ctrl.usb"))
|
|
|
|
|
.expect("create udc dir");
|
|
|
|
|
std::fs::create_dir_all(dir.path().join("cfg/lesavka")).expect("create cfg dir");
|
|
|
|
|
std::fs::write(
|
|
|
|
|
dir.path().join("sys/class/udc/fake-ctrl.usb/state"),
|
|
|
|
|
"configured\n",
|
|
|
|
|
)
|
|
|
|
|
.expect("write state");
|
2026-04-15 01:20:51 -03:00
|
|
|
std::fs::write(dir.path().join("cfg/lesavka/UDC"), "fake-ctrl.usb\n").expect("write udc");
|
2026-04-14 23:35:29 -03:00
|
|
|
|
|
|
|
|
let kb = tokio::fs::File::from_std(
|
|
|
|
|
std::fs::OpenOptions::new()
|
|
|
|
|
.read(true)
|
|
|
|
|
.write(true)
|
|
|
|
|
.open(dir.path().join("hidg0.bin"))
|
|
|
|
|
.expect("open kb"),
|
|
|
|
|
);
|
|
|
|
|
let ms = tokio::fs::File::from_std(
|
|
|
|
|
std::fs::OpenOptions::new()
|
|
|
|
|
.read(true)
|
|
|
|
|
.write(true)
|
|
|
|
|
.open(dir.path().join("hidg1.bin"))
|
|
|
|
|
.expect("open ms"),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
with_var(
|
|
|
|
|
"LESAVKA_GADGET_SYSFS_ROOT",
|
|
|
|
|
Some(dir.path().join("sys").to_string_lossy().to_string()),
|
|
|
|
|
|| {
|
|
|
|
|
with_var(
|
|
|
|
|
"LESAVKA_GADGET_CONFIGFS_ROOT",
|
|
|
|
|
Some(dir.path().join("cfg").to_string_lossy().to_string()),
|
|
|
|
|
|| {
|
|
|
|
|
let handler = Handler {
|
2026-04-21 13:08:20 -03:00
|
|
|
kb: std::sync::Arc::new(tokio::sync::Mutex::new(Some(kb))),
|
|
|
|
|
ms: std::sync::Arc::new(tokio::sync::Mutex::new(Some(ms))),
|
2026-04-14 23:35:29 -03:00
|
|
|
gadget: UsbGadget::new("lesavka"),
|
2026-04-15 01:20:51 -03:00
|
|
|
did_cycle: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(
|
|
|
|
|
false,
|
|
|
|
|
)),
|
2026-04-14 23:35:29 -03:00
|
|
|
camera_rt: std::sync::Arc::new(CameraRuntime::new()),
|
|
|
|
|
capture_power: CapturePowerManager::new(),
|
2026-04-19 04:24:27 -03:00
|
|
|
eye_hubs: std::sync::Arc::new(tokio::sync::Mutex::new(
|
|
|
|
|
std::collections::HashMap::new(),
|
|
|
|
|
)),
|
2026-04-14 23:35:29 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
with_var(
|
|
|
|
|
"LESAVKA_HID_DIR",
|
|
|
|
|
Some(dir.path().join("missing").to_string_lossy().to_string()),
|
|
|
|
|
|| {
|
|
|
|
|
let rt = tokio::runtime::Runtime::new().expect("runtime");
|
|
|
|
|
let err = rt
|
|
|
|
|
.block_on(async {
|
|
|
|
|
handler.reset_usb(tonic::Request::new(Empty {})).await
|
|
|
|
|
})
|
|
|
|
|
.expect_err("reopen hid should fail after successful cycle");
|
|
|
|
|
assert_eq!(err.code(), tonic::Code::Internal);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-04-13 02:52:32 -03:00
|
|
|
}
|