FTP connections preserve passwords and filenames, with CI checking every change (#1)
CI / checks (push) Successful in 53s
CI / checks (push) Successful in 53s
Co-authored-by: Danny Kim <temeddix@gmail.com> Co-committed-by: Danny Kim <temeddix@gmail.com>
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
//! 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user