Files
michel 36819d4c42 test: persistence e2e and registration-relogin e2e; lint clean
Adds a lib target (soulseek_rs_tui) exposing cli/models/persist so
integration tests can drive the persistence pipeline: state and config
survive a simulated restart through the real files, with the incomplete
download flagged for auto-resume. The lib e2e suite gains a
register-then-relogin test against soulfind proving the stored-credential
flow's server assumptions. cargo clippy --all-targets and cargo fmt are
clean.
2026-07-20 20:20:54 +02:00

104 lines
3.5 KiB
Rust

//! End-to-end test of the persistence pipeline across a simulated restart:
//! capture live-ish app state, write it through the real state files, load
//! it back in a fresh store (a "second session"), and restore it.
use soulseek_rs::DownloadStatus;
use soulseek_rs::types::{Download, DownloadMetadata};
use soulseek_rs_tui::models::{AppState, DownloadEntry};
use soulseek_rs_tui::persist::config::FileConfig;
use soulseek_rs_tui::persist::snapshot::{Snapshot, restore_searches};
use soulseek_rs_tui::persist::state::StateStore;
fn download_entry(filename: &str, status: DownloadStatus) -> DownloadEntry {
DownloadEntry {
download: Download {
username: "peer".into(),
filename: filename.into(),
token: 1,
size: 1000,
download_directory: "/music".into(),
status,
sender: std::sync::mpsc::channel().0,
queue_position: None,
metadata: DownloadMetadata::default(),
},
receiver: None,
}
}
#[test]
fn state_survives_a_restart() {
let tmp = tempfile::tempdir().unwrap();
let state_dir = tmp.path().join("state");
// Session 1: user searched, joined rooms, and has one finished and one
// in-flight download when the app exits.
{
let mut state = AppState::new();
state
.downloads
.push(download_entry("done.flac", DownloadStatus::Completed));
state.downloads.push(download_entry(
"half.flac",
DownloadStatus::InProgress {
bytes_downloaded: 500,
total_bytes: 1000,
speed_bytes_per_sec: 1.0,
},
));
restore_searches(&mut state, &["beatles".to_string()]);
state.rooms.focus_or_open("indie");
let store = StateStore::new(state_dir.clone());
let snapshot = Snapshot::capture(&state);
store.save_downloads(&snapshot.downloads).unwrap();
store.save_search_queries(&snapshot.queries).unwrap();
store.save_rooms(&snapshot.rooms).unwrap();
}
// The files on disk are versioned envelopes.
let raw =
std::fs::read_to_string(state_dir.join("downloads.json")).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&raw).unwrap();
assert!(parsed.get("version").is_some(), "missing version envelope");
// Session 2: a fresh store (new process) sees the same state; the
// incomplete download is flagged for auto-resume.
let store = StateStore::new(state_dir);
let downloads = store.load_downloads();
assert_eq!(downloads.len(), 2);
assert!(
downloads
.iter()
.any(|d| d.filename == "done.flac" && d.completed)
);
assert!(
downloads
.iter()
.any(|d| d.filename == "half.flac" && !d.completed)
);
assert_eq!(store.load_search_queries(), vec!["beatles".to_string()]);
assert_eq!(store.load_rooms(), vec!["indie".to_string()]);
let mut state = AppState::new();
restore_searches(&mut state, &store.load_search_queries());
assert_eq!(state.searches[0].query, "beatles");
}
#[test]
fn config_survives_a_restart() {
let tmp = tempfile::tempdir().unwrap();
let path = tmp.path().join("config").join("config.toml");
let config = FileConfig {
username: Some("alice".into()),
server: Some("localhost:2242".into()),
..FileConfig::default()
};
config.save(&path).unwrap();
// "Second session": reload from disk.
let loaded = FileConfig::load(&path).unwrap();
assert_eq!(loaded, config);
}