Skip to content

Commit 895821f

Browse files
committed
add service tests
1 parent b1c9199 commit 895821f

10 files changed

Lines changed: 228 additions & 20 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ $ curl -u username:password -F "file=@{file_name}" 127.0.0.1:8080/upload
6969
$ curl -o {file_name} -u username:password http://127.0.0.1:8080/{code}/{file_name}
7070

7171
# Upload a file and then display the QR code.
72-
$ curl -s -F "file=@{file_name}" 127.0.0.1:8080/upload\?qr_code_format=text | jq -r '.qr_code' | base64 -d; echo
72+
$ curl -s -F "file=@{file_name}" 127.0.0.1:8080/upload\?qr_code_format=text \
73+
| jq -r '.qr_code' | base64 -d; echo
7374

7475
# Upload a file with an expiration time of 100 seconds (default value specified in settings file).
7576
$ curl -F "file=@{file_name}" 127.0.0.1:8080/upload\?expire_secs=100

api/src/configure/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub struct ApiConfig {
2424
pub max_upload_bytes_size: usize,
2525
pub default_code_length: usize,
2626
pub default_expire_secs: u64,
27+
// TODO add default_delete_manually:bool
2728
}
2829

2930
#[derive(Debug, Deserialize, Clone)]

api/src/database/meta_data_file.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use sdk::dto::response::MetaDataFileResponse;
77
use serde::{Deserialize, Serialize};
88
use sled::IVec;
99

10-
#[derive(Clone, Serialize, Deserialize)]
10+
#[derive(Debug, Clone, Serialize, Deserialize, fake::Dummy)]
1111
pub struct MetaDataFile {
1212
pub created_at: DateTime<Utc>,
1313
pub expire_date_time: DateTime<Utc>,

api/src/database/mod.rs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ impl Database {
7373
Err(err) => {
7474
tracing::error!("Compare and swap failed, Error: {err}");
7575
Err(ApiError::DatabaseError(sled::Error::ReportableBug(
76-
"Updating the meta data file in the database failed.".to_string(),
76+
format!("Updating the meta data file in the database failed, Error: {err}"),
7777
)))
7878
}
7979
}
@@ -267,13 +267,12 @@ mod tests {
267267
.store(path.clone(), meta.clone())
268268
.await
269269
.unwrap();
270-
let result = ctx.state.db.exist(&path).unwrap();
271-
assert!(result);
270+
assert!(ctx.state.db.exist(&path).unwrap());
272271
}
273272

274273
#[test_context(StateTestContext)]
275274
#[tokio::test]
276-
async fn test_store_file_and_purge_it(ctx: &mut StateTestContext) {
275+
async fn test_store_file_and_expire_it(ctx: &mut StateTestContext) {
277276
let path: FilePath = Faker.fake();
278277
let meta = MetaDataFile {
279278
created_at: Utc::now(),
@@ -290,8 +289,7 @@ mod tests {
290289
.await
291290
.unwrap();
292291
tokio::time::sleep(Duration::from_secs(1)).await;
293-
let result = ctx.state.db.exist(&path).unwrap();
294-
assert!(!result);
292+
assert!(!ctx.state.db.exist(&path).unwrap());
295293
}
296294

297295
#[test_context(StateTestContext)]
@@ -312,14 +310,53 @@ mod tests {
312310
.store(path.clone(), meta.clone())
313311
.await
314312
.unwrap();
315-
ctx.state.db.delete(path).await.unwrap().unwrap();
313+
ctx.state.db.delete(path.clone()).await.unwrap().unwrap();
314+
assert!(!ctx.state.db.exist(&path).unwrap());
316315
}
317316

318317
#[test_context(StateTestContext)]
319318
#[tokio::test]
320319
async fn test_delete_file_that_does_not_exist(ctx: &mut StateTestContext) {
321-
let path: FilePath = Faker.fake();
320+
let mut path: FilePath = Faker.fake();
321+
let meta = MetaDataFile {
322+
created_at: Utc::now(),
323+
expire_date_time: Utc::now() + chrono::Duration::seconds(10),
324+
secret: None,
325+
delete_manually: true,
326+
max_download: None,
327+
count_downloads: 0,
328+
};
329+
ctx
330+
.state
331+
.db
332+
.store(path.clone(), meta.clone())
333+
.await
334+
.unwrap();
335+
path.file_name = format!("{}.txt", Faker.fake::<String>());
322336
let result = ctx.state.db.delete(path).await.unwrap();
323337
assert!(result.is_none())
324338
}
339+
340+
#[test_context(StateTestContext)]
341+
#[tokio::test]
342+
async fn test_fetch_file_that_does_not_exist(ctx: &mut StateTestContext) {
343+
let mut path: FilePath = Faker.fake();
344+
let meta = MetaDataFile {
345+
created_at: Utc::now(),
346+
expire_date_time: Utc::now() + chrono::Duration::seconds(10),
347+
secret: None,
348+
delete_manually: true,
349+
max_download: None,
350+
count_downloads: 0,
351+
};
352+
ctx
353+
.state
354+
.db
355+
.store(path.clone(), meta.clone())
356+
.await
357+
.unwrap();
358+
path.file_name = format!("{}.txt", Faker.fake::<String>());
359+
let result = ctx.state.db.fetch(&path).unwrap();
360+
assert!(result.is_none())
361+
}
325362
}

api/src/error/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub enum ApiError {
3434
ReqwestError(#[from] reqwest::Error),
3535
#[error(transparent)]
3636
SystemTimeError(#[from] std::time::SystemTimeError),
37-
#[error("hash error {0}")]
37+
#[error("hash error: {0}")]
3838
HashError(String),
3939
#[error(transparent)]
4040
SpawnTaskError(#[from] tokio::task::JoinError),

api/src/error/result.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ use super::ApiError;
33
pub type ApiResult<T = ()> = std::result::Result<T, ApiError>;
44

55
pub trait ToApiResult<T> {
6-
fn to_result(self) -> ApiResult<T>;
6+
fn to_result(self, resource: &str) -> ApiResult<T>;
77
}
88

99
impl<T> ToApiResult<T> for Option<T> {
10-
fn to_result(self) -> ApiResult<T> {
11-
self.ok_or_else(|| ApiError::NotFoundError(format!("{} not found", std::any::type_name::<T>())))
10+
fn to_result(self, resource: &str) -> ApiResult<T> {
11+
self.ok_or_else(|| ApiError::NotFoundError(format!("{resource} not found")))
1212
}
1313
}

api/src/service/file.rs

Lines changed: 132 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,10 @@ pub async fn info(
147147
code: code.to_string(),
148148
file_name: file_name.to_string(),
149149
};
150-
let meta = state.db.fetch(&file_path)?.to_result()?;
150+
let meta = state
151+
.db
152+
.fetch(&file_path)?
153+
.to_result(&file_path.to_string())?;
151154
if let Some(max) = meta.max_download {
152155
if meta.count_downloads >= max {
153156
state.db.delete(file_path.clone()).await?;
@@ -168,7 +171,10 @@ pub async fn fetch(
168171
code: code.to_string(),
169172
file_name: file_name.to_string(),
170173
};
171-
let meta_data = state.db.fetch(&file_path)?.to_result()?;
174+
let meta_data = state
175+
.db
176+
.fetch(&file_path)?
177+
.to_result(&file_path.to_string())?;
172178
authorize_user(secret, &meta_data.secret)?;
173179
if let Some(max) = meta_data.max_download {
174180
if meta_data.count_downloads >= max {
@@ -245,6 +251,7 @@ mod tests {
245251
util::{multipart::create_multipart_request, test::StateTestContext},
246252
};
247253

254+
use fake::{Fake, Faker};
248255
use test_context::test_context;
249256

250257
#[test_context(StateTestContext)]
@@ -257,12 +264,132 @@ mod tests {
257264
delete_manually: Some(false),
258265
qr_code_format: None,
259266
};
260-
let multipart = create_multipart_request("file_name.txt", "data")
267+
let file_name = format!("{}.txt", Faker.fake::<String>());
268+
let multipart = create_multipart_request(&file_name, "data").await.unwrap();
269+
let (file_path, _) = store(&ctx.state, &param, None, multipart).await.unwrap();
270+
let result = delete(&ctx.state, &file_path.code, &file_path.file_name, None).await;
271+
assert_err!(result, |e: &ApiError| e.to_string()
272+
== format!("{}/{file_name} is not deletable", file_path.code));
273+
}
274+
275+
#[test_context(StateTestContext)]
276+
#[tokio::test]
277+
async fn test_max_download_file_error(ctx: &mut StateTestContext) {
278+
let param = UploadQueryParam {
279+
max_download: Some(1),
280+
code_length: None,
281+
expire_secs: None,
282+
delete_manually: Some(false),
283+
qr_code_format: None,
284+
};
285+
let file_name = format!("{}.txt", Faker.fake::<String>());
286+
let multipart = create_multipart_request(&file_name, "data").await.unwrap();
287+
let (file_path, _) = store(&ctx.state, &param, None, multipart).await.unwrap();
288+
fetch(&ctx.state, &file_path.code, &file_path.file_name, None)
289+
.await
290+
.unwrap();
291+
let result = fetch(&ctx.state, &file_path.code, &file_path.file_name, None).await;
292+
assert_err!(result, |e: &ApiError| e.to_string()
293+
== format!(
294+
"resource not found: {}/{file_name} not found",
295+
file_path.code
296+
));
297+
}
298+
299+
#[test_context(StateTestContext)]
300+
#[tokio::test]
301+
async fn test_authorization_header_required_error(ctx: &mut StateTestContext) {
302+
let secret = Secret::new(Faker.fake::<String>());
303+
let param = UploadQueryParam {
304+
max_download: None,
305+
code_length: None,
306+
expire_secs: None,
307+
delete_manually: Some(true),
308+
qr_code_format: None,
309+
};
310+
let file_name = format!("{}.txt", Faker.fake::<String>());
311+
let multipart = create_multipart_request(&file_name, "data").await.unwrap();
312+
let (file_path, _) = store(&ctx.state, &param, Some(secret), multipart)
261313
.await
262314
.unwrap();
263-
let (file_path, _) = store(&ctx.state, &param, None, multipart).await.unwrap();
264315
let result = delete(&ctx.state, &file_path.code, &file_path.file_name, None).await;
265316
assert_err!(result, |e: &ApiError| e.to_string()
266-
== format!("{}/file_name.txt is not deletable", file_path.code));
317+
== "Authorization header required.");
318+
let result = fetch(&ctx.state, &file_path.code, &file_path.file_name, None).await;
319+
assert_err!(result, |e: &ApiError| e.to_string()
320+
== "Authorization header required.");
321+
}
322+
323+
#[test_context(StateTestContext)]
324+
#[tokio::test]
325+
async fn test_secret_token_is_invalid_error(ctx: &mut StateTestContext) {
326+
let mut secret = Secret::new(Faker.fake::<String>());
327+
let param = UploadQueryParam {
328+
max_download: None,
329+
code_length: None,
330+
expire_secs: None,
331+
delete_manually: Some(true),
332+
qr_code_format: None,
333+
};
334+
let file_name = format!("{}.txt", Faker.fake::<String>());
335+
let multipart = create_multipart_request(&file_name, "data").await.unwrap();
336+
let (file_path, _) = store(&ctx.state, &param, Some(secret), multipart)
337+
.await
338+
.unwrap();
339+
secret = Secret::new(Faker.fake::<String>());
340+
let result = delete(
341+
&ctx.state,
342+
&file_path.code,
343+
&file_path.file_name,
344+
Some(secret.clone()),
345+
)
346+
.await;
347+
assert_err!(result, |e: &ApiError| e.to_string()
348+
== "Secret token is invalid");
349+
350+
let result = fetch(
351+
&ctx.state,
352+
&file_path.code,
353+
&file_path.file_name,
354+
Some(secret),
355+
)
356+
.await;
357+
assert_err!(result, |e: &ApiError| e.to_string()
358+
== "Secret token is invalid");
359+
}
360+
361+
#[test_context(StateTestContext)]
362+
#[tokio::test]
363+
async fn test_code_length(ctx: &mut StateTestContext) {
364+
let code_length = 100;
365+
let param = UploadQueryParam {
366+
max_download: None,
367+
code_length: Some(code_length),
368+
expire_secs: None,
369+
delete_manually: Some(false),
370+
qr_code_format: None,
371+
};
372+
let file_name = format!("{}.txt", Faker.fake::<String>());
373+
let multipart = create_multipart_request(&file_name, "data").await.unwrap();
374+
let (file_path, _) = store(&ctx.state, &param, None, multipart).await.unwrap();
375+
assert_eq!(file_path.code.len(), code_length);
376+
}
377+
378+
#[test_context(StateTestContext)]
379+
#[tokio::test]
380+
async fn test_file_does_not_exist_error(ctx: &mut StateTestContext) {
381+
let file_path = Faker.fake::<FilePath>();
382+
let result = fetch(&ctx.state, &file_path.code, &file_path.file_name, None).await;
383+
assert_err!(result, |e: &ApiError| e.to_string()
384+
== format!(
385+
"resource not found: {}/{} not found",
386+
file_path.code, file_path.file_name
387+
));
388+
let result = info(&ctx.state, &file_path.code, &file_path.file_name, None).await;
389+
assert_err!(result, |e: &ApiError| e.to_string()
390+
== format!(
391+
"resource not found: {}/{} not found",
392+
file_path.code, file_path.file_name
393+
));
267394
}
268395
}

api/src/util/secret.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use crate::error::{result::ApiResult, ApiError};
33
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
44
pub struct Secret(String);
55

6-
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Eq, PartialEq, PartialOrd, Ord)]
6+
#[derive(
7+
Debug, serde::Serialize, serde::Deserialize, Clone, Eq, PartialEq, PartialOrd, Ord, fake::Dummy,
8+
)]
79
pub struct SecretHash(String);
810

911
impl Secret {

cli/tests/cli/decrypt_cli_test.rs

Whitespace-only changes.

cli/tests/cli/encrypt_cli_test.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use assert_cmd::Command;
2+
3+
use crate::helper::CliTestContext;
4+
5+
#[test_context::test_context(CliTestContext)]
6+
#[tokio::test]
7+
async fn test_upload_command(ctx: &mut CliTestContext) {
8+
let (file, _) = ctx.create_dummy_file().await.unwrap();
9+
let _out = Command::cargo_bin("cli")
10+
.unwrap()
11+
.args([
12+
"--server-addr",
13+
&ctx.server_addr,
14+
"upload",
15+
"--source-file",
16+
file.to_str().unwrap(),
17+
])
18+
.assert()
19+
.success()
20+
.to_string();
21+
}
22+
23+
24+
#[test_context::test_context(CliTestContext)]
25+
#[tokio::test]
26+
async fn test_upload_and_encrypt_command(ctx: &mut CliTestContext) {
27+
let (file, _) = ctx.create_dummy_file().await.unwrap();
28+
let _out = Command::cargo_bin("cli")
29+
.unwrap()
30+
.args([
31+
"--server-addr",
32+
&ctx.server_addr,
33+
"upload",
34+
"--source-file",
35+
file.to_str().unwrap(),
36+
])
37+
.assert()
38+
.success()
39+
.to_string();
40+
}

0 commit comments

Comments
 (0)