#![allow(dead_code)]
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
pub const PROFILE_NAME: &str = "s7cmd-e2e-test";
pub fn s7cmd_cmd() -> Command {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_s7cmd"));
cmd.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
cmd
}
pub fn run(cmd: &mut Command) -> (Option<i32>, String, String) {
let output = cmd.output().expect("failed to spawn s7cmd binary");
(
output.status.code(),
String::from_utf8_lossy(&output.stdout).to_string(),
String::from_utf8_lossy(&output.stderr).to_string(),
)
}
pub fn generate_bucket_name() -> String {
format!("s7cmd-e2e-{}", uuid::Uuid::new_v4())
}
pub fn create_temp_dir() -> PathBuf {
let dir = PathBuf::from(format!("./playground/tmp_{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&dir).expect("create_temp_dir");
dir
}
pub fn create_test_file(dir: &Path, name: &str, body: &[u8]) -> PathBuf {
let path = dir.join(name);
std::fs::write(&path, body).expect("create_test_file");
path
}
pub fn create_sized_file(dir: &Path, name: &str, size: usize) -> PathBuf {
let path = dir.join(name);
std::fs::write(&path, vec![0u8; size]).expect("create_sized_file");
path
}
#[cfg(e2e_test)]
#[allow(unused_imports)]
pub use e2e::{REGION, TestHelper};
#[cfg(e2e_test)]
mod e2e {
use super::PROFILE_NAME;
use aws_config::BehaviorVersion;
use aws_config::meta::region::{ProvideRegion, RegionProviderChain};
use aws_sdk_s3::Client;
use aws_sdk_s3::config::Region;
use aws_sdk_s3::operation::get_object_tagging::GetObjectTaggingOutput;
use aws_sdk_s3::types::{
BucketLocationConstraint, BucketVersioningStatus, CreateBucketConfiguration, Tag, Tagging,
VersioningConfiguration,
};
pub const REGION: &str = "ap-northeast-1";
pub struct TestHelper {
pub client: Client,
}
impl TestHelper {
pub async fn new() -> Self {
Self {
client: Self::create_client().await,
}
}
async fn create_client() -> Client {
let cfg = aws_config::defaults(BehaviorVersion::latest())
.credentials_provider(
aws_config::profile::ProfileFileCredentialsProvider::builder()
.profile_name(PROFILE_NAME)
.build(),
)
.region(Self::region_provider())
.load()
.await;
Client::new(&cfg)
}
fn region_provider() -> Box<dyn ProvideRegion> {
let env_region = std::env::var("S7CMD_E2E_REGION").ok().map(Region::new);
let profile_region = aws_config::profile::ProfileFileRegionProvider::builder()
.profile_name(PROFILE_NAME)
.build();
let chain = RegionProviderChain::first_try(env_region)
.or_else(profile_region)
.or_else(REGION);
Box::new(chain)
}
pub async fn create_bucket(&self, bucket: &str, region: &str) {
let constraint = BucketLocationConstraint::from(region);
let cfg = CreateBucketConfiguration::builder()
.location_constraint(constraint)
.build();
self.client
.create_bucket()
.create_bucket_configuration(cfg)
.bucket(bucket)
.send()
.await
.expect("create_bucket");
}
pub async fn is_bucket_exist(&self, bucket: &str) -> bool {
let head = self.client.head_bucket().bucket(bucket).send().await;
match head {
Ok(_) => true,
Err(e) => !e.into_service_error().is_not_found(),
}
}
pub async fn delete_bucket_with_cascade(&self, bucket: &str) {
if !self.is_bucket_exist(bucket).await {
return;
}
self.delete_all_objects(bucket).await;
self.delete_all_object_versions(bucket).await;
let _ = self.client.delete_bucket().bucket(bucket).send().await;
}
pub async fn put_object(&self, bucket: &str, key: &str, body: Vec<u8>) {
self.client
.put_object()
.bucket(bucket)
.key(key)
.body(body.into())
.send()
.await
.expect("put_object");
}
pub async fn put_object_with_tagging(
&self,
bucket: &str,
key: &str,
body: Vec<u8>,
tagging: &str,
) {
self.client
.put_object()
.bucket(bucket)
.key(key)
.tagging(tagging)
.body(body.into())
.send()
.await
.expect("put_object_with_tagging");
}
pub async fn get_object_bytes(
&self,
bucket: &str,
key: &str,
version_id: Option<String>,
) -> Vec<u8> {
let out = self
.client
.get_object()
.bucket(bucket)
.key(key)
.set_version_id(version_id)
.send()
.await
.expect("get_object");
out.body
.collect()
.await
.expect("collect body")
.into_bytes()
.to_vec()
}
pub async fn get_object_tagging(
&self,
bucket: &str,
key: &str,
version_id: Option<String>,
) -> GetObjectTaggingOutput {
self.client
.get_object_tagging()
.bucket(bucket)
.key(key)
.set_version_id(version_id)
.send()
.await
.expect("get_object_tagging")
}
pub async fn put_object_returning_version_id(
&self,
bucket: &str,
key: &str,
body: Vec<u8>,
) -> String {
let out = self
.client
.put_object()
.bucket(bucket)
.key(key)
.body(body.into())
.send()
.await
.expect("put_object");
out.version_id()
.expect("PutObject must return VersionId on a versioned bucket")
.to_string()
}
pub async fn is_object_exist(
&self,
bucket: &str,
key: &str,
version_id: Option<String>,
) -> bool {
let req = self
.client
.head_object()
.bucket(bucket)
.key(key)
.set_version_id(version_id);
match req.send().await {
Ok(_) => true,
Err(e) => !e.into_service_error().is_not_found(),
}
}
pub async fn delete_object(&self, bucket: &str, key: &str, version_id: Option<String>) {
self.client
.delete_object()
.bucket(bucket)
.key(key)
.set_version_id(version_id)
.send()
.await
.expect("delete_object");
}
pub async fn delete_all_objects(&self, bucket: &str) {
let Ok(list) = self.client.list_objects_v2().bucket(bucket).send().await else {
return;
};
for obj in list.contents() {
if let Some(key) = obj.key() {
self.delete_object(bucket, key, None).await;
}
}
}
pub async fn delete_all_object_versions(&self, bucket: &str) {
let Ok(out) = self
.client
.list_object_versions()
.bucket(bucket)
.send()
.await
else {
return;
};
for v in out.versions() {
if let Some(key) = v.key() {
self.delete_object(bucket, key, v.version_id().map(str::to_string))
.await;
}
}
for m in out.delete_markers() {
if let Some(key) = m.key() {
self.delete_object(bucket, key, m.version_id().map(str::to_string))
.await;
}
}
}
pub async fn put_object_tagging(&self, bucket: &str, key: &str, tags: &[(&str, &str)]) {
let tag_set: Vec<Tag> = tags
.iter()
.map(|(k, v)| Tag::builder().key(*k).value(*v).build().unwrap())
.collect();
let tagging = Tagging::builder()
.set_tag_set(Some(tag_set))
.build()
.unwrap();
self.client
.put_object_tagging()
.bucket(bucket)
.key(key)
.tagging(tagging)
.send()
.await
.expect("put_object_tagging");
}
pub async fn put_bucket_tagging(&self, bucket: &str, tags: &[(&str, &str)]) {
let tag_set: Vec<Tag> = tags
.iter()
.map(|(k, v)| Tag::builder().key(*k).value(*v).build().unwrap())
.collect();
let tagging = Tagging::builder()
.set_tag_set(Some(tag_set))
.build()
.unwrap();
self.client
.put_bucket_tagging()
.bucket(bucket)
.tagging(tagging)
.send()
.await
.expect("put_bucket_tagging");
}
pub async fn put_bucket_policy(&self, bucket: &str, policy_json: &str) {
self.client
.put_bucket_policy()
.bucket(bucket)
.policy(policy_json)
.send()
.await
.expect("put_bucket_policy");
}
pub async fn enable_bucket_versioning(&self, bucket: &str) {
let cfg = VersioningConfiguration::builder()
.status(BucketVersioningStatus::Enabled)
.build();
self.client
.put_bucket_versioning()
.bucket(bucket)
.versioning_configuration(cfg)
.send()
.await
.expect("enable_bucket_versioning");
}
pub async fn abort_all_multipart_uploads(&self, bucket: &str) {
let Ok(list) = self
.client
.list_multipart_uploads()
.bucket(bucket)
.send()
.await
else {
return;
};
for upload in list.uploads() {
if let (Some(key), Some(upload_id)) = (upload.key(), upload.upload_id()) {
let _ = self
.client
.abort_multipart_upload()
.bucket(bucket)
.key(key)
.upload_id(upload_id)
.send()
.await;
}
}
}
}
}