use dove_core::backend::SelfHosted;
use dove_core::progress::Progress;
use dove_core::transfer::{GetRequest, Transfer};
use std::cell::RefCell;
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
#[derive(Default)]
struct RecordingProgress {
events: RefCell<Vec<String>>,
}
impl Progress for RecordingProgress {
fn step(&self, label: &str) {
self.events.borrow_mut().push(format!("step:{label}"));
}
fn done(&self, label: &str) {
self.events.borrow_mut().push(format!("done:{label}"));
}
fn field(&self, key: &str, value: &str) {
self.events
.borrow_mut()
.push(format!("field:{key}={value}"));
}
fn bytes(&self, _uploaded: u64, _total: u64) {
self.events.borrow_mut().push("bytes".to_string());
}
}
fn respond(mut stream: TcpStream, body: &[u8]) {
let mut buf = [0u8; 4096];
let _ = stream.set_read_timeout(Some(std::time::Duration::from_millis(500)));
let _ = stream.read(&mut buf);
let header = format!(
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
body.len()
);
let _ = stream.write_all(header.as_bytes());
let _ = stream.write_all(body);
let _ = stream.flush();
}
#[test]
fn from_and_message_are_reported_before_the_first_byte_downloads() {
let secret = dove_core::crypto::gen_key();
let fragment = dove_core::crypto::key_to_fragment(&secret);
let meta_json = serde_json::json!({
"name": "report.pdf",
"from": "Alex",
"msg": "the codes",
})
.to_string();
let meta_blob = dove_core::crypto::encrypt_meta(&secret, meta_json.as_bytes());
let meta_body = serde_json::json!({ "meta": meta_blob }).to_string();
let dl_body = b"not-a-dove-container".to_vec();
let listener = TcpListener::bind("127.0.0.1:0").expect("bind mock server");
let port = listener.local_addr().unwrap().port();
let server = std::thread::spawn(move || {
let (meta_conn, _) = listener.accept().expect("accept /meta connection");
respond(meta_conn, meta_body.as_bytes());
let (dl_conn, _) = listener.accept().expect("accept /dl connection");
respond(dl_conn, &dl_body);
});
let url = format!("http://127.0.0.1:{port}/d/abc123/report.pdf#{fragment}");
let out = std::env::temp_dir().join(format!("dove-core-get-ordering-{port}.bin"));
let req = GetRequest {
url,
out: Some(out.clone()),
pin: None,
};
let progress = RecordingProgress::default();
let backend = SelfHosted::adhoc();
let result = backend.get(req, &progress);
server.join().expect("mock server thread panicked");
let _ = std::fs::remove_file(&out);
assert!(
result.is_err(),
"expected a decrypt/integrity error, got Ok"
);
if let Err(e) = &result {
assert!(
matches!(e, dove_core::error::Error::Integrity),
"expected Error::Integrity, got {e:?}"
);
}
let events = progress.events.borrow();
let from_idx = events
.iter()
.position(|e| e == "field:from=Alex")
.unwrap_or_else(|| panic!("no 'from' field reported: {events:?}"));
let message_idx = events
.iter()
.position(|e| e == "field:message=the codes")
.unwrap_or_else(|| panic!("no 'message' field reported: {events:?}"));
let first_bytes_idx = events
.iter()
.position(|e| e == "bytes")
.unwrap_or_else(|| panic!("download never reported any bytes: {events:?}"));
assert!(
from_idx < first_bytes_idx,
"the 'from' trust field must be reported before any download bytes: {events:?}"
);
assert!(
message_idx < first_bytes_idx,
"the 'message' trust field must be reported before any download bytes: {events:?}"
);
}