| // 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(()) |
| } |