Files
shell-rs/tests/ui/support/connection.rs
XuzandClaude Opus 5.5 cd489a5fc2 设置 › 终端新增「本地终端」:指定 Shell;Windows 默认改用 PowerShell
本地终端运行的程序可以写路径或命令,带参数;输入框右端的下拉列出装了的常用 shell
(zsh、bash、sh、fish、nu、pwsh,从 /etc/shells、PATH 和 Homebrew 目录找),旁边
「选择文件…」用系统的文件选择框。留空是自动检测,新开和重新启动的本地终端生效。
只写一个 shell 时和默认一样按登录 shell 启动,带参数或不是 shell 的照写执行。

Windows 的默认 shell 原来是 cmd.exe,现在先用 PowerShell 7,其次 Windows PowerShell,
都没有才用 cmd.exe。

工作区改为每次打开、重启本地终端时向 LocalTerminalTransportProvider 要 factory,
UI 测试注入的 FixedLocalTerminalTransportProvider 记下每次要的 shell。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:44:25 +08:00

146 lines
4.5 KiB
Rust

//! A fake 测试连接 that records each login it is given.
use super::*;
/// Same, with the host dialog's connection test answered by `tester`.
///
/// Motion is reduced, as in the other fixtures: dialogs would otherwise
/// slide in over real time, and under a loaded test run a field or button
/// can move between being found and being clicked.
pub fn open_workspace_with_tester(
cx: &mut TestAppContext,
store: HostStore,
tester: Arc<FakeConnectionTester>,
) -> (WindowHandle<Root>, Entity<Workspace>) {
init_app(cx);
cx.update(|cx| cx.set_reduce_motion(true));
let mut workspace = None;
let handle = cx.open_window(size(px(1280.), px(800.)), |window, cx| {
let store = cx.new(|_| store);
let remote = Arc::new(FixedRemoteTerminalTransportProvider::new(Arc::new(
FakeTerminalFactory::default(),
)));
let view = cx.new(|cx| {
Workspace::new_with_services(
store,
cx.new(|_| settings_store()),
remote,
fake_local_terminals(),
Arc::new(FakeSftpProvider::default()),
Arc::new(FakeLocalDirectory::default()),
tester.clone(),
Arc::new(FakeForwardProvider::default()),
window,
cx,
)
});
workspace = Some(view.clone());
Root::new(view, window, cx)
});
(handle, workspace.expect("workspace created"))
}
/// `(host, port, user, password)` of one connection test.
pub type TestedLogin = (String, u16, String, Option<String>);
/// Stands in for the SSH login behind 「测试连接」: records what the form sent,
/// optionally asks to trust a made-up host key, and answers with `result`.
pub struct FakeConnectionTester {
pub asks_trust: bool,
pub result: Result<(), String>,
pub requests: Mutex<Vec<TestedLogin>>,
/// How each test was to reach the host, with the proxy password the
/// form gave.
pub routes: Mutex<Vec<(LoginRoute, Option<String>)>>,
pub trust_answers: Mutex<Vec<bool>>,
}
impl Default for FakeConnectionTester {
fn default() -> Self {
Self {
asks_trust: false,
result: Ok(()),
requests: Mutex::default(),
routes: Mutex::default(),
trust_answers: Mutex::default(),
}
}
}
impl FakeConnectionTester {
pub fn failing(reason: &str) -> Self {
Self {
result: Err(reason.to_string()),
..Self::default()
}
}
pub fn asking_trust() -> Self {
Self {
asks_trust: true,
..Self::default()
}
}
pub fn requests(&self) -> Vec<TestedLogin> {
self.requests
.lock()
.unwrap_or_else(|error| error.into_inner())
.clone()
}
pub fn routes(&self) -> Vec<(LoginRoute, Option<String>)> {
self.routes
.lock()
.unwrap_or_else(|error| error.into_inner())
.clone()
}
pub fn trust_answers(&self) -> Vec<bool> {
self.trust_answers
.lock()
.unwrap_or_else(|error| error.into_inner())
.clone()
}
}
impl ConnectionTester for FakeConnectionTester {
fn test(&self, request: LoginTest, trust: TrustCallback) -> Result<(), String> {
self.requests
.lock()
.unwrap_or_else(|error| error.into_inner())
.push((
request.host().to_string(),
request.port(),
request.user().to_string(),
request.password().map(str::to_string),
));
self.routes
.lock()
.unwrap_or_else(|error| error.into_inner())
.push((
request.login().route.clone(),
request.proxy_password().map(str::to_string),
));
if self.asks_trust {
let ConnectionPromptKind::UnknownHost(prompt) = ConnectionPromptKind::unknown_host(
request.host(),
request.port(),
"ssh-ed25519",
"SHA256:test-fingerprint",
) else {
unreachable!("unknown_host builds an UnknownHost prompt");
};
let trusted = trust(prompt);
self.trust_answers
.lock()
.unwrap_or_else(|error| error.into_inner())
.push(trusted);
if !trusted {
return Err("未信任该主机的密钥".to_string());
}
}
self.result.clone()
}
}