Files
soulseek-rs-modded/soulseek-rs/src/ui/main_tui/tests/settings.rs
T
Michel de Graaf ea884535a8 fix(tui): apply username folders everywhere and resume in place
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.
2026-10-22 14:19:18 +02:00

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());
}