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");
|
println!("[agent] session closed");
|
||||||
Ok(())
|
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