Move the sanitizer next to the directory helpers so the CLI download command and the daemon honour use_username_dirs too, not only the TUI. Resumed downloads keep the folder they were saved with instead of nesting the username a second time, and toggling the setting applies to the next download without a restart. Document the config key.
229 lines
7.8 KiB
Rust
229 lines
7.8 KiB
Rust
//! The settings popup: the account it names, the folders it applies,
|
|
//! and the password and logout it can reach.
|
|
|
|
use super::*;
|
|
|
|
/// Attached, both folders in the settings form name paths on the
|
|
/// *daemon's* machine: they go over the socket exactly as typed, and
|
|
/// nothing is created or validated against this machine's filesystem.
|
|
#[test]
|
|
fn applying_settings_attached_pushes_both_folders_to_the_daemon() {
|
|
let session = Arc::new(TalkativeSession {
|
|
shared: true,
|
|
..TalkativeSession::default()
|
|
});
|
|
let scratch = tempfile::tempdir().expect("a temp dir");
|
|
let config_path = scratch.path().join("config.toml");
|
|
let mut tui = MainTui::new(
|
|
session.clone(),
|
|
"/daemon/old".to_string(),
|
|
false,
|
|
Duration::from_secs(1),
|
|
None,
|
|
Some(config_path.clone()),
|
|
);
|
|
|
|
let daemon_only = scratch.path().join("only-on-the-daemon");
|
|
let daemon_dir = daemon_only.to_str().expect("utf-8").to_string();
|
|
tui.open_settings();
|
|
{
|
|
let settings = tui.state.settings.as_mut().expect("open");
|
|
settings.download_dir.clone_from(&daemon_dir);
|
|
settings.share_dirs = vec!["/not-here-either".to_string()];
|
|
}
|
|
tui.apply_settings();
|
|
|
|
assert_eq!(
|
|
*session.download_dir_set.lock().expect("not poisoned"),
|
|
Some(daemon_dir.clone()),
|
|
"the folder is pushed to the daemon"
|
|
);
|
|
assert_eq!(
|
|
*session.shares_set.lock().expect("not poisoned"),
|
|
Some(vec!["/not-here-either".to_string()]),
|
|
"shares go over as typed — the daemon's filesystem judges them"
|
|
);
|
|
assert!(
|
|
!daemon_only.exists(),
|
|
"no directory is created on this machine for a daemon's path"
|
|
);
|
|
assert_eq!(tui.download_dir, daemon_dir);
|
|
|
|
let saved = crate::persist::config::FileConfig::load(&config_path)
|
|
.expect("config readable");
|
|
assert_eq!(
|
|
saved.download_dir.as_deref(),
|
|
Some(daemon_dir.as_str()),
|
|
"the change still lands in config.toml for the next restart"
|
|
);
|
|
}
|
|
|
|
/// Locally the same form works on this machine: the folder is created
|
|
/// here and share paths that do not exist here are dropped before the
|
|
/// session hears about them.
|
|
#[test]
|
|
fn applying_settings_locally_stays_on_this_machine() {
|
|
let session = Arc::new(TalkativeSession {
|
|
shared: false,
|
|
..TalkativeSession::default()
|
|
});
|
|
let scratch = tempfile::tempdir().expect("a temp dir");
|
|
let config_path = scratch.path().join("config.toml");
|
|
let share = scratch.path().join("music");
|
|
std::fs::create_dir_all(&share).expect("share dir");
|
|
let mut tui = MainTui::new(
|
|
session.clone(),
|
|
"/tmp".to_string(),
|
|
false,
|
|
Duration::from_secs(1),
|
|
None,
|
|
Some(config_path),
|
|
);
|
|
|
|
let downloads = scratch.path().join("downloads");
|
|
let downloads_dir = downloads.to_str().expect("utf-8").to_string();
|
|
tui.open_settings();
|
|
{
|
|
let settings = tui.state.settings.as_mut().expect("open");
|
|
settings.download_dir.clone_from(&downloads_dir);
|
|
settings.share_dirs = vec![
|
|
share.to_str().expect("utf-8").to_string(),
|
|
"/definitely-not-a-real-share".to_string(),
|
|
];
|
|
}
|
|
tui.apply_settings();
|
|
|
|
// The seam is told in both modes; a local session accepts and takes
|
|
// the directory per download instead of storing it.
|
|
assert_eq!(
|
|
*session.download_dir_set.lock().expect("not poisoned"),
|
|
Some(downloads_dir.clone())
|
|
);
|
|
assert_eq!(
|
|
*session.shares_set.lock().expect("not poisoned"),
|
|
Some(vec![share.to_str().expect("utf-8").to_string()]),
|
|
"paths missing on this machine are dropped before applying"
|
|
);
|
|
assert!(downloads.is_dir(), "the folder is created here");
|
|
assert_eq!(tui.download_dir, downloads_dir);
|
|
}
|
|
|
|
#[test]
|
|
fn toggling_username_folders_applies_live_and_persists() {
|
|
let session = Arc::new(TalkativeSession::default());
|
|
let scratch = tempfile::tempdir().expect("a temp dir");
|
|
let config_path = scratch.path().join("config.toml");
|
|
let mut tui = MainTui::new(
|
|
session,
|
|
scratch.path().to_str().expect("utf-8").to_string(),
|
|
false,
|
|
Duration::from_secs(1),
|
|
None,
|
|
Some(config_path.clone()),
|
|
);
|
|
|
|
tui.open_settings();
|
|
tui.state.settings.as_mut().expect("open").use_username_dirs = true;
|
|
tui.apply_settings();
|
|
|
|
assert!(tui.use_username_dirs, "the next download uses it");
|
|
let saved = crate::persist::config::FileConfig::load(&config_path)
|
|
.expect("config saved");
|
|
assert_eq!(saved.use_username_dirs, Some(true));
|
|
}
|
|
|
|
/// The account block answers "who is this session, and what is it
|
|
/// offering" without a trip to another screen.
|
|
#[test]
|
|
fn the_settings_popup_names_the_account_and_its_shares() {
|
|
let mut tui = with_session(TalkativeSession::default());
|
|
press(&mut tui, KeyCode::Char('o'));
|
|
|
|
let screen = screen_sized(&mut tui, 100, 30);
|
|
assert!(screen.contains("tester"), "{screen}");
|
|
assert!(screen.contains("12 files in 3 folders"), "{screen}");
|
|
}
|
|
|
|
/// Typed twice the same way, the new password reaches the session — and
|
|
/// attached to a daemon it is the daemon that stores it.
|
|
#[test]
|
|
fn a_password_typed_twice_reaches_the_session() {
|
|
let session = Arc::new(TalkativeSession {
|
|
shared: true,
|
|
..TalkativeSession::default()
|
|
});
|
|
let mut tui = attach(session.clone());
|
|
press(&mut tui, KeyCode::Char('o'));
|
|
press(&mut tui, KeyCode::Enter); // the change-password row is first
|
|
for _ in 0..2 {
|
|
for c in "hunter2".chars() {
|
|
press(&mut tui, KeyCode::Char(c));
|
|
}
|
|
press(&mut tui, KeyCode::Enter);
|
|
}
|
|
|
|
assert_eq!(
|
|
*session.password_set.lock().expect("not poisoned"),
|
|
Some("hunter2".to_string())
|
|
);
|
|
let settings = tui.state.settings.as_ref().expect("still open");
|
|
assert_eq!(settings.status.as_deref(), Some("Password changed"));
|
|
}
|
|
|
|
/// A mistyped repeat is caught here rather than on the server, where the
|
|
/// account would be left with a password nobody knows.
|
|
#[test]
|
|
fn a_password_typed_two_different_ways_is_not_sent() {
|
|
let session = Arc::new(TalkativeSession {
|
|
shared: true,
|
|
..TalkativeSession::default()
|
|
});
|
|
let mut tui = attach(session.clone());
|
|
press(&mut tui, KeyCode::Char('o'));
|
|
press(&mut tui, KeyCode::Enter);
|
|
for typed in ["hunter2", "hunter3"] {
|
|
for c in typed.chars() {
|
|
press(&mut tui, KeyCode::Char(c));
|
|
}
|
|
press(&mut tui, KeyCode::Enter);
|
|
}
|
|
|
|
assert_eq!(*session.password_set.lock().expect("not poisoned"), None);
|
|
}
|
|
|
|
/// Logging out ends the window, but as a log-out: the caller brings the
|
|
/// login screen back rather than returning to the shell.
|
|
#[test]
|
|
fn logging_out_asks_first_and_then_closes_the_window() {
|
|
let mut tui = with_session(TalkativeSession::default());
|
|
press(&mut tui, KeyCode::Char('o'));
|
|
press(&mut tui, KeyCode::Down); // change password -> log out
|
|
press(&mut tui, KeyCode::Enter);
|
|
assert!(tui.state.exit.is_none(), "the confirm comes first");
|
|
|
|
press(&mut tui, KeyCode::Char('y'));
|
|
assert_eq!(tui.state.exit, Some(TuiExit::Logout));
|
|
}
|
|
|
|
/// Attached, the login is the daemon's: there is no logout row to land
|
|
/// on, so the row below the password is the download folder.
|
|
#[test]
|
|
fn an_attached_window_offers_no_logout() {
|
|
let session = Arc::new(TalkativeSession {
|
|
shared: true,
|
|
..TalkativeSession::default()
|
|
});
|
|
let mut tui = attach(session);
|
|
press(&mut tui, KeyCode::Char('o'));
|
|
press(&mut tui, KeyCode::Down);
|
|
press(&mut tui, KeyCode::Down);
|
|
press(&mut tui, KeyCode::Enter);
|
|
|
|
let settings = tui.state.settings.as_ref().expect("still open");
|
|
assert_eq!(
|
|
settings.mode,
|
|
crate::models::SettingsMode::EditingDownloadDir
|
|
);
|
|
assert!(tui.state.exit.is_none());
|
|
}
|