Skip to content

Commit

Permalink
start add test service
Browse files Browse the repository at this point in the history
  • Loading branch information
robatipoor committed Feb 23, 2024
1 parent 0c1d2c1 commit a373861
Show file tree
Hide file tree
Showing 15 changed files with 132 additions and 58 deletions.
2 changes: 1 addition & 1 deletion api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ tracing = { workspace = true }
tracing-subscriber = { workspace = true }
url = { workspace = true }
image = { workspace = true }
garde = { workspace = true }
garde = { workspace = true }
41 changes: 36 additions & 5 deletions api/src/service/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,24 @@ const DEFAULT_BUF_SIZE: usize = 8192;

pub async fn store(
state: &ApiState,
query: &UploadQueryParam,
param: &UploadQueryParam,
secret: Option<Secret>,
mut multipart: Multipart,
) -> ApiResult<(FilePath, DateTime<Utc>)> {
let secret = secret.map(|s| s.hash()).transpose()?;
let expire_secs = query
let expire_secs = param
.expire_secs
.unwrap_or(state.config.default_expire_secs) as i64;
let now = Utc::now();
let expire_date_time = calc_expiration_date(now, expire_secs);
let mut code_length = query
let mut code_length = param
.code_length
.unwrap_or(state.config.default_code_length);
let meta = MetaDataFile {
created_at: now,
expire_date_time,
delete_manually: query.delete_manually.unwrap_or(true),
max_download: query.max_download,
delete_manually: param.delete_manually.unwrap_or(true),
max_download: param.max_download,
secret,
count_downloads: 0,
};
Expand Down Expand Up @@ -232,3 +232,34 @@ pub fn authorize_user(secret: Option<Secret>, secret_hash: &Option<SecretHash>)
pub fn calc_expiration_date(now: DateTime<Utc>, secs: i64) -> DateTime<Utc> {
now + chrono::Duration::seconds(secs)
}

#[cfg(test)]
mod tests {

use super::*;
use crate::{
assert_err,
util::{multipart::create_multipart_request, test::StateTestContext},
};

use test_context::test_context;

#[test_context(StateTestContext)]
#[tokio::test]
async fn test_delete_unprivileged_file(ctx: &mut StateTestContext) {
let param = UploadQueryParam {
max_download: None,
code_length: None,
expire_secs: None,
delete_manually: Some(false),
qr_code_format: None,
};
let multipart = create_multipart_request("file_name.txt", "data")
.await
.unwrap();
let (file_path, _) = store(&ctx.state, &param, None, multipart).await.unwrap();
let result = delete(&ctx.state, &file_path.code, &file_path.file_name, None).await;
assert_err!(result, |e: &ApiError| e.to_string()
== format!("{}/file_name.txt is not deletable", file_path.code));
}
}
20 changes: 20 additions & 0 deletions api/src/util/assert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#[macro_export]
macro_rules! assert_ok {
($result:expr) => {
assert!(
matches!($result, std::result::Result::Ok(_)),
"match failed: {:?}",
$result,
)
};
}

#[macro_export]
macro_rules! assert_err {
($result:expr $(, $closure:expr )?) => {
assert!(
matches!($result,std::result::Result::Err(ref _e) $( if $closure(_e) )?),
"match failed: {:?}",$result,
)
};
}
2 changes: 2 additions & 0 deletions api/src/util/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
pub mod assert;
pub mod file_name;
pub mod hash;
pub mod http;
pub mod multipart;
pub mod qr_code;
pub mod secret;
pub mod task;
Expand Down
14 changes: 14 additions & 0 deletions api/src/util/multipart.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use axum::{
body::{Body, Bytes},
extract::{FromRequest, Multipart, Request},
};
use hyper::header::CONTENT_TYPE;

pub async fn create_multipart_request(file_name: &str, data: &str) -> anyhow::Result<Multipart> {
let data = format!("--X-BOUNDARY\r\nContent-Disposition: form-data; name=\"file\"; filename=\"{file_name}\"\r\n\r\n{data}\r\n--X-BOUNDARY--\r\n");
let body = Body::from(Bytes::from(data));
let request = Request::builder()
.header(CONTENT_TYPE, "multipart/form-data; boundary=X-BOUNDARY")
.body(body)?;
Ok(Multipart::from_request(request, &()).await?)
}
5 changes: 5 additions & 0 deletions api/src/util/qr_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,9 @@ mod tests {
pub fn test_encode_to_text_format() {
let _qr_code = encode_to_text_format(&Faker.fake::<String>()).unwrap();
}

#[test]
pub fn test_encode_to_image_format() {
let _qr_code = encode_to_image_format(&Faker.fake::<String>()).unwrap();
}
}
33 changes: 0 additions & 33 deletions api/src/util/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,6 @@ use crate::{configure::CONFIG, util::tracing::INIT_SUBSCRIBER};
use once_cell::sync::Lazy;
use test_context::AsyncTestContext;

#[macro_export]
macro_rules! assert_ok {
($result:expr) => {
assert!(
matches!($result, sdk::dto::response::ApiResponseResult::Ok(_)),
"match failed: {:?}",
$result,
)
};
}

#[macro_export]
macro_rules! assert_err {
($result:expr $(, $closure:expr )?) => {
assert!(
matches!($result,sdk::dto::response::ApiResponseResult::Err(ref _e) $( if $closure(_e) )?),
"match failed: {:?}",$result,
)
};
}

#[macro_export]
macro_rules! unwrap {
($result:expr) => {
match $result {
sdk::dto::response::ApiResponseResult::Ok(resp) => resp,
sdk::dto::response::ApiResponseResult::Err(e) => {
panic!("called `util::unwrap!()` on an `Err` value {e:?}")
}
}
};
}

pub struct StateTestContext {
pub state: ApiState,
gc_task: tokio::task::JoinHandle<ApiResult>,
Expand Down
8 changes: 4 additions & 4 deletions api/tests/api/delete_api_test.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::helper::ApiTestContext;
use api::{assert_err, assert_ok};
use crate::{assert_response_err, assert_response_ok};
use fake::{Fake, Faker};
use sdk::dto::{response::BodyResponseError, FileUrlPath};
use test_context::test_context;
Expand All @@ -11,11 +11,11 @@ pub async fn test_delete_exist_file(ctx: &mut ApiTestContext) {
.upload_dummy_file(None, None, None, None, None, None)
.await;
let (status, resp) = ctx.info(&file.url_path, None).await.unwrap();
assert_ok!(resp);
assert_response_ok!(resp);
assert!(status.is_success(), "status: {status}");
ctx.delete(&file.url_path, None).await.unwrap();
let (status, resp) = ctx.info(&file.url_path, None).await.unwrap();
assert_err!(resp, |e: &BodyResponseError| e.error_type == "NOT_FOUND");
assert_response_err!(resp, |e: &BodyResponseError| e.error_type == "NOT_FOUND");
assert_eq!(status, reqwest::StatusCode::NOT_FOUND);
}

Expand All @@ -39,7 +39,7 @@ pub async fn test_delete_file_is_not_deletable(ctx: &mut ApiTestContext) {
let (status, _) = ctx.info(&file.url_path, None).await.unwrap();
assert!(status.is_success(), "status: {status}");
let (status, resp) = ctx.delete(&file.url_path, None).await.unwrap();
assert_err!(resp, |e: &BodyResponseError| e.error_type
assert_response_err!(resp, |e: &BodyResponseError| e.error_type
== "PERMISSION_DENIED");
assert!(!status.is_success(), "status: {status}");
let (status, _) = ctx.info(&file.url_path, None).await.unwrap();
Expand Down
8 changes: 4 additions & 4 deletions api/tests/api/download_api_test.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::time::Duration;

use api::{assert_err, unwrap};
use crate::{assert_response_err, unwrap};
use fake::{Fake, Faker};
use sdk::dto::response::BodyResponseError;
use test_context::test_context;
Expand Down Expand Up @@ -28,7 +28,7 @@ pub async fn test_download_when_file_exceed_max_dl(ctx: &mut ApiTestContext) {
let (status, _) = ctx.download(&file.url_path, None).await.unwrap();
assert!(status.is_success());
let (status, resp) = ctx.download(&file.url_path, None).await.unwrap();
assert_err!(resp, |e: &BodyResponseError| e.error_type == "NOT_FOUND");
assert_response_err!(resp, |e: &BodyResponseError| e.error_type == "NOT_FOUND");
assert!(!status.is_success(), "status: {status}");
}

Expand All @@ -42,7 +42,7 @@ pub async fn test_download_when_expired(ctx: &mut ApiTestContext) {
assert!(status.is_success());
tokio::time::sleep(Duration::from_secs(1)).await;
let (status, resp) = ctx.download(&file.url_path, None).await.unwrap();
assert_err!(resp, |e: &BodyResponseError| e.error_type == "NOT_FOUND");
assert_response_err!(resp, |e: &BodyResponseError| e.error_type == "NOT_FOUND");
assert!(!status.is_success(), "status: {status}");
}

Expand All @@ -54,7 +54,7 @@ pub async fn test_download_file_with_auth(ctx: &mut ApiTestContext) {
.upload_dummy_file(None, None, Some(1), None, None, auth.clone())
.await;
let (status, resp) = ctx.download(&file.url_path, None).await.unwrap();
assert_err!(resp, |e: &BodyResponseError| e.error_type
assert_response_err!(resp, |e: &BodyResponseError| e.error_type
== "PERMISSION_DENIED");
assert!(!status.is_success());
let (status, _) = ctx.download(&file.url_path, auth).await.unwrap();
Expand Down
4 changes: 2 additions & 2 deletions api/tests/api/healthz_api_test.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use api::assert_ok;
use crate::assert_response_ok;
use test_context::test_context;

use crate::helper::ApiTestContext;
Expand All @@ -7,6 +7,6 @@ use crate::helper::ApiTestContext;
#[tokio::test]
pub async fn test_health_check(ctx: &mut ApiTestContext) {
let (status, body) = ctx.health_check().await.unwrap();
assert_ok!(body);
assert_response_ok!(body);
assert!(status.is_success(), "status: {status}");
}
32 changes: 32 additions & 0 deletions api/tests/api/helper/assert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#[macro_export]
macro_rules! assert_response_ok {
($result:expr) => {
assert!(
matches!($result, sdk::dto::response::ApiResponseResult::Ok(_)),
"match failed: {:?}",
$result,
)
};
}

#[macro_export]
macro_rules! assert_response_err {
($result:expr $(, $closure:expr )?) => {
assert!(
matches!($result,sdk::dto::response::ApiResponseResult::Err(ref _e) $( if $closure(_e) )?),
"match failed: {:?}",$result,
)
};
}

#[macro_export]
macro_rules! unwrap {
($result:expr) => {
match $result {
sdk::dto::response::ApiResponseResult::Ok(resp) => resp,
sdk::dto::response::ApiResponseResult::Err(e) => {
panic!("called `util::unwrap!()` on an `Err` value {e:?}")
}
}
};
}
4 changes: 3 additions & 1 deletion api/tests/api/helper/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use std::ops::Deref;
use std::path::{Path, PathBuf};

use crate::unwrap;
use api::configure::CONFIG;
use api::error::ApiResult;
use api::server::worker::GarbageCollectorTask;
use api::server::{ApiServer, ApiState};
use api::unwrap;
use api::util::tracing::INIT_SUBSCRIBER;
use fake::{Fake, Faker};
use once_cell::sync::Lazy;
Expand All @@ -14,6 +14,8 @@ use sdk::dto::request::{QrCodeFormat, UploadQueryParam};
use sdk::dto::FileUrlPath;
use test_context::AsyncTestContext;

pub mod assert;

pub struct ApiTestContext {
pub state: ApiState,
pub workspace: PathBuf,
Expand Down
6 changes: 3 additions & 3 deletions api/tests/api/info_api_test.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::helper::ApiTestContext;
use api::{assert_err, unwrap};
use crate::{assert_response_err, unwrap};
use fake::{Fake, Faker};
use sdk::dto::{response::BodyResponseError, FileUrlPath};
use test_context::test_context;
Expand All @@ -21,7 +21,7 @@ pub async fn test_info(ctx: &mut ApiTestContext) {
pub async fn test_get_info_when_file_not_exist(ctx: &mut ApiTestContext) {
let url_path = Faker.fake::<FileUrlPath>();
let (status, resp) = ctx.info(&url_path, None).await.unwrap();
assert_err!(resp, |e: &BodyResponseError| e.error_type == "NOT_FOUND");
assert_response_err!(resp, |e: &BodyResponseError| e.error_type == "NOT_FOUND");
assert!(!status.is_success(), "status: {status}");
}

Expand All @@ -34,6 +34,6 @@ pub async fn test_get_info_when_file_exceed_max_dl(ctx: &mut ApiTestContext) {
let (status, _resp) = ctx.download(&file.url_path, None).await.unwrap();
assert!(status.is_success());
let (status, resp) = ctx.info(&file.url_path, None).await.unwrap();
assert_err!(resp, |e: &BodyResponseError| e.error_type == "NOT_FOUND");
assert_response_err!(resp, |e: &BodyResponseError| e.error_type == "NOT_FOUND");
assert!(!status.is_success(), "status: {status}");
}
6 changes: 3 additions & 3 deletions api/tests/api/upload_api_test.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use api::{assert_err, assert_ok};
use crate::{assert_response_err, assert_response_ok};
use sdk::dto::{request::UploadQueryParam, response::BodyResponseError};
use test_context::test_context;

Expand All @@ -15,7 +15,7 @@ pub async fn test_success_upload(ctx: &mut ApiTestContext) {
.upload(filename, content_type, &query, file, None)
.await
.unwrap();
assert_ok!(resp);
assert_response_ok!(resp);
assert!(status.is_success(), "status: {status}");
}

Expand All @@ -33,7 +33,7 @@ pub async fn test_upload_with_invalid_len_param_query(ctx: &mut ApiTestContext)
.upload(filename, content_type, &query, file, None)
.await
.unwrap();
assert_err!(resp, |e: &BodyResponseError| e.error_type
assert_response_err!(resp, |e: &BodyResponseError| e.error_type
== "INVALID_INPUT");
assert!(!status.is_success(), "status: {status}");
}
5 changes: 3 additions & 2 deletions sdk/src/dto/request.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use fake::Dummy;
use garde::Validate;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Validate, Default)]
#[derive(Debug, Serialize, Deserialize, Validate, Default, Dummy)]
pub struct UploadQueryParam {
#[garde(range(min = 1, max = 100_000_000))]
pub max_download: Option<u32>,
Expand All @@ -15,7 +16,7 @@ pub struct UploadQueryParam {
pub qr_code_format: Option<QrCodeFormat>,
}

#[derive(Debug, Serialize, Deserialize, Validate)]
#[derive(Debug, Serialize, Deserialize, Validate, Dummy)]
pub enum QrCodeFormat {
#[serde(rename = "text")]
Text,
Expand Down

0 comments on commit a373861

Please sign in to comment.