blob: feee7c73079c13e2f5b702b9cd71e3c6b5d37147 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use std::str::FromStr;
use std::time::Duration;
use futures::AsyncReadExt;
use futures::TryStreamExt;
use http::StatusCode;
use reqwest::Url;
use tokio::time::sleep;
use crate::*;
pub fn tests(op: &Operator, tests: &mut Vec<Trial>) {
let cap = op.info().capability();
if cap.read && cap.write {
tests.extend(async_trials!(
op,
test_read_full,
test_read_range,
test_reader,
test_buffer_stream_metadata,
test_buffer_stream_metadata_with_concurrent,
test_futures_bytes_stream_metadata,
test_futures_bytes_stream_metadata_with_concurrent,
test_reader_with_if_match,
test_reader_with_if_none_match,
test_reader_with_if_modified_since,
test_reader_with_if_unmodified_since,
test_read_not_exist,
test_read_with_if_match,
test_read_with_if_none_match,
test_read_with_if_modified_since,
test_read_with_if_unmodified_since,
test_read_with_dir_path,
test_read_with_special_chars,
test_read_with_override_cache_control,
test_read_with_override_content_disposition,
test_read_with_override_content_type,
test_read_with_version,
test_read_with_not_existing_version
))
}
if cap.read && cap.write && cap.read_with_suffix {
tests.extend(async_trials!(
op,
test_read_suffix,
test_read_suffix_larger_than_file,
test_read_suffix_zero,
test_reader_suffix_with_chunk
))
}
if cap.read && !cap.write {
tests.extend(async_trials!(
op,
test_read_only_read_full,
test_read_only_read_full_with_special_chars,
test_read_only_read_with_range,
test_read_only_read_not_exist,
test_read_only_read_with_dir_path,
test_read_only_read_with_if_match,
test_read_only_read_with_if_none_match,
test_reader_only_read_with_if_match,
test_reader_only_read_with_if_none_match
))
}
if cap.read && !cap.write && cap.read_with_suffix {
tests.extend(async_trials!(op, test_read_only_read_with_suffix))
}
}
/// Read full content should match.
pub async fn test_read_full(op: Operator) -> anyhow::Result<()> {
let (path, content, size) = TEST_FIXTURE.new_file(op.clone());
op.write(&path, content.clone())
.await
.expect("write must succeed");
let bs = op.read(&path).await?.to_bytes();
assert_eq!(size, bs.len(), "read size");
assert_eq!(sha256_digest(&bs), sha256_digest(&content), "read content");
Ok(())
}
/// Read range content should match.
pub async fn test_read_range(op: Operator) -> anyhow::Result<()> {
let (path, content, size) = TEST_FIXTURE.new_file(op.clone());
let (offset, length) = gen_offset_length(size);
op.write(&path, content.clone())
.await
.expect("write must succeed");
let bs = op
.read_with(&path)
.range(offset..offset + length)
.await?
.to_bytes();
assert_eq!(bs.len() as u64, length, "read size");
assert_eq!(
sha256_digest(&bs),
sha256_digest(&content[offset as usize..(offset + length) as usize]),
"read content"
);
Ok(())
}
/// Read suffix content should match.
pub async fn test_read_suffix(op: Operator) -> anyhow::Result<()> {
let path = TEST_FIXTURE.new_file_path();
let content = gen_fixed_bytes(1024);
let suffix_size = 257;
op.write(&path, content.clone())
.await
.expect("write must succeed");
let bs = op
.read_with(&path)
.range(BytesRange::suffix(suffix_size as u64))
.await?
.to_bytes();
assert_eq!(bs.len(), suffix_size, "read size");
assert_eq!(
sha256_digest(&bs),
sha256_digest(&content[content.len() - suffix_size..]),
"read content"
);
Ok(())
}
/// Read suffix larger than the file should return the full content.
pub async fn test_read_suffix_larger_than_file(op: Operator) -> anyhow::Result<()> {
let path = TEST_FIXTURE.new_file_path();
let content = gen_fixed_bytes(1024);
op.write(&path, content.clone())
.await
.expect("write must succeed");
let bs = op
.read_with(&path)
.range(BytesRange::suffix((content.len() * 2) as u64))
.await?
.to_bytes();
assert_eq!(bs.len(), content.len(), "read size");
assert_eq!(sha256_digest(&bs), sha256_digest(&content), "read content");
Ok(())
}
/// Read zero suffix should return empty content.
pub async fn test_read_suffix_zero(op: Operator) -> anyhow::Result<()> {
let path = TEST_FIXTURE.new_file_path();
let content = gen_fixed_bytes(1024);
op.write(&path, content).await.expect("write must succeed");
let bs = op
.read_with(&path)
.range(BytesRange::suffix(0))
.await?
.to_bytes();
assert!(bs.is_empty(), "read content must be empty");
Ok(())
}
/// Reader suffix with chunk should still return the requested suffix content.
pub async fn test_reader_suffix_with_chunk(op: Operator) -> anyhow::Result<()> {
let path = TEST_FIXTURE.new_file_path();
let content = gen_fixed_bytes(4096);
let suffix_size = 1025;
op.write(&path, content.clone())
.await
.expect("write must succeed");
let reader = op.reader_with(&path).chunk(257).concurrent(3).await?;
let bs = reader
.read(BytesRange::suffix(suffix_size as u64))
.await?
.to_bytes();
assert_eq!(bs.len(), suffix_size, "read size");
assert_eq!(
sha256_digest(&bs),
sha256_digest(&content[content.len() - suffix_size..]),
"read content"
);
Ok(())
}
/// Read full content should match.
pub async fn test_reader(op: Operator) -> anyhow::Result<()> {
let (path, content, size) = TEST_FIXTURE.new_file(op.clone());
op.write(&path, content.clone())
.await
.expect("write must succeed");
// Reader.
let bs = op.reader(&path).await?.read(..).await?.to_bytes();
assert_eq!(size, bs.len(), "read size");
assert_eq!(sha256_digest(&bs), sha256_digest(&content), "read content");
// Bytes Stream
let bs = op
.reader(&path)
.await?
.into_bytes_stream(..)
.await?
.try_fold(Vec::new(), |mut acc, chunk| {
acc.extend_from_slice(&chunk);
async { Ok(acc) }
})
.await?;
assert_eq!(size, bs.len(), "read size");
assert_eq!(sha256_digest(&bs), sha256_digest(&content), "read content");
// Futures Reader
let mut futures_reader = op
.reader(&path)
.await?
.into_futures_async_read(0..size as u64)
.await?;
let mut bs = Vec::new();
futures_reader.read_to_end(&mut bs).await?;
assert_eq!(size, bs.len(), "read size");
assert_eq!(sha256_digest(&bs), sha256_digest(&content), "read content");
Ok(())
}
/// BufferStream should return complete object metadata before reading if supported.
pub async fn test_buffer_stream_metadata(op: Operator) -> anyhow::Result<()> {
let path = TEST_FIXTURE.new_file_path();
let content = gen_fixed_bytes(1024);
let start = 128;
let end = 640;
op.write(&path, content.clone())
.await
.expect("write must succeed");
let mut stream = op
.reader(&path)
.await?
.into_stream(start as u64..end as u64)
.await?;
match stream.metadata().await {
Ok(meta) => assert_eq!(
meta.content_length(),
content.len() as u64,
"metadata content length"
),
Err(err) if err.kind() == ErrorKind::Unsupported => {}
Err(err) => return Err(err.into()),
}
let bs: Vec<_> = stream.try_collect().await?;
let bs: Buffer = bs.into_iter().flatten().collect();
assert_eq!(bs.len(), end - start, "read size");
assert_eq!(
sha256_digest(bs.to_bytes()),
sha256_digest(&content[start..end]),
"read content"
);
Ok(())
}
/// BufferStream should return complete object metadata with concurrent reads if supported.
pub async fn test_buffer_stream_metadata_with_concurrent(op: Operator) -> anyhow::Result<()> {
let path = TEST_FIXTURE.new_file_path();
let content = gen_fixed_bytes(1024);
let start = 128;
let end = 640;
op.write(&path, content.clone())
.await
.expect("write must succeed");
let mut stream = op
.reader_with(&path)
.chunk(128)
.concurrent(4)
.await?
.into_stream(start as u64..end as u64)
.await?;
match stream.metadata().await {
Ok(meta) => assert_eq!(
meta.content_length(),
content.len() as u64,
"metadata content length"
),
Err(err) if err.kind() == ErrorKind::Unsupported => {}
Err(err) => return Err(err.into()),
}
let bs: Vec<_> = stream.try_collect().await?;
let bs: Buffer = bs.into_iter().flatten().collect();
assert_eq!(bs.len(), end - start, "read size");
assert_eq!(
sha256_digest(bs.to_bytes()),
sha256_digest(&content[start..end]),
"read content"
);
Ok(())
}
/// FuturesBytesStream should return complete object metadata before reading if supported.
pub async fn test_futures_bytes_stream_metadata(op: Operator) -> anyhow::Result<()> {
let path = TEST_FIXTURE.new_file_path();
let content = gen_fixed_bytes(1024);
let start = 128;
let end = 640;
op.write(&path, content.clone())
.await
.expect("write must succeed");
let mut stream = op
.reader(&path)
.await?
.into_bytes_stream(start as u64..end as u64)
.await?;
match stream.metadata().await {
Ok(meta) => assert_eq!(
meta.content_length(),
content.len() as u64,
"metadata content length"
),
Err(err) if err.kind() == ErrorKind::Unsupported => {}
Err(err) => return Err(err.into()),
}
let bs = stream
.try_fold(Vec::new(), |mut acc, chunk| {
acc.extend_from_slice(&chunk);
async { Ok(acc) }
})
.await?;
assert_eq!(bs.len(), end - start, "read size");
assert_eq!(
sha256_digest(&bs),
sha256_digest(&content[start..end]),
"read content"
);
Ok(())
}
/// FuturesBytesStream should return complete object metadata with concurrent reads if supported.
pub async fn test_futures_bytes_stream_metadata_with_concurrent(
op: Operator,
) -> anyhow::Result<()> {
let path = TEST_FIXTURE.new_file_path();
let content = gen_fixed_bytes(1024);
let start = 128;
let end = 640;
op.write(&path, content.clone())
.await
.expect("write must succeed");
let mut stream = op
.reader_with(&path)
.chunk(128)
.concurrent(4)
.await?
.into_bytes_stream(start as u64..end as u64)
.await?;
match stream.metadata().await {
Ok(meta) => assert_eq!(
meta.content_length(),
content.len() as u64,
"metadata content length"
),
Err(err) if err.kind() == ErrorKind::Unsupported => {}
Err(err) => return Err(err.into()),
}
let bs = stream
.try_fold(Vec::new(), |mut acc, chunk| {
acc.extend_from_slice(&chunk);
async { Ok(acc) }
})
.await?;
assert_eq!(bs.len(), end - start, "read size");
assert_eq!(
sha256_digest(&bs),
sha256_digest(&content[start..end]),
"read content"
);
Ok(())
}
/// Read not exist file should return NotFound
pub async fn test_read_not_exist(op: Operator) -> anyhow::Result<()> {
let path = uuid::Uuid::new_v4().to_string();
let bs = op.read(&path).await;
assert!(bs.is_err());
assert_eq!(bs.unwrap_err().kind(), ErrorKind::NotFound);
Ok(())
}
/// Reader with if_match should match, else get a ConditionNotMatch error.
pub async fn test_reader_with_if_match(op: Operator) -> anyhow::Result<()> {
if !op.info().capability().read_with_if_match {
return Ok(());
}
let (path, content, _) = TEST_FIXTURE.new_file(op.clone());
op.write(&path, content.clone())
.await
.expect("write must succeed");
let meta = op.stat(&path).await?;
let reader = op.reader_with(&path).if_match("\"invalid_etag\"").await?;
let res = reader.read(..).await;
assert!(res.is_err());
assert_eq!(res.unwrap_err().kind(), ErrorKind::ConditionNotMatch);
let reader = op
.reader_with(&path)
.if_match(meta.etag().expect("etag must exist"))
.await?;
let bs = reader.read(..).await.expect("read must succeed").to_bytes();
assert_eq!(bs, content);
Ok(())
}
/// Read with if_match should match, else get a ConditionNotMatch error.
pub async fn test_read_with_if_match(op: Operator) -> anyhow::Result<()> {
if !op.info().capability().read_with_if_match {
return Ok(());
}
let (path, content, _) = TEST_FIXTURE.new_file(op.clone());
op.write(&path, content.clone())
.await
.expect("write must succeed");
let meta = op.stat(&path).await?;
let res = op.read_with(&path).if_match("\"invalid_etag\"").await;
assert!(res.is_err());
assert_eq!(res.unwrap_err().kind(), ErrorKind::ConditionNotMatch);
let bs = op
.read_with(&path)
.if_match(meta.etag().expect("etag must exist"))
.await
.expect("read must succeed")
.to_bytes();
assert_eq!(bs, content);
Ok(())
}
/// Reader with if_none_match should match, else get a ConditionNotMatch error.
pub async fn test_reader_with_if_none_match(op: Operator) -> anyhow::Result<()> {
if !op.info().capability().read_with_if_none_match {
return Ok(());
}
let (path, content, _) = TEST_FIXTURE.new_file(op.clone());
op.write(&path, content.clone())
.await
.expect("write must succeed");
let meta = op.stat(&path).await?;
let reader = op
.reader_with(&path)
.if_none_match(meta.etag().expect("etag must exist"))
.await?;
let res = reader.read(..).await;
assert!(res.is_err());
assert_eq!(res.unwrap_err().kind(), ErrorKind::ConditionNotMatch);
let reader = op
.reader_with(&path)
.if_none_match("\"invalid_etag\"")
.await?;
let bs = reader.read(..).await.expect("read must succeed").to_bytes();
assert_eq!(bs, content);
Ok(())
}
/// Reader with if_modified_since should match, otherwise, a ConditionNotMatch error will be returned.
pub async fn test_reader_with_if_modified_since(op: Operator) -> anyhow::Result<()> {
if !op.info().capability().read_with_if_modified_since {
return Ok(());
}
let (path, content, _) = TEST_FIXTURE.new_file(op.clone());
op.write(&path, content.clone())
.await
.expect("write must succeed");
let last_modified_time = op.stat(&path).await?.last_modified().unwrap();
let since = last_modified_time - Duration::from_secs(1);
let reader = op.reader_with(&path).if_modified_since(since).await?;
let bs = reader.read(..).await?.to_bytes();
assert_eq!(bs, content);
sleep(Duration::from_secs(1)).await;
let since = last_modified_time + Duration::from_secs(1);
let reader = op.reader_with(&path).if_modified_since(since).await?;
let res = reader.read(..).await;
assert!(res.is_err());
assert_eq!(res.unwrap_err().kind(), ErrorKind::ConditionNotMatch);
Ok(())
}
/// Reader with if_unmodified_since should match, otherwise, a ConditionNotMatch error will be returned.
pub async fn test_reader_with_if_unmodified_since(op: Operator) -> anyhow::Result<()> {
if !op.info().capability().read_with_if_unmodified_since {
return Ok(());
}
let (path, content, _) = TEST_FIXTURE.new_file(op.clone());
op.write(&path, content.clone())
.await
.expect("write must succeed");
let last_modified_time = op.stat(&path).await?.last_modified().unwrap();
let since = last_modified_time - Duration::from_secs(1);
let reader = op.reader_with(&path).if_unmodified_since(since).await?;
let res = reader.read(..).await;
assert!(res.is_err());
assert_eq!(res.unwrap_err().kind(), ErrorKind::ConditionNotMatch);
sleep(Duration::from_secs(1)).await;
let since = last_modified_time + Duration::from_secs(1);
let reader = op.reader_with(&path).if_unmodified_since(since).await?;
let bs = reader.read(..).await?.to_bytes();
assert_eq!(bs, content);
Ok(())
}
/// Read with if_none_match should match, else get a ConditionNotMatch error.
pub async fn test_read_with_if_none_match(op: Operator) -> anyhow::Result<()> {
if !op.info().capability().read_with_if_none_match {
return Ok(());
}
let (path, content, _) = TEST_FIXTURE.new_file(op.clone());
op.write(&path, content.clone())
.await
.expect("write must succeed");
let meta = op.stat(&path).await?;
let res = op
.read_with(&path)
.if_none_match(meta.etag().expect("etag must exist"))
.await;
assert!(res.is_err());
assert_eq!(res.unwrap_err().kind(), ErrorKind::ConditionNotMatch);
let bs = op
.read_with(&path)
.if_none_match("\"invalid_etag\"")
.await
.expect("read must succeed")
.to_bytes();
assert_eq!(bs, content);
Ok(())
}
/// Read with dir path should return an error.
pub async fn test_read_with_dir_path(op: Operator) -> anyhow::Result<()> {
if !op.info().capability().create_dir {
return Ok(());
}
let path = TEST_FIXTURE.new_dir_path();
op.create_dir(&path).await.expect("write must succeed");
let result = op.read(&path).await;
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), ErrorKind::IsADirectory);
Ok(())
}
/// Read file with special chars should succeed.
pub async fn test_read_with_special_chars(op: Operator) -> anyhow::Result<()> {
let path = format!("{} !@#$%^&()_+-=;',.txt", uuid::Uuid::new_v4());
let (path, content, size) = TEST_FIXTURE.new_file_with_path(op.clone(), &path);
op.write(&path, content.clone())
.await
.expect("write must succeed");
let bs = op.read(&path).await?.to_bytes();
assert_eq!(size, bs.len(), "read size");
assert_eq!(sha256_digest(&bs), sha256_digest(&content), "read content");
Ok(())
}
/// Read file with override-cache-control should succeed.
pub async fn test_read_with_override_cache_control(op: Operator) -> anyhow::Result<()> {
if !(op.info().capability().read_with_override_cache_control && op.info().capability().presign)
{
return Ok(());
}
let (path, content, _) = TEST_FIXTURE.new_file(op.clone());
op.write(&path, content.clone())
.await
.expect("write must succeed");
let target_cache_control = "no-cache, no-store, must-revalidate";
let signed_req = op
.presign_read_with(&path, Duration::from_secs(60))
.override_cache_control(target_cache_control)
.await
.expect("sign must succeed");
let client = reqwest::Client::new();
let mut req = client.request(
signed_req.method().clone(),
Url::from_str(&signed_req.uri().to_string()).expect("must be valid url"),
);
for (k, v) in signed_req.header() {
req = req.header(k, v);
}
let resp = req.send().await.expect("send must succeed");
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers()
.get("cache-control")
.expect("cache-control header must exist")
.to_str()
.expect("cache-control header must be string"),
target_cache_control
);
Ok(())
}
/// Read file with override_content_disposition should succeed.
pub async fn test_read_with_override_content_disposition(op: Operator) -> anyhow::Result<()> {
if !(op
.info()
.capability()
.read_with_override_content_disposition
&& op.info().capability().presign)
{
return Ok(());
}
let (path, content, _) = TEST_FIXTURE.new_file(op.clone());
op.write(&path, content.clone())
.await
.expect("write must succeed");
let target_content_disposition = "attachment; filename=foo.txt";
let signed_req = op
.presign_read_with(&path, Duration::from_secs(60))
.override_content_disposition(target_content_disposition)
.await
.expect("presign must succeed");
let client = reqwest::Client::new();
let mut req = client.request(
signed_req.method().clone(),
Url::from_str(&signed_req.uri().to_string()).expect("must be valid url"),
);
for (k, v) in signed_req.header() {
req = req.header(k, v);
}
let resp = req.send().await.expect("send must succeed");
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers()
.get(http::header::CONTENT_DISPOSITION)
.expect("content-disposition header must exist")
.to_str()
.expect("content-disposition header must be string"),
target_content_disposition
);
assert_eq!(resp.bytes().await?, content);
Ok(())
}
/// Read file with override_content_type should succeed.
pub async fn test_read_with_override_content_type(op: Operator) -> anyhow::Result<()> {
if !(op.info().capability().read_with_override_content_type && op.info().capability().presign) {
return Ok(());
}
let (path, content, _) = TEST_FIXTURE.new_file(op.clone());
op.write(&path, content.clone())
.await
.expect("write must succeed");
let target_content_type = "application/opendal";
let signed_req = op
.presign_read_with(&path, Duration::from_secs(60))
.override_content_type(target_content_type)
.await
.expect("presign must succeed");
let client = reqwest::Client::new();
let mut req = client.request(
signed_req.method().clone(),
Url::from_str(&signed_req.uri().to_string()).expect("must be valid url"),
);
for (k, v) in signed_req.header() {
req = req.header(k, v);
}
let resp = req.send().await.expect("send must succeed");
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers()
.get(http::header::CONTENT_TYPE)
.expect("content-type header must exist")
.to_str()
.expect("content-type header must be string"),
target_content_type
);
assert_eq!(resp.bytes().await?, content);
Ok(())
}
/// Read with if_modified_since should match, otherwise, a ConditionNotMatch error will be returned.
pub async fn test_read_with_if_modified_since(op: Operator) -> anyhow::Result<()> {
if !op.info().capability().read_with_if_modified_since {
return Ok(());
}
let (path, content, _) = TEST_FIXTURE.new_file(op.clone());
op.write(&path, content.clone())
.await
.expect("write must succeed");
let last_modified_time = op.stat(&path).await?.last_modified().unwrap();
let since = last_modified_time - Duration::from_secs(1);
let bs = op
.read_with(&path)
.if_modified_since(since)
.await?
.to_bytes();
assert_eq!(bs, content);
sleep(Duration::from_secs(1)).await;
let since = last_modified_time + Duration::from_secs(1);
let res = op.read_with(&path).if_modified_since(since).await;
assert!(res.is_err());
assert_eq!(res.unwrap_err().kind(), ErrorKind::ConditionNotMatch);
Ok(())
}
/// Read with if_unmodified_since should match, otherwise, a ConditionNotMatch error will be returned.
pub async fn test_read_with_if_unmodified_since(op: Operator) -> anyhow::Result<()> {
if !op.info().capability().read_with_if_unmodified_since {
return Ok(());
}
let (path, content, _) = TEST_FIXTURE.new_file(op.clone());
op.write(&path, content.clone())
.await
.expect("write must succeed");
let last_modified = op.stat(&path).await?.last_modified().unwrap();
let since = last_modified - Duration::from_secs(3600);
let res = op.read_with(&path).if_unmodified_since(since).await;
assert!(res.is_err());
assert_eq!(res.unwrap_err().kind(), ErrorKind::ConditionNotMatch);
sleep(Duration::from_secs(1)).await;
let since = last_modified + Duration::from_secs(1);
let bs = op
.read_with(&path)
.if_unmodified_since(since)
.await?
.to_bytes();
assert_eq!(bs, content);
Ok(())
}
/// Read full content should match.
pub async fn test_read_only_read_full(op: Operator) -> anyhow::Result<()> {
let bs = op.read("normal_file.txt").await?.to_bytes();
assert_eq!(bs.len(), 30482, "read size");
assert_eq!(
sha256_digest(&bs),
"943048ba817cdcd786db07d1f42d5500da7d10541c2f9353352cd2d3f66617e5",
"read content"
);
Ok(())
}
/// Read full content should match.
pub async fn test_read_only_read_full_with_special_chars(op: Operator) -> anyhow::Result<()> {
let bs = op
.read("special_file !@#$%^&()_+-=;',.txt")
.await?
.to_bytes();
assert_eq!(bs.len(), 30482, "read size");
assert_eq!(
sha256_digest(&bs),
"943048ba817cdcd786db07d1f42d5500da7d10541c2f9353352cd2d3f66617e5",
"read content"
);
Ok(())
}
/// Read full content should match.
pub async fn test_read_only_read_with_range(op: Operator) -> anyhow::Result<()> {
let bs = op
.read_with("normal_file.txt")
.range(1024..2048)
.await?
.to_bytes();
assert_eq!(bs.len(), 1024, "read size");
assert_eq!(
sha256_digest(&bs),
"330c6d57fdc1119d6021b37714ca5ad0ede12edd484f66be799a5cff59667034",
"read content"
);
Ok(())
}
/// Read suffix content should match.
pub async fn test_read_only_read_with_suffix(op: Operator) -> anyhow::Result<()> {
let bs = op
.read_with("normal_file.txt")
.range(BytesRange::suffix(1024))
.await?
.to_bytes();
assert_eq!(bs.len(), 1024, "read size");
assert_eq!(
sha256_digest(&bs),
"cc9312c869238ea9410b6716e0fc3f48056f2bfb2fe06ccf5f96f2c3bf39e71b",
"read content"
);
Ok(())
}
/// Read not exist file should return NotFound
pub async fn test_read_only_read_not_exist(op: Operator) -> anyhow::Result<()> {
let path = uuid::Uuid::new_v4().to_string();
let bs = op.read(&path).await;
assert!(bs.is_err());
assert_eq!(bs.unwrap_err().kind(), ErrorKind::NotFound);
Ok(())
}
/// Read with dir path should return an error.
pub async fn test_read_only_read_with_dir_path(op: Operator) -> anyhow::Result<()> {
let path = format!("{}/", uuid::Uuid::new_v4());
let result = op.read(&path).await;
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), ErrorKind::IsADirectory);
Ok(())
}
/// Reader with if_match should match, else get a ConditionNotMatch error.
pub async fn test_reader_only_read_with_if_match(op: Operator) -> anyhow::Result<()> {
if !op.info().capability().read_with_if_match {
return Ok(());
}
let path = "normal_file.txt";
let meta = op.stat(path).await?;
let reader = op.reader_with(path).if_match("invalid_etag").await?;
let res = reader.read(..).await;
assert!(res.is_err());
assert_eq!(res.unwrap_err().kind(), ErrorKind::ConditionNotMatch);
let reader = op
.reader_with(path)
.if_match(meta.etag().expect("etag must exist"))
.await?;
let bs = reader.read(..).await.expect("read must succeed").to_bytes();
assert_eq!(bs.len(), 30482, "read size");
assert_eq!(
sha256_digest(&bs),
"943048ba817cdcd786db07d1f42d5500da7d10541c2f9353352cd2d3f66617e5",
"read content"
);
Ok(())
}
/// Read with if_match should match, else get a ConditionNotMatch error.
pub async fn test_read_only_read_with_if_match(op: Operator) -> anyhow::Result<()> {
if !op.info().capability().read_with_if_match {
return Ok(());
}
let path = "normal_file.txt";
let meta = op.stat(path).await?;
let res = op.read_with(path).if_match("invalid_etag").await;
assert!(res.is_err());
assert_eq!(res.unwrap_err().kind(), ErrorKind::ConditionNotMatch);
let bs = op
.read_with(path)
.if_match(meta.etag().expect("etag must exist"))
.await
.expect("read must succeed")
.to_bytes();
assert_eq!(bs.len(), 30482, "read size");
assert_eq!(
sha256_digest(&bs),
"943048ba817cdcd786db07d1f42d5500da7d10541c2f9353352cd2d3f66617e5",
"read content"
);
Ok(())
}
/// Reader with if_none_match should match, else get a ConditionNotMatch error.
pub async fn test_reader_only_read_with_if_none_match(op: Operator) -> anyhow::Result<()> {
if !op.info().capability().read_with_if_none_match {
return Ok(());
}
let path = "normal_file.txt";
let meta = op.stat(path).await?;
let reader = op
.reader_with(path)
.if_none_match(meta.etag().expect("etag must exist"))
.await?;
let res = reader.read(..).await;
assert!(res.is_err());
assert_eq!(res.unwrap_err().kind(), ErrorKind::ConditionNotMatch);
let reader = op.reader_with(path).if_none_match("invalid_etag").await?;
let bs = reader.read(..).await.expect("read must succeed").to_bytes();
assert_eq!(bs.len(), 30482, "read size");
assert_eq!(
sha256_digest(&bs),
"943048ba817cdcd786db07d1f42d5500da7d10541c2f9353352cd2d3f66617e5",
"read content"
);
Ok(())
}
/// Read with if_none_match should match, else get a ConditionNotMatch error.
pub async fn test_read_only_read_with_if_none_match(op: Operator) -> anyhow::Result<()> {
if !op.info().capability().read_with_if_none_match {
return Ok(());
}
let path = "normal_file.txt";
let meta = op.stat(path).await?;
let res = op
.read_with(path)
.if_none_match(meta.etag().expect("etag must exist"))
.await;
assert!(res.is_err());
assert_eq!(res.unwrap_err().kind(), ErrorKind::ConditionNotMatch);
let bs = op
.read_with(path)
.if_none_match("invalid_etag")
.await
.expect("read must succeed")
.to_bytes();
assert_eq!(bs.len(), 30482, "read size");
assert_eq!(
sha256_digest(&bs),
"943048ba817cdcd786db07d1f42d5500da7d10541c2f9353352cd2d3f66617e5",
"read content"
);
Ok(())
}
pub async fn test_read_with_version(op: Operator) -> anyhow::Result<()> {
if !op.info().capability().read_with_version {
return Ok(());
}
let (path, content, _) = TEST_FIXTURE.new_file(op.clone());
op.write(path.as_str(), content.clone())
.await
.expect("write must success");
let meta = op.stat(path.as_str()).await.expect("stat must success");
let version = meta.version().expect("must have version");
let data = op
.read_with(path.as_str())
.version(version)
.await
.expect("read must success");
assert_eq!(content, data.to_vec());
op.write(path.as_str(), "1")
.await
.expect("write must success");
// After writing new data, we can still read the first version data
let second_data = op
.read_with(path.as_str())
.version(version)
.await
.expect("read must success");
assert_eq!(content, second_data.to_vec());
Ok(())
}
pub async fn test_read_with_not_existing_version(op: Operator) -> anyhow::Result<()> {
if !op.info().capability().read_with_version {
return Ok(());
}
// retrieve a valid version
let (path, content, _) = TEST_FIXTURE.new_file(op.clone());
op.write(path.as_str(), content.clone())
.await
.expect("write must success");
let version = op
.stat(path.as_str())
.await
.expect("stat must success")
.version()
.expect("must have version")
.to_string();
let (path, content, _) = TEST_FIXTURE.new_file(op.clone());
op.write(path.as_str(), content)
.await
.expect("write must success");
let ret = op.read_with(path.as_str()).version(&version).await;
assert!(ret.is_err());
assert_eq!(ret.unwrap_err().kind(), ErrorKind::NotFound);
Ok(())
}