90a54034f7
CI / checks (push) Successful in 53s
Co-authored-by: Danny Kim <temeddix@gmail.com> Co-committed-by: Danny Kim <temeddix@gmail.com>
174 lines
5.9 KiB
Rust
174 lines
5.9 KiB
Rust
//! FTP command recovery against an in-memory protocol transport. Live QA
|
|
//! separately exercises the compiled WASM component over real host sockets.
|
|
|
|
use super::{Ftp, stage_path_with, start_writer, test_host};
|
|
use memona_plugin_sdk::{ErrorCode, Guest, GuestWriter, Locator, Setting};
|
|
|
|
type TestResult<T = ()> = Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
|
|
|
fn guest<T>(value: Result<T, ErrorCode>) -> TestResult<T> {
|
|
value.map_err(|error| format!("guest operation failed: {error:?}").into())
|
|
}
|
|
|
|
fn config() -> Vec<(String, Setting)> {
|
|
vec![
|
|
("host".into(), Setting::Text("127.0.0.1".into())),
|
|
(
|
|
"port".into(),
|
|
Setting::Text(test_host::CONTROL_PORT.to_string()),
|
|
),
|
|
("username".into(), Setting::Text("fixture".into())),
|
|
(
|
|
"password".into(),
|
|
Setting::Secret(" fixture secret ".into()),
|
|
),
|
|
]
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn a_listing_reconnects_after_the_server_expires_the_control_session()
|
|
-> TestResult {
|
|
let fixture = test_host::install(test_host::Scenario::ExpiredListing);
|
|
guest(Ftp::open("ftp".into(), config()))?;
|
|
let first = guest(Ftp::list(Locator::Path("/".into()), None, 10))?;
|
|
assert_eq!(first.entries[0].name, " report.md ");
|
|
let next = guest(Ftp::list(Locator::Path("/".into()), None, 10))?;
|
|
assert_eq!(next.entries[0].name, " report.md ");
|
|
Ftp::close();
|
|
let summary = fixture.summary();
|
|
assert_eq!(summary.control_connections, 2);
|
|
assert_eq!(summary.listings, 2);
|
|
assert_eq!(summary.mkdir_commands, 0);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn recovery_before_a_mutation_never_replays_a_lost_mutation_reply()
|
|
-> TestResult {
|
|
let fixture = test_host::install(test_host::Scenario::LostMutationReply);
|
|
guest(Ftp::open("ftp".into(), config()))?;
|
|
assert!(matches!(
|
|
Ftp::mkdir(Locator::Path("/new".into())),
|
|
Err(ErrorCode::OutcomeUnknown)
|
|
));
|
|
Ftp::close();
|
|
let summary = fixture.summary();
|
|
assert_eq!(summary.control_connections, 2);
|
|
assert_eq!(summary.listings, 0);
|
|
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(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn a_lost_upload_completion_reports_an_unknown_outcome() -> TestResult {
|
|
let fixture = test_host::install(test_host::Scenario::LostUploadCompletion);
|
|
guest(Ftp::open("ftp".into(), config()))?;
|
|
let writer = guest(start_writer("/target".into()))?;
|
|
guest(writer.push(b"body".to_vec()))?;
|
|
assert!(matches!(writer.finish(), Err(ErrorCode::OutcomeUnknown)));
|
|
drop(writer);
|
|
Ftp::close();
|
|
let summary = fixture.summary();
|
|
assert_eq!(summary.control_connections, 2);
|
|
assert_eq!(summary.upload_commands, 1);
|
|
assert_eq!(summary.target_upload_commands, 0);
|
|
assert_eq!(summary.uploaded_bytes, 4);
|
|
assert_eq!(summary.stage_deletes, 1);
|
|
assert_eq!(summary.promotions, 0);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn an_uncertain_upload_write_discards_the_control_session() -> TestResult
|
|
{
|
|
let fixture = test_host::install(test_host::Scenario::LostUploadData);
|
|
guest(Ftp::open("ftp".into(), config()))?;
|
|
let writer = guest(start_writer("/target".into()))?;
|
|
assert!(matches!(
|
|
writer.push(b"body".to_vec()),
|
|
Err(ErrorCode::OutcomeUnknown)
|
|
));
|
|
let page = guest(Ftp::list(Locator::Path("/".into()), None, 10))?;
|
|
assert_eq!(page.entries[0].name, " report.md ");
|
|
drop(writer);
|
|
Ftp::close();
|
|
let summary = fixture.summary();
|
|
assert_eq!(summary.control_connections, 2);
|
|
assert_eq!(summary.upload_commands, 1);
|
|
assert_eq!(summary.target_upload_commands, 0);
|
|
assert_eq!(summary.uploaded_bytes, 0);
|
|
assert_eq!(summary.stage_deletes, 1);
|
|
assert_eq!(summary.promotions, 0);
|
|
assert_eq!(summary.listings, 1);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn a_complete_upload_promotes_its_private_stage() -> TestResult {
|
|
let fixture = test_host::install(test_host::Scenario::SuccessfulUpload);
|
|
guest(Ftp::open("ftp".into(), config()))?;
|
|
let writer = guest(start_writer("/target".into()))?;
|
|
guest(writer.push(b"body".to_vec()))?;
|
|
let info = guest(writer.finish())?.ok_or("missing upload result")?;
|
|
assert_eq!(info.size, 4);
|
|
drop(writer);
|
|
Ftp::close();
|
|
let summary = fixture.summary();
|
|
assert_eq!(summary.control_connections, 1);
|
|
assert_eq!(summary.upload_commands, 1);
|
|
assert_eq!(summary.target_upload_commands, 0);
|
|
assert_eq!(summary.uploaded_bytes, 4);
|
|
assert_eq!(summary.stage_deletes, 0);
|
|
assert_eq!(summary.promotions, 1);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn dropping_an_upload_removes_only_its_private_stage() -> TestResult {
|
|
let fixture = test_host::install(test_host::Scenario::AbandonedUpload);
|
|
guest(Ftp::open("ftp".into(), config()))?;
|
|
let writer = guest(start_writer("/target".into()))?;
|
|
guest(writer.push(b"partial".to_vec()))?;
|
|
drop(writer);
|
|
Ftp::close();
|
|
let summary = fixture.summary();
|
|
assert_eq!(summary.control_connections, 1);
|
|
assert_eq!(summary.upload_commands, 1);
|
|
assert_eq!(summary.target_upload_commands, 0);
|
|
assert_eq!(summary.uploaded_bytes, 7);
|
|
assert_eq!(summary.stage_deletes, 1);
|
|
assert_eq!(summary.promotions, 0);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn a_stage_name_collision_draws_a_fresh_random_token() -> TestResult {
|
|
let fixture = test_host::install(test_host::Scenario::StageCollision);
|
|
guest(Ftp::open("ftp".into(), config()))?;
|
|
let mut tokens = [1, 2].into_iter();
|
|
let staged = guest(stage_path_with("/target", || {
|
|
tokens.next().ok_or(ErrorCode::Internal)
|
|
}))?;
|
|
assert_eq!(staged, "/.memona-upload-00000000000000000000000000000002");
|
|
Ftp::close();
|
|
assert_eq!(fixture.summary().stage_probes, 2);
|
|
Ok(())
|
|
}
|