diff --git a/src/lib.rs b/src/lib.rs index 416f012..cbc279b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,8 +100,12 @@ fn with_control( /// idle session is replaced and logged in again. Nothing inside a transfer or /// mutation retries, because its missing reply may hide a completed change. fn ready() -> Result<(), ErrorCode> { - if with_control(|control| control.expect("NOOP")).is_ok() { - return Ok(()); + if !CONTROL.with_borrow(Option::is_none) { + match with_control(|control| control.expect("NOOP")) { + Ok(_) => return Ok(()), + Err(ErrorCode::Unavailable) => {} + Err(error) => return Err(error), + } } let session = SESSION .with_borrow(Clone::clone) diff --git a/src/session_tests.rs b/src/session_tests.rs index bef5fb2..8e059fa 100644 --- a/src/session_tests.rs +++ b/src/session_tests.rs @@ -58,3 +58,20 @@ async fn recovery_before_a_mutation_never_replays_a_lost_mutation_reply() assert_eq!(summary.mkdir_commands, 1); Ok(()) } + +#[tokio::test] +async fn cancelled_preflight_never_reconnects_or_executes_the_mutation() +-> TestResult { + let fixture = test_host::install(test_host::Scenario::CancelledPreflight); + guest(Ftp::open("ftp".into(), config()))?; + assert!(matches!( + Ftp::mkdir(Locator::Path("/cancelled".into())), + Err(ErrorCode::Cancelled) + )); + Ftp::close(); + let summary = fixture.summary(); + assert_eq!(summary.control_connections, 1); + assert_eq!(summary.listings, 0); + assert_eq!(summary.mkdir_commands, 0); + Ok(()) +} diff --git a/src/test_host.rs b/src/test_host.rs index 9be6f4e..cff73ec 100644 --- a/src/test_host.rs +++ b/src/test_host.rs @@ -13,6 +13,7 @@ const LISTING: &[u8] = b"type=file;size=3; report.md \r\n"; #[derive(Clone, Copy)] pub enum Scenario { + CancelledPreflight, ExpiredListing, LostMutationReply, } @@ -142,6 +143,9 @@ impl Tcp { state.listings += 1; "150 Listing\r\n226 Complete\r\n" } + "NOOP" if matches!(state.scenario, Scenario::CancelledPreflight) => { + return Err(HostError::Cancelled); + } "NOOP" if index == 0 && (matches!(state.scenario, Scenario::LostMutationReply) @@ -157,6 +161,13 @@ impl Tcp { state.mkdir_commands += 1; return Ok(bytes.len() as u32); } + "MKD /cancelled" + if index == 1 + && matches!(state.scenario, Scenario::CancelledPreflight) => + { + state.mkdir_commands += 1; + return Err(HostError::Unavailable); + } _ => return Err(HostError::InvalidInput), } };