use super::*;
use crate::transport::test_transport::{self, Outcome};
use loonfs_api::v0::ObjectTransferAccess;
use loonfs_api::{CapabilityDocument, ContentId, ContentRef, PROFILE_CORE_V0, PROTOCOL_VERSION};
use std::collections::BTreeMap;
const DEFAULT_PROXY_CAP_BYTES: u64 = 256 * 1024 * 1024;
const AUDIT_FILE_BYTES: u64 = 300 * 1024 * 1024;
fn client() -> Client {
Client::new(ClientConfig {
server_url: "http://example.invalid".to_owned(),
auth_token: None,
request_timeout_ms: None,
disable_transient_retry: false,
ca_cert_path: None,
})
.expect("valid client config")
}
fn capabilities(direct_get: bool, proxy_cap_bytes: Option<u64>) -> Outcome {
let document = CapabilityDocument {
protocol_version: PROTOCOL_VERSION.to_owned(),
profiles: vec![PROFILE_CORE_V0.to_owned()],
features: BTreeMap::from([(FEATURE_DOWNLOADS_DIRECT_GET.to_owned(), direct_get)]),
limits: proxy_cap_bytes
.map(|cap| BTreeMap::from([(LIMIT_DOWNLOAD_MAX_CONTENT_BYTES.to_owned(), cap)]))
.unwrap_or_default(),
};
Outcome::Success(serde_json::to_vec(&document).expect("serialize capability document"))
}
#[tokio::test]
async fn a_file_past_the_default_proxy_cap_takes_the_grant() {
let client = client();
let _guard = test_transport::script([capabilities(true, Some(DEFAULT_PROXY_CAP_BYTES))]);
assert!(client.offers_direct_download(AUDIT_FILE_BYTES).await);
assert!(!client.offers_direct_download(DEFAULT_PROXY_CAP_BYTES).await);
assert!(!client.offers_direct_download(1).await);
}
#[tokio::test]
async fn a_deployment_without_the_capability_never_takes_the_grant() {
let client = client();
let _guard = test_transport::script([capabilities(false, Some(DEFAULT_PROXY_CAP_BYTES))]);
assert!(!client.offers_direct_download(AUDIT_FILE_BYTES).await);
}
#[tokio::test]
async fn a_deployment_that_advertises_no_cap_stays_proxied() {
let client = client();
let _guard = test_transport::script([capabilities(true, None)]);
assert!(!client.offers_direct_download(AUDIT_FILE_BYTES).await);
}
fn grant(content_ref: ContentRef, url: &str) -> BeginDownloadResponse {
BeginDownloadResponse {
namespace_id: NamespaceId::parse("demo").expect("namespace id"),
absolute_path: AbsolutePath::parse("/big.bin").expect("absolute path"),
revision_no: RevisionNo(1),
content_ref,
access: ObjectTransferAccess::PresignedUrl {
method: "GET".to_owned(),
url: url.to_owned(),
headers: BTreeMap::new(),
expires_at_ms: 0,
},
}
}
#[tokio::test]
async fn a_streamed_read_is_refused_when_the_bytes_are_not_what_the_grant_named() {
let payload = b"the bytes the grant described".to_vec();
let served = b"something else entirely, and a different length".to_vec();
let content_ref = ContentRef::blob_v1(ContentId::generate(), &payload);
let client = client();
let _guard = test_transport::script([Outcome::Success(served)]);
let mut sink = Vec::new();
let error = client
.download_via_presigned_url(
&grant(content_ref, "http://example.invalid/object"),
&mut sink,
)
.await
.expect_err("bytes that are not the granted object");
assert!(
matches!(&error, ClientError::Http(message) if message.contains("grant named")),
"unexpected error: {error}"
);
}
#[tokio::test]
async fn a_streamed_read_writes_the_granted_object_and_reports_its_length() {
let payload = b"exactly the bytes the grant described".to_vec();
let content_ref = ContentRef::blob_v1(ContentId::generate(), &payload);
let client = client();
let _guard = test_transport::script([Outcome::Success(payload.clone())]);
let mut sink = Vec::new();
let written = client
.download_via_presigned_url(
&grant(content_ref, "http://example.invalid/object"),
&mut sink,
)
.await
.expect("granted object");
assert_eq!(written, payload.len() as u64);
assert_eq!(sink, payload);
}
#[tokio::test]
async fn a_resumed_download_asks_for_the_rest_and_verifies_the_whole_file() {
let payload = b"the first half and then the second half".to_vec();
let held = 10;
let content_ref = ContentRef::blob_v1(ContentId::generate(), &payload);
let client = client();
let guard = test_transport::script([Outcome::Success(payload[held..].to_vec())]);
let mut download = client
.open_direct_download_at(
&grant(content_ref, "http://example.invalid/object"),
held as u64,
)
.await
.expect("resumed grant");
download.fold_resumed_prefix(&payload[..held]);
let mut received = Vec::new();
while let Some(chunk) = download.next_chunk().await.expect("chunk") {
received.extend_from_slice(&chunk);
}
assert_eq!(
received,
payload[held..],
"only the bytes past the resume point arrive"
);
let sent = guard.sent();
assert_eq!(sent.len(), 1);
assert_eq!(
sent[0].header("range"),
Some("bytes=10-"),
"the rest is asked for by range: {sent:?}"
);
}
#[tokio::test]
async fn a_resume_is_refused_until_it_accounts_for_what_it_holds() {
let payload = b"a whole object".to_vec();
let content_ref = ContentRef::blob_v1(ContentId::generate(), &payload);
let client = client();
let guard = test_transport::script([Outcome::Success(payload.clone())]);
let mut whole = client
.open_direct_download(&grant(content_ref.clone(), "http://example.invalid/object"))
.await
.expect("grant");
while whole.next_chunk().await.expect("chunk").is_some() {}
assert_eq!(
guard.sent()[0].header("range"),
None,
"a download of the whole object names no range"
);
drop(guard);
let _guard = test_transport::script([Outcome::Success(payload[4..].to_vec())]);
let mut resumed = client
.open_direct_download_at(&grant(content_ref, "http://example.invalid/object"), 4)
.await
.expect("resumed grant");
let error = resumed
.next_chunk()
.await
.expect_err("the skipped bytes are still owed");
assert!(
matches!(&error, ClientError::Http(message) if message.contains("resumed at offset 4")),
"unexpected error: {error}"
);
}
#[tokio::test]
async fn a_grant_that_does_not_authorize_a_read_is_refused_before_any_request() {
let payload = b"unused".to_vec();
let content_ref = ContentRef::blob_v1(ContentId::generate(), &payload);
let mut grant = grant(content_ref, "http://example.invalid/object");
let ObjectTransferAccess::PresignedUrl { method, .. } = &mut grant.access;
*method = "PUT".to_owned();
let mut sink = Vec::new();
let error = client()
.download_via_presigned_url(&grant, &mut sink)
.await
.expect_err("a write capability cannot serve a read");
assert!(
matches!(&error, ClientError::Http(message) if message.contains("presigned download method")),
"unexpected error: {error}"
);
assert!(sink.is_empty());
}