test(shares): two real clients search and download a shared file e2e
End-to-end proof of the whole sharing stack with no mock peer: a sharer indexes a temp file, the leecher's search is distributed by soulfind, the sharer replies with a FileSearchResponse, and the leecher downloads the real bytes that the sharer serves over a peer-to-peer transfer. Asserts the search result and that the downloaded bytes match the source. Server-gated (SOULSEEK_E2E_REQUIRED).
This commit is contained in:
@@ -1043,3 +1043,105 @@ fn a_file_downloads_from_a_firewalled_peer_via_server_broker() {
|
|||||||
|
|
||||||
let _ = std::fs::remove_dir_all(&download_dir);
|
let _ = std::fs::remove_dir_all(&download_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Two real clients: one shares a file, the other searches for it and downloads
|
||||||
|
// it — the entire search + connect + upload/download stack, no mock peer.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_real_clients_search_and_download() {
|
||||||
|
let server = server_or_skip!();
|
||||||
|
|
||||||
|
// Sharer with one distinctively named file.
|
||||||
|
let share_dir = unique_download_dir();
|
||||||
|
let content: Vec<u8> = (0..4096u32).map(|i| (i % 251) as u8).collect();
|
||||||
|
let filename = "e2e_probe_xyzzy.bin";
|
||||||
|
std::fs::write(share_dir.join(filename), &content).unwrap();
|
||||||
|
|
||||||
|
let sharer_port = free_port().expect("sharer port");
|
||||||
|
let mut sharer = Client::with_settings(ClientSettings {
|
||||||
|
shared_directory: Some(share_dir.display().to_string()),
|
||||||
|
..server.listening_settings("e2e_sharer", "pw", sharer_port)
|
||||||
|
});
|
||||||
|
sharer.connect().expect("sharer connect");
|
||||||
|
assert!(sharer.login().expect("sharer login"));
|
||||||
|
|
||||||
|
let leecher_port = free_port().expect("leecher port");
|
||||||
|
let mut leecher = Client::with_settings(server.listening_settings(
|
||||||
|
"e2e_leecher",
|
||||||
|
"pw",
|
||||||
|
leecher_port,
|
||||||
|
));
|
||||||
|
leecher.connect().expect("leecher connect");
|
||||||
|
assert!(leecher.login().expect("leecher login"));
|
||||||
|
|
||||||
|
// Let soulfind register both SetWaitPorts before the search resolves peers.
|
||||||
|
std::thread::sleep(Duration::from_secs(1));
|
||||||
|
|
||||||
|
let query = "xyzzy";
|
||||||
|
let _ = leecher.search(query, Duration::from_secs(3));
|
||||||
|
|
||||||
|
// Poll until the sharer's response for our file arrives.
|
||||||
|
let mut hit: Option<(String, u64)> = None;
|
||||||
|
let deadline = Instant::now() + Duration::from_secs(20);
|
||||||
|
while Instant::now() < deadline && hit.is_none() {
|
||||||
|
for result in leecher.get_search_results(query) {
|
||||||
|
if result.username == "e2e_sharer" {
|
||||||
|
for file in &result.files {
|
||||||
|
if file.name.contains("e2e_probe_xyzzy") {
|
||||||
|
hit = Some((file.name.clone(), file.size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hit.is_none() {
|
||||||
|
std::thread::sleep(Duration::from_millis(200));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let (result_path, size) =
|
||||||
|
hit.expect("leecher should find the sharer's file");
|
||||||
|
assert_eq!(size, content.len() as u64);
|
||||||
|
|
||||||
|
// Download it from the sharer.
|
||||||
|
let download_dir = unique_download_dir();
|
||||||
|
let (_download, status_rx) = leecher
|
||||||
|
.download(
|
||||||
|
result_path.clone(),
|
||||||
|
"e2e_sharer".to_string(),
|
||||||
|
size,
|
||||||
|
download_dir.display().to_string(),
|
||||||
|
)
|
||||||
|
.expect("start download");
|
||||||
|
|
||||||
|
let mut completed = false;
|
||||||
|
let deadline = Instant::now() + Duration::from_secs(25);
|
||||||
|
while Instant::now() < deadline {
|
||||||
|
match status_rx.recv_timeout(Duration::from_millis(500)) {
|
||||||
|
Ok(DownloadStatus::Completed) => {
|
||||||
|
completed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Ok(DownloadStatus::Failed | DownloadStatus::TimedOut) => break,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
if leecher
|
||||||
|
.get_all_downloads()
|
||||||
|
.iter()
|
||||||
|
.any(|d| matches!(d.status, DownloadStatus::Completed))
|
||||||
|
{
|
||||||
|
completed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(completed, "the download should complete");
|
||||||
|
|
||||||
|
// The virtual path is backslash-separated; the saved file uses the basename.
|
||||||
|
let basename = result_path.rsplit(['\\', '/']).next().unwrap();
|
||||||
|
let written = std::fs::read(download_dir.join(basename))
|
||||||
|
.expect("downloaded file should exist");
|
||||||
|
assert_eq!(written, content, "downloaded bytes should match the source");
|
||||||
|
|
||||||
|
let _ = std::fs::remove_dir_all(share_dir);
|
||||||
|
let _ = std::fs::remove_dir_all(download_dir);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user