mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 10:29:58 +02:00
210 lines
6.8 KiB
Rust
210 lines
6.8 KiB
Rust
mod support;
|
|
|
|
use std::{
|
|
sync::Arc,
|
|
time::{Duration, Instant},
|
|
};
|
|
|
|
use support::{
|
|
receive_with_response, share_path, wait_for_receiver_request, CoreGuard, RecordingSink,
|
|
TestNode,
|
|
};
|
|
use vnidrop::{CoreLimits, ShareMetadataInput, ShareSource, SourceKind, TransferAccessMode};
|
|
|
|
fn wait_for_completed_delivery(
|
|
sender: &vnidrop::VnidropCore,
|
|
transfer_id: u64,
|
|
) -> Vec<vnidrop::ReceiverRequest> {
|
|
let started = Instant::now();
|
|
loop {
|
|
let requests = sender.list_receiver_requests(transfer_id).unwrap();
|
|
if requests.iter().any(|request| request.status == "completed") {
|
|
return requests;
|
|
}
|
|
assert!(
|
|
started.elapsed() < Duration::from_secs(5),
|
|
"delivery receipt was not recorded"
|
|
);
|
|
std::thread::sleep(Duration::from_millis(10));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn public_share_receives_without_sender_approval() {
|
|
let source_dir = tempfile::tempdir().unwrap();
|
|
let output_dir = tempfile::tempdir().unwrap();
|
|
let source_path = source_dir.path().join("public.txt");
|
|
std::fs::write(&source_path, b"public content").unwrap();
|
|
let sender = TestNode::new();
|
|
let receiver = TestNode::new();
|
|
let share = sender
|
|
.core
|
|
.share_files(
|
|
vec![ShareSource {
|
|
kind: SourceKind::Path,
|
|
value: source_path.to_string_lossy().into_owned(),
|
|
display_name: Some("public.txt".to_string()),
|
|
is_directory: false,
|
|
}],
|
|
ShareMetadataInput {
|
|
transfer_id: 30,
|
|
transfer_name: Some("Public file".to_string()),
|
|
sender_name: Some("Sender".to_string()),
|
|
access_mode: TransferAccessMode::Public,
|
|
},
|
|
)
|
|
.unwrap();
|
|
|
|
receiver
|
|
.core
|
|
.receive(
|
|
share.ticket,
|
|
output_dir.path().to_string_lossy().into_owned(),
|
|
Some("Receiver".to_string()),
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
std::fs::read(output_dir.path().join("public.txt")).unwrap(),
|
|
b"public content"
|
|
);
|
|
let deliveries = wait_for_completed_delivery(&sender.core, share.transfer_id);
|
|
assert_eq!(deliveries.len(), 1);
|
|
assert_eq!(deliveries[0].receiver_name.as_deref(), Some("Receiver"));
|
|
assert_eq!(deliveries[0].status, "completed");
|
|
assert!(deliveries[0].completed_at.is_some());
|
|
assert!(
|
|
sender.sink.events().iter().any(|event| {
|
|
event.phase == "delivery"
|
|
&& event.kind == "receiver-completed"
|
|
&& event.transfer_id == Some(share.transfer_id)
|
|
}),
|
|
"delivery receipts must emit a delivery phase event for UI live updates"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn approval_required_denies_then_allows_receiver() {
|
|
let source_dir = tempfile::tempdir().unwrap();
|
|
let denied_output = tempfile::tempdir().unwrap();
|
|
let allowed_output = tempfile::tempdir().unwrap();
|
|
let source_path = source_dir.path().join("private.txt");
|
|
std::fs::write(&source_path, b"approved content").unwrap();
|
|
let sender = TestNode::new();
|
|
let receiver = TestNode::new();
|
|
let share = share_path(&sender.core, &source_path, 9, "private.txt", false);
|
|
|
|
assert!(receive_with_response(
|
|
&sender.core,
|
|
share.transfer_id,
|
|
receiver.core.arc(),
|
|
share.ticket.clone(),
|
|
denied_output.path(),
|
|
false,
|
|
)
|
|
.is_err());
|
|
assert!(sender
|
|
.sink
|
|
.events()
|
|
.iter()
|
|
.any(|event| event.phase == "approval" && event.kind == "receiver-refused"));
|
|
|
|
receive_with_response(
|
|
&sender.core,
|
|
share.transfer_id,
|
|
receiver.core.arc(),
|
|
share.ticket,
|
|
allowed_output.path(),
|
|
true,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
std::fs::read(allowed_output.path().join("private.txt")).unwrap(),
|
|
b"approved content"
|
|
);
|
|
let completed = wait_for_completed_delivery(&sender.core, share.transfer_id);
|
|
assert!(completed
|
|
.iter()
|
|
.any(|request| request.status == "completed"));
|
|
}
|
|
|
|
#[test]
|
|
fn receiver_can_cancel_while_waiting_for_approval() {
|
|
let source_dir = tempfile::tempdir().unwrap();
|
|
let output_dir = tempfile::tempdir().unwrap();
|
|
let source_path = source_dir.path().join("waiting.txt");
|
|
std::fs::write(&source_path, b"waiting").unwrap();
|
|
let sender = TestNode::new();
|
|
let receiver = TestNode::new();
|
|
let share = share_path(&sender.core, &source_path, 26, "waiting.txt", false);
|
|
let receiver_core = receiver.core.arc();
|
|
let ticket = share.ticket;
|
|
let output = output_dir.path().to_string_lossy().to_string();
|
|
let worker = std::thread::spawn(move || {
|
|
receiver_core.receive(ticket, output, Some("receiver".to_string()))
|
|
});
|
|
|
|
let request = wait_for_receiver_request(&sender.core, share.transfer_id);
|
|
receiver.core.cancel_transfer(share.transfer_id).unwrap();
|
|
let _ = sender.core.respond_receiver_request(
|
|
request.id,
|
|
false,
|
|
Some("receiver-cancelled".to_string()),
|
|
);
|
|
assert!(worker.join().unwrap().is_err());
|
|
|
|
let transfer = receiver
|
|
.core
|
|
.list_transfers()
|
|
.unwrap()
|
|
.into_iter()
|
|
.find(|transfer| transfer.transfer_id == share.transfer_id)
|
|
.unwrap();
|
|
assert_eq!(transfer.status, "cancelled");
|
|
}
|
|
|
|
#[test]
|
|
fn pending_approval_limit_denies_excess_receiver() {
|
|
let sender_dir = tempfile::tempdir().unwrap();
|
|
let source_dir = tempfile::tempdir().unwrap();
|
|
let output_one = tempfile::tempdir().unwrap();
|
|
let output_two = tempfile::tempdir().unwrap();
|
|
let source_path = source_dir.path().join("limited.txt");
|
|
std::fs::write(&source_path, b"limited").unwrap();
|
|
let limits = CoreLimits {
|
|
max_pending_approvals: 1,
|
|
..CoreLimits::default()
|
|
};
|
|
let sender = CoreGuard::start_with_limits(
|
|
sender_dir.path(),
|
|
Arc::new(RecordingSink::default()),
|
|
limits,
|
|
);
|
|
let receiver_one = TestNode::new();
|
|
let receiver_two = TestNode::new();
|
|
let share = share_path(&sender, &source_path, 28, "limited.txt", false);
|
|
|
|
let first_core = receiver_one.core.arc();
|
|
let first_ticket = share.ticket.clone();
|
|
let first_output = output_one.path().to_string_lossy().to_string();
|
|
let first = std::thread::spawn(move || {
|
|
first_core.receive(first_ticket, first_output, Some("first".to_string()))
|
|
});
|
|
let request = wait_for_receiver_request(&sender, share.transfer_id);
|
|
|
|
let second = receiver_two.core.receive(
|
|
share.ticket,
|
|
output_two.path().to_string_lossy().to_string(),
|
|
Some("second".to_string()),
|
|
);
|
|
assert!(second
|
|
.unwrap_err()
|
|
.to_string()
|
|
.contains("too-many-pending-approvals"));
|
|
|
|
sender
|
|
.respond_receiver_request(request.id, false, Some("test complete".to_string()))
|
|
.unwrap();
|
|
assert!(first.join().unwrap().is_err());
|
|
}
|