mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-21 16:02:20 +00:00
test(sftp): drive the subsystem against a real server
`SftpManager` had no live coverage of any kind. Its tests cover the path arithmetic — `remote_join`, `remote_parent`, `safe_local_name` — and the transport-failure classifier, none of which needs a server, and all of which sits downstream of a subsystem no test had ever opened. The whole panel is built on two calls, `list` and `op`, and neither had been made against sshd. Now both are, and I ran it: mkdir a scratch directory, create a file in it, list it and check the entry comes back as a `File`, rename it and check both that the new name is there and the old one is not, stat a path that was never created and require the answer to be something other than `Done`, then remove the file, remove the directory, and confirm listing it fails. Everything lives under a directory named for the process on the far side and is removed at the end; the far side being this same machine is what makes it safe to assert about. Checked afterwards that `$HOME` holds no leftovers. Takes its turn on the same mutex as the other two, for the reason that one documents: they share `SshManager::global()`. All three pass together — the fourth, GSSAPI, still wants a Kerberos realm this machine does not have.
This commit is contained in:
@@ -1047,6 +1047,95 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
/// The SFTP subsystem against a real server: create, list, stat, rename,
|
||||
/// remove.
|
||||
///
|
||||
/// `SftpManager` had no live coverage of any kind. Its own tests cover the
|
||||
/// path arithmetic — `remote_join`, `remote_parent`, `safe_local_name` —
|
||||
/// and the transport-failure classifier, none of which needs a server, and
|
||||
/// all of which is downstream of a subsystem that was never opened in a
|
||||
/// test. The panel is built entirely on these two calls.
|
||||
///
|
||||
/// Everything happens under a scratch directory named for this process, on
|
||||
/// the far side, and is removed at the end. The far side being this same
|
||||
/// machine is what makes that safe to assert about.
|
||||
#[test]
|
||||
#[ignore = "requires a live SSH server that accepts a key of yours"]
|
||||
fn live_sftp_lists_and_edits_a_real_directory() {
|
||||
let _turn = live_ssh_turn();
|
||||
let spec = live_key_spec();
|
||||
let manager = SshManager::global();
|
||||
let broker = PromptBroker::new(Box::new(|_| true));
|
||||
let conn = manager
|
||||
.runtime
|
||||
.block_on(async { manager.open_connection(&spec, &broker).await })
|
||||
.expect("a connection to run sftp over")
|
||||
.0;
|
||||
|
||||
let sftp = crate::daemon::ssh::sftp::SftpManager::global();
|
||||
let root = format!(
|
||||
"{}/tty7-sftp-live-{}",
|
||||
std::env::var("HOME").unwrap_or_else(|_| "/tmp".into()),
|
||||
std::process::id()
|
||||
);
|
||||
let file = format!("{root}/hello.txt");
|
||||
let moved = format!("{root}/moved.txt");
|
||||
|
||||
use crate::daemon::protocol::{SftpEntryKind, SftpOp, SftpOpResult};
|
||||
let ok = |op: SftpOp| match sftp.op(&conn, &op) {
|
||||
SftpOpResult::Done => {}
|
||||
other => panic!("{op:?} answered {other:?}"),
|
||||
};
|
||||
|
||||
ok(SftpOp::Mkdir { path: root.clone() });
|
||||
ok(SftpOp::CreateFile { path: file.clone() });
|
||||
|
||||
let listed = sftp.list(&conn, &root).expect("list the scratch directory");
|
||||
let hello = listed
|
||||
.iter()
|
||||
.find(|e| e.name == "hello.txt")
|
||||
.unwrap_or_else(|| panic!("hello.txt is missing from {listed:?}"));
|
||||
assert_eq!(hello.kind, SftpEntryKind::File, "and it is a file");
|
||||
|
||||
ok(SftpOp::Rename {
|
||||
from: file.clone(),
|
||||
to: moved.clone(),
|
||||
});
|
||||
let listed = sftp.list(&conn, &root).expect("list it again");
|
||||
assert!(
|
||||
listed.iter().any(|e| e.name == "moved.txt"),
|
||||
"the rename moved it: {listed:?}"
|
||||
);
|
||||
assert!(
|
||||
!listed.iter().any(|e| e.name == "hello.txt"),
|
||||
"and left nothing behind: {listed:?}"
|
||||
);
|
||||
|
||||
// A path that is not there answers rather than hanging or succeeding.
|
||||
assert!(
|
||||
!matches!(
|
||||
sftp.op(
|
||||
&conn,
|
||||
&SftpOp::Stat {
|
||||
path: format!("{root}/never-existed"),
|
||||
}
|
||||
),
|
||||
SftpOpResult::Done
|
||||
),
|
||||
"stat of a missing path is not Done"
|
||||
);
|
||||
|
||||
ok(SftpOp::RemoveFile { path: moved });
|
||||
ok(SftpOp::RemoveDir { path: root.clone() });
|
||||
assert!(
|
||||
sftp.list(&conn, &root).is_err(),
|
||||
"the scratch directory is gone"
|
||||
);
|
||||
|
||||
conn.mark_dead();
|
||||
manager.evict_connection(conn.key());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires a live SSH server and local GSSAPI credentials"]
|
||||
fn live_gssapi_connects_and_opens_a_channel() {
|
||||
|
||||
Reference in New Issue
Block a user