diff --git a/soulseek-rs/tests/cli_e2e.rs b/soulseek-rs/tests/cli_e2e.rs index b05bf93..040e663 100644 --- a/soulseek-rs/tests/cli_e2e.rs +++ b/soulseek-rs/tests/cli_e2e.rs @@ -11,7 +11,7 @@ mod common; -use common::{CLI_ENV_VARS, Soulfind, free_port, login, settle}; +use common::{CLI_ENV_VARS, Soulfind, free_port, login, settle, silent_peer}; use soulseek_rs::Client; use std::io::Write; use std::net::{TcpStream, ToSocketAddrs}; @@ -1560,12 +1560,14 @@ fn transfer_cancel_stops_a_download_another_client_is_waiting_on() { return; } let server = server_or_skip!(); + let _peer = + silent_peer(&server.host, server.port, "cli_e2e_cancel_silent_peer"); let mut all = server.args("cli_e2e_cancel_waiter"); all.extend( [ "download", - "e2e_ghost_peer", - "@@ghost\\never.mp3", + "cli_e2e_cancel_silent_peer", + "@@silent\\never.mp3", "--size", "10", "--timeout", @@ -1587,7 +1589,12 @@ fn transfer_cancel_stops_a_download_another_client_is_waiting_on() { let output = cli( &server, "cli_e2e_cancel_waiter", - &["transfer", "cancel", "e2e_ghost_peer", "@@ghost\\never.mp3"], + &[ + "transfer", + "cancel", + "cli_e2e_cancel_silent_peer", + "@@silent\\never.mp3", + ], ); if code(&output) == EXIT_OK { cancelled = Some(output); diff --git a/soulseek-rs/tests/common/mod.rs b/soulseek-rs/tests/common/mod.rs index 03ada03..d942a9e 100644 --- a/soulseek-rs/tests/common/mod.rs +++ b/soulseek-rs/tests/common/mod.rs @@ -3,8 +3,10 @@ #![allow(dead_code)] +use soulseek_rs::message::server::MessageFactory; use soulseek_rs::{Client, ClientSettings, PeerAddress}; -use std::net::TcpStream; +use std::io::{Read, Write}; +use std::net::{TcpListener, TcpStream}; use std::path::{Path, PathBuf}; use std::process::{Child, Command, Stdio}; use std::time::{Duration, Instant}; @@ -145,6 +147,57 @@ pub fn login(host: &str, port: u16, user: &str, shares: Vec) -> Client { client } +/// A user who is online and reachable but never answers, held for as long as +/// the value lives. +/// +/// Their listener accepts a connection and then says nothing, so a download +/// from them waits in the queue the way one waits behind a busy peer, with no +/// timer to race. A user who does not exist cannot stand in for this: the +/// server refuses to broker a connection to them, and their download fails +/// before a test gets to act on it. +pub struct SilentPeer { + _session: TcpStream, + _listener: TcpListener, +} + +pub fn silent_peer(host: &str, port: u16, user: &str) -> SilentPeer { + // Not loopback: soulfind hands a loopback login's address out as the + // host's LAN address, where a 127.0.0.1 listener is never reached. + let listener = TcpListener::bind("0.0.0.0:0").expect("silent listener"); + let wait_port = listener.local_addr().expect("listener address").port(); + let mut session = TcpStream::connect((host, port)).expect("server"); + let login = MessageFactory::build_login_message( + user, + "pw", + soulseek_rs::ClientVersion::default(), + ); + session + .write_all(&login.get_buffer()) + .expect("silent login"); + // soulfind drops everything but a login until the login has landed, so a + // port announced any sooner is announced to nobody. + session + .set_read_timeout(Some(Duration::from_secs(10))) + .expect("read timeout"); + loop { + let mut len = [0; 4]; + session.read_exact(&mut len).expect("login reply"); + let mut body = vec![0; u32::from_le_bytes(len) as usize]; + session.read_exact(&mut body).expect("login reply"); + if body[..4] == 1u32.to_le_bytes() { + assert_eq!(body[4], 1, "{user} should log in"); + break; + } + } + let wait = MessageFactory::build_set_wait_port_message(wait_port); + session.write_all(&wait.get_buffer()).expect("wait port"); + settle(); + SilentPeer { + _session: session, + _listener: listener, + } +} + /// Wait out the SetWaitPort registrations so peer lookups resolve. pub fn settle() { std::thread::sleep(Duration::from_secs(1)); diff --git a/soulseek-rs/tests/daemon_protocol_e2e.rs b/soulseek-rs/tests/daemon_protocol_e2e.rs index 60fd48f..c67063d 100644 --- a/soulseek-rs/tests/daemon_protocol_e2e.rs +++ b/soulseek-rs/tests/daemon_protocol_e2e.rs @@ -13,7 +13,7 @@ mod common; -use common::{CLI_ENV_VARS, Soulfind, free_port}; +use common::{CLI_ENV_VARS, Soulfind, free_port, silent_peer}; use std::io::{BufRead, BufReader, Write}; use std::net::TcpStream; use std::path::{Path, PathBuf}; @@ -838,19 +838,21 @@ fn a_wishlist_search_is_reported_as_just_started() { #[test] fn a_queued_download_is_cancelled_and_listed_as_such() { let daemon = daemon_or_skip!(); + let _peer = + silent_peer("127.0.0.1", daemon.server.port(), "cli_e2e_silent_peer"); let mut connection = daemon.connect_tcp().expect("tcp"); connection .authenticate(Some(&daemon.token())) .expect("an answer"); let started = connection .call( - r#"{"jsonrpc":"2.0","id":2,"method":"download.start","params":{"username":"e2e_ghost_peer","filename":"@@ghost\\never.mp3","size":10}}"#, + r#"{"jsonrpc":"2.0","id":2,"method":"download.start","params":{"username":"cli_e2e_silent_peer","filename":"@@silent\\never.mp3","size":10}}"#, ) .expect("an answer"); assert!(!started.contains("error"), "got {started}"); let reply = connection .call( - r#"{"jsonrpc":"2.0","id":3,"method":"download.cancel","params":{"username":"e2e_ghost_peer","filename":"@@ghost\\never.mp3"}}"#, + r#"{"jsonrpc":"2.0","id":3,"method":"download.cancel","params":{"username":"cli_e2e_silent_peer","filename":"@@silent\\never.mp3"}}"#, ) .expect("an answer"); assert!(reply.contains(r#""ok":true"#), "got {reply}");