test(agent-core): v3 分发路径单测——默认后端不 panic、截断契约、空输入
新功能必须带测试,agent-core 此前无测试模块,v3 新增的 text 默认实现与 截断分支正好落在盲区里: - 后端不实现 text() 时(Linux/X11 的真实形态)必须被正常调用且不 panic; - 超 MAX_TEXT_UNITS 必须以截断后的长度到注入侧,未超限原样; - 空 Text 仍会下发到后端——被控端不假设对端守规矩,线格式输入按不可信处理。
This commit is contained in:
parent
936986a940
commit
8ffe19cd5f
@ -509,3 +509,131 @@ where
|
||||
println!("[agent] session closed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::Mutex;
|
||||
|
||||
/// 记录调用轨迹的注入后端:验证分发与截断,不碰任何真实输入。
|
||||
#[derive(Default)]
|
||||
struct MockInjector {
|
||||
calls: Mutex<Vec<String>>,
|
||||
}
|
||||
|
||||
impl MockInjector {
|
||||
fn take(&self) -> Vec<String> {
|
||||
let mut g = self.calls.lock().unwrap();
|
||||
std::mem::take(&mut g)
|
||||
}
|
||||
}
|
||||
|
||||
impl InputInjector for MockInjector {
|
||||
fn mouse_move(&self, x: u16, y: u16) {
|
||||
self.calls.lock().unwrap().push(format!("move {x},{y}"));
|
||||
}
|
||||
fn mouse_button(&self, b: MouseButton, down: bool) {
|
||||
self.calls.lock().unwrap().push(format!("btn {b:?} {down}"));
|
||||
}
|
||||
fn wheel(&self, dy: i32) {
|
||||
self.calls.lock().unwrap().push(format!("wheel {dy}"));
|
||||
}
|
||||
fn key(&self, scancode: u16, extended: bool, down: bool) {
|
||||
self.calls
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(format!("key {scancode:#x} ext={extended} {down}"));
|
||||
}
|
||||
// 刻意**不**实现 text():让默认实现进入测试面,验证「后端没做文本注入
|
||||
// 时也必须被 dispatch_input 正常调用、且不 panic」——Linux 端正是这个形态。
|
||||
}
|
||||
|
||||
/// 实现了 text() 的后端,用来验证截断后的长度真的传给了注入侧。
|
||||
struct TextCapableInjector {
|
||||
received: Mutex<Vec<usize>>,
|
||||
}
|
||||
|
||||
impl InputInjector for TextCapableInjector {
|
||||
fn mouse_move(&self, _x: u16, _y: u16) {}
|
||||
fn mouse_button(&self, _b: MouseButton, _down: bool) {}
|
||||
fn wheel(&self, _dy: i32) {}
|
||||
fn key(&self, _scancode: u16, _extended: bool, _down: bool) {}
|
||||
fn text(&self, units: &[u16]) {
|
||||
self.received.lock().unwrap().push(units.len());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispatch_routes_each_event_kind() {
|
||||
let inj = MockInjector::default();
|
||||
dispatch_input(&inj, InputEvent::MouseMove { x: 1, y: 2 });
|
||||
dispatch_input(&inj, InputEvent::MouseDown(MouseButton::Right));
|
||||
dispatch_input(&inj, InputEvent::Wheel { dy: -120 });
|
||||
dispatch_input(
|
||||
&inj,
|
||||
InputEvent::KeyDown {
|
||||
scancode: 0x48,
|
||||
extended: true,
|
||||
},
|
||||
);
|
||||
let calls = inj.take();
|
||||
assert_eq!(
|
||||
calls,
|
||||
vec![
|
||||
"move 1,2",
|
||||
"btn Right true",
|
||||
"wheel -120",
|
||||
"key 0x48 ext=true true",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// v3 关键回归:`Text` 必须被分发,且**后端没有实现 text() 时不能 panic**。
|
||||
/// 这是 Linux/X11 后端的真实形态——用户对着 Linux 被控端打字会走到这里。
|
||||
#[test]
|
||||
fn text_event_reaches_default_backend_without_panic() {
|
||||
let inj = MockInjector::default();
|
||||
dispatch_input(
|
||||
&inj,
|
||||
InputEvent::Text {
|
||||
units: vec![0x4F60, 0x597D],
|
||||
},
|
||||
);
|
||||
// 默认实现只记日志,不产生调用记录;此处断言"没炸"即为通过。
|
||||
assert!(inj.take().is_empty());
|
||||
}
|
||||
|
||||
/// 截断契约:超过 `MAX_TEXT_UNITS` 的输入必须以截断后的长度到达注入侧。
|
||||
/// 不截断会让长粘贴把远端注入队列刷爆;截断不告知则是静默丢数据。
|
||||
#[test]
|
||||
fn text_is_clamped_to_protocol_cap() {
|
||||
let inj = TextCapableInjector {
|
||||
received: Mutex::new(vec![]),
|
||||
};
|
||||
dispatch_input(
|
||||
&inj,
|
||||
InputEvent::Text {
|
||||
units: vec![0x61; common::MAX_TEXT_UNITS + 500],
|
||||
},
|
||||
);
|
||||
dispatch_input(
|
||||
&inj,
|
||||
InputEvent::Text {
|
||||
units: vec![0x62; 10],
|
||||
},
|
||||
);
|
||||
let got = inj.received.lock().unwrap().clone();
|
||||
assert_eq!(got, vec![common::MAX_TEXT_UNITS, 10], "超限截断、未超限原样");
|
||||
}
|
||||
|
||||
/// 空 Text 仍会调用后端(是否忽略由后端决定):主控侧 `send_text` 已拦掉空输入,
|
||||
/// 但被控端不能假设对端守规矩——线格式输入一律按不可信处理。
|
||||
#[test]
|
||||
fn empty_text_still_dispatches() {
|
||||
let inj = TextCapableInjector {
|
||||
received: Mutex::new(vec![]),
|
||||
};
|
||||
dispatch_input(&inj, InputEvent::Text { units: vec![] });
|
||||
assert_eq!(inj.received.lock().unwrap().clone(), vec![0]);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user