From 39aa1f0ad65e8b2d24159e78ae7e19b7ace74e0f Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 14:11:01 +0100 Subject: [PATCH 01/19] fix: possible crypto file write fixes --- core/src/crypto.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/src/crypto.rs b/core/src/crypto.rs index 9eaf538..c8344f1 100644 --- a/core/src/crypto.rs +++ b/core/src/crypto.rs @@ -238,8 +238,8 @@ async fn age_decrypt_with_private_key( }; let mut decrypt_writer = decryptor.decrypt_async(iter::once( - &age::x25519::Identity::from_str(key).map_err(|e| ServiceError::AgeKeyError(e.to_string()))? - as &dyn age::Identity, + &age::x25519::Identity::from_str(key) + .map_err(|e| ServiceError::AgeKeyError(e.to_string()))? as &dyn age::Identity, ))?; decrypt_writer.read_to_end(output_buffer).await?; @@ -289,12 +289,18 @@ pub async fn encrypt_file_with_recipients>( tokio::io::AsyncReadExt::read_to_end(&mut plain_file, &mut plain_file_contents).await?; + drop(plain_file); + age_encrypt_with_recipients( plain_file_contents.as_slice(), &mut cipher_file, &recipients, ) - .await + .await?; + + tokio::io::AsyncWriteExt::shutdown(&mut cipher_file).await?; + + Ok(()) } pub async fn decrypt_file_with_private_key>( From 5bac182f831b63c5e93c8548237e8769fe49f229 Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 14:11:10 +0100 Subject: [PATCH 02/19] fix: portfolio submit fix --- core/src/services/candidate_service.rs | 45 ++++++++++++-------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 592ce8d..c524d3d 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -47,14 +47,14 @@ impl CandidateService { let (pubkey, priv_key_plain_text) = crypto::create_identity(); - let encrypted_priv_key = crypto::encrypt_password(priv_key_plain_text, plain_text_password.to_string()).await?; + let encrypted_priv_key = + crypto::encrypt_password(priv_key_plain_text, plain_text_password.to_string()).await?; let hashed_personal_id_number = hash_password(personal_id_number).await?; // TODO: Specify root path in config? tokio::fs::create_dir_all(Path::new(&application_id.to_string()).join("cache")).await?; - let candidate = Mutation::create_candidate( db, application_id, @@ -142,40 +142,29 @@ impl CandidateService { let mut archive = tokio::fs::File::create(path.join("PORTFOLIO.zip")).await?; - let mut writer = async_zip::write::ZipFileWriter::new(&mut archive); for entry in vec!["MOTIVACNI_DOPIS.pdf", "PORTFOLIO.pdf", "PORTFOLIO.zip"] { - let mut entry_file = tokio::fs::File::open(cache_path.join(entry)) - .await?; + let mut entry_file = tokio::fs::File::open(cache_path.join(entry)).await?; - let mut contents = vec![]; + let mut contents_buffer = vec![]; - entry_file - .read_to_end(&mut contents) - .await?; + entry_file.read_to_end(&mut contents_buffer).await?; let builder = async_zip::ZipEntryBuilder::new(entry.to_string(), async_zip::Compression::Deflate); - let mut entry_writer = writer - .write_entry_stream(builder) - .await?; - - // TODO: write_all_buf? - entry_writer - .write_all(&mut contents) - .await?; + writer.write_entry_whole(builder, &contents_buffer).await?; } // TODO: Ne unwrap writer.close().await.unwrap(); archive.shutdown().await.unwrap(); - let admin_public_keys = Query::get_all_admin_public_keys(db) - .await?; + let admin_public_keys = Query::get_all_admin_public_keys(db).await?; - let candidate = Query::find_candidate_by_id(db, candidate_id).await? + let candidate = Query::find_candidate_by_id(db, candidate_id) + .await? .ok_or(ServiceError::CandidateNotFound)?; let candidate_public_key = candidate.public_key; @@ -187,27 +176,33 @@ impl CandidateService { recipients.append(&mut admin_public_keys_refrence); + let final_path = path.join("PORTFOLIO.zip"); + let Ok(_) = crypto::encrypt_file_with_recipients( - path.join("PORTFOLIO.zip"), - path.join("PORTFOLIO.zip"), + &final_path, + &final_path.with_extension("age"), recipients, ) .await else { return Err(ServiceError::CryptoEncryptFailed); }; + tokio::fs::remove_file(final_path).await?; + Ok(()) } pub async fn get_portfolio(candidate_id: i32, db: &DbConn) -> Result, ServiceError> { - let candidate = Query::find_candidate_by_id(db, candidate_id).await? + let candidate = Query::find_candidate_by_id(db, candidate_id) + .await? .ok_or(ServiceError::CandidateNotFound)?; let candidate_public_key = candidate.public_key; - let path = Path::new(&candidate_id.to_string()).join("PORTFOLIO.zip"); + let path = Path::new(&candidate_id.to_string()).join("PORTFOLIO.age"); - let buffer = crypto::decrypt_file_with_private_key_as_buffer(path, &candidate_public_key).await?; + let buffer = + crypto::decrypt_file_with_private_key_as_buffer(path, &candidate_public_key).await?; Ok(buffer) } From f475af0eec73389aca1241e898e987604d1d61ef Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 14:11:26 +0100 Subject: [PATCH 03/19] feat: submit endpoint --- api/src/lib.rs | 1 + api/src/routes/candidate.rs | 36 +++++++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/api/src/lib.rs b/api/src/lib.rs index b6517b1..7d57562 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -46,6 +46,7 @@ async fn start() -> Result<(), rocket::Error> { routes::candidate::upload_cover_letter, routes::candidate::upload_portfolio_letter, routes::candidate::upload_portfolio_zip, + routes::candidate::submit_portfolio, ], ) .mount( diff --git a/api/src/routes/candidate.rs b/api/src/routes/candidate.rs index c66d4db..7360429 100644 --- a/api/src/routes/candidate.rs +++ b/api/src/routes/candidate.rs @@ -2,7 +2,7 @@ use std::net::SocketAddr; use portfolio_core::candidate_details::ApplicationDetails; use portfolio_core::services::application_service::ApplicationService; -use portfolio_core::services::candidate_service::{CandidateService}; +use portfolio_core::services::candidate_service::CandidateService; use requests::LoginRequest; use rocket::http::{Cookie, CookieJar, Status}; use rocket::response::status::Custom; @@ -68,7 +68,8 @@ pub async fn fill_details( let form = details.into_inner(); let candidate: entity::candidate::Model = session.into(); // TODO: don't return candidate from session - let candidate_parent = ApplicationService::add_all_details(db, candidate.application, form).await; + let candidate_parent = + ApplicationService::add_all_details(db, candidate.application, form).await; if candidate_parent.is_err() { // TODO cleanup @@ -95,7 +96,12 @@ pub async fn get_details( // let handle = tokio::spawn(async move { let details = ApplicationService::decrypt_all_details(db, candidate.application, password) .await - .map_err(|e| Custom(Status::from_code(e.code()).unwrap_or_default(), e.to_string())); + .map_err(|e| { + Custom( + Status::from_code(e.code()).unwrap_or_default(), + e.to_string(), + ) + }); details.map(|d| Json(d)) } @@ -163,3 +169,27 @@ pub async fn upload_portfolio_zip( Ok("Letter added".to_string()) } + +#[post("/submit")] +pub async fn submit_portfolio( + conn: Connection<'_, Db>, + session: CandidateAuth, +) -> Result> { + let db = conn.into_inner(); + + let candidate: entity::candidate::Model = session.into(); + + let submit = CandidateService::submit_portfolio(candidate.application, &db).await; + + if submit.is_err() { + // TODO cleanup + let e = submit.err().unwrap(); + eprintln!("{}", e); + return Err(Custom( + Status::from_code(e.code()).unwrap_or_default(), + e.to_string(), + )); + } + + Ok("Portfolio submitted".to_string()) +} From 62e341c26e4cd64638f6fb4aa652d00697c593b6 Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 14:21:29 +0100 Subject: [PATCH 04/19] feat: add is_submitted --- api/src/lib.rs | 1 + api/src/routes/candidate.rs | 19 +++++++++++++++++++ core/src/services/candidate_service.rs | 6 ++++++ 3 files changed, 26 insertions(+) diff --git a/api/src/lib.rs b/api/src/lib.rs index 7d57562..a12add0 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -47,6 +47,7 @@ async fn start() -> Result<(), rocket::Error> { routes::candidate::upload_portfolio_letter, routes::candidate::upload_portfolio_zip, routes::candidate::submit_portfolio, + routes::candidate::is_submitted, ], ) .mount( diff --git a/api/src/routes/candidate.rs b/api/src/routes/candidate.rs index 7360429..4e5113c 100644 --- a/api/src/routes/candidate.rs +++ b/api/src/routes/candidate.rs @@ -193,3 +193,22 @@ pub async fn submit_portfolio( Ok("Portfolio submitted".to_string()) } + +#[get("/is_submitted")] +pub async fn is_submitted( + session: CandidateAuth, +) -> Result> { + let candidate: entity::candidate::Model = session.into(); + + let is_ok = CandidateService::is_portfolio_submitted(candidate.application).await; + + if !is_ok { + // TODO: Correct error + return Err(Custom( + Status::from_code(404).unwrap_or_default(), + "Portfolio not submitted".to_string(), + )); + } + + Ok("Portfolio ok".to_string()) +} diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index c524d3d..8f41dec 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -192,6 +192,12 @@ impl CandidateService { Ok(()) } + pub async fn is_portfolio_submitted(candidate_id: i32) -> bool { + let path = Path::new(&candidate_id.to_string()).join("PORTFOLIO.age"); + + tokio::fs::metadata(path).await.is_ok() + } + pub async fn get_portfolio(candidate_id: i32, db: &DbConn) -> Result, ServiceError> { let candidate = Query::find_candidate_by_id(db, candidate_id) .await? From abfcf9f3eb175517443c87631307cbec9306ebaa Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 14:35:36 +0100 Subject: [PATCH 05/19] refactor: refactor routes and function names --- api/src/lib.rs | 17 ++++++++++-- api/src/routes/candidate.rs | 38 +++++++++++++++++++------- core/src/services/candidate_service.rs | 14 +++++----- 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/api/src/lib.rs b/api/src/lib.rs index a12add0..22a905d 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -41,13 +41,24 @@ async fn start() -> Result<(), rocket::Error> { routes![ routes::candidate::login, routes::candidate::whoami, - routes::candidate::fill_details, routes::candidate::get_details, - routes::candidate::upload_cover_letter, + ], + ) + .mount( + "/candidate/add", + routes![ + routes::candidate::add_details, routes::candidate::upload_portfolio_letter, routes::candidate::upload_portfolio_zip, + routes::candidate::upload_cover_letter, + ], + ) + .mount( + "/candidate/portfolio", + routes![ routes::candidate::submit_portfolio, - routes::candidate::is_submitted, + routes::candidate::is_portfolio_prepared, + routes::candidate::is_portfolio_submitted, ], ) .mount( diff --git a/api/src/routes/candidate.rs b/api/src/routes/candidate.rs index 4e5113c..dd2dd55 100644 --- a/api/src/routes/candidate.rs +++ b/api/src/routes/candidate.rs @@ -59,7 +59,7 @@ pub async fn whoami(session: CandidateAuth) -> Result> { } #[post("/details", data = "
")] -pub async fn fill_details( +pub async fn add_details( conn: Connection<'_, Db>, details: Json, session: CandidateAuth, @@ -105,14 +105,14 @@ pub async fn get_details( details.map(|d| Json(d)) } -#[post("/coverletter", data = "")] +#[post("/cover_letter", data = "")] pub async fn upload_cover_letter( session: CandidateAuth, letter: Letter, ) -> Result> { let candidate: entity::candidate::Model = session.into(); - let candidate = CandidateService::add_cover_letter(candidate.application, letter.into()).await; + let candidate = CandidateService::add_cover_letter_to_cache(candidate.application, letter.into()).await; if candidate.is_err() { // TODO cleanup @@ -126,7 +126,7 @@ pub async fn upload_cover_letter( Ok("Letter added".to_string()) } -#[post("/portfolioletter", data = "")] +#[post("/portfolio_letter", data = "")] pub async fn upload_portfolio_letter( session: CandidateAuth, letter: Letter, @@ -134,7 +134,7 @@ pub async fn upload_portfolio_letter( let candidate: entity::candidate::Model = session.into(); let candidate = - CandidateService::add_portfolio_letter(candidate.application, letter.into()).await; + CandidateService::add_portfolio_letter_to_cache(candidate.application, letter.into()).await; if candidate.is_err() { // TODO cleanup @@ -148,7 +148,7 @@ pub async fn upload_portfolio_letter( Ok("Letter added".to_string()) } -#[post("/portfolio", data = "")] +#[post("/portfolio_zip", data = "")] pub async fn upload_portfolio_zip( session: CandidateAuth, portfolio: Portfolio, @@ -156,7 +156,7 @@ pub async fn upload_portfolio_zip( let candidate: entity::candidate::Model = session.into(); let candidate = - CandidateService::add_portfolio_zip(candidate.application, portfolio.into()).await; + CandidateService::add_portfolio_zip_to_cache(candidate.application, portfolio.into()).await; if candidate.is_err() { // TODO cleanup @@ -167,7 +167,7 @@ pub async fn upload_portfolio_zip( )); } - Ok("Letter added".to_string()) + Ok("Portfolio added".to_string()) } #[post("/submit")] @@ -179,7 +179,7 @@ pub async fn submit_portfolio( let candidate: entity::candidate::Model = session.into(); - let submit = CandidateService::submit_portfolio(candidate.application, &db).await; + let submit = CandidateService::add_portfolio(candidate.application, &db).await; if submit.is_err() { // TODO cleanup @@ -193,9 +193,27 @@ pub async fn submit_portfolio( Ok("Portfolio submitted".to_string()) } +#[get("/is_prepared")] +pub async fn is_portfolio_prepared( + session: CandidateAuth, +) -> Result> { + let candidate: entity::candidate::Model = session.into(); + + let is_ok = CandidateService::is_portfolio_prepared(candidate.application).await; + + if !is_ok { + // TODO: Correct error + return Err(Custom( + Status::from_code(404).unwrap_or_default(), + "Portfolio not prepared".to_string(), + )); + } + + Ok("Portfolio ok".to_string()) +} #[get("/is_submitted")] -pub async fn is_submitted( +pub async fn is_portfolio_submitted( session: CandidateAuth, ) -> Result> { let candidate: entity::candidate::Model = session.into(); diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 8f41dec..6e8f3f7 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -76,7 +76,7 @@ impl CandidateService { Ok(model) } - pub fn is_set_up(candidate: &candidate::Model) -> bool { + pub fn are_candidate_details_complete(candidate: &candidate::Model) -> bool { candidate.name.is_some() && candidate.surname.is_some() && candidate.birthplace.is_some() @@ -103,22 +103,22 @@ impl CandidateService { Ok(()) } - pub async fn add_cover_letter(candidate_id: i32, letter: Vec) -> Result<(), ServiceError> { + pub async fn add_cover_letter_to_cache(candidate_id: i32, letter: Vec) -> Result<(), ServiceError> { Self::write_portfolio_file(candidate_id, letter, "MOTIVACNI_DOPIS.pdf").await } - pub async fn add_portfolio_letter( + pub async fn add_portfolio_letter_to_cache( candidate_id: i32, letter: Vec, ) -> Result<(), ServiceError> { Self::write_portfolio_file(candidate_id, letter, "PORTFOLIO.pdf").await } - pub async fn add_portfolio_zip(candidate_id: i32, zip: Vec) -> Result<(), ServiceError> { + pub async fn add_portfolio_zip_to_cache(candidate_id: i32, zip: Vec) -> Result<(), ServiceError> { Self::write_portfolio_file(candidate_id, zip, "PORTFOLIO.zip").await } - pub async fn is_portfolio_complete(candidate_id: i32) -> bool { + pub async fn is_portfolio_prepared(candidate_id: i32) -> bool { let cache_path = Path::new(&candidate_id.to_string()).join("cache"); tokio::fs::metadata(cache_path.join("MOTIVACNI_DOPIS.pdf")) @@ -132,11 +132,11 @@ impl CandidateService { .is_ok() } - pub async fn submit_portfolio(candidate_id: i32, db: &DbConn) -> Result<(), ServiceError> { + pub async fn add_portfolio(candidate_id: i32, db: &DbConn) -> Result<(), ServiceError> { let path = Path::new(&candidate_id.to_string()).to_path_buf(); let cache_path = path.join("cache"); - if Self::is_portfolio_complete(candidate_id).await == false { + if Self::is_portfolio_prepared(candidate_id).await == false { return Err(ServiceError::IncompletePortfolio); } From 5821c7c19022a1db9deadc38b243158c6c5cb350 Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 15:35:13 +0100 Subject: [PATCH 06/19] fix: remove unwrap --- core/src/services/candidate_service.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 6e8f3f7..4365db9 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -157,9 +157,8 @@ impl CandidateService { writer.write_entry_whole(builder, &contents_buffer).await?; } - // TODO: Ne unwrap - writer.close().await.unwrap(); - archive.shutdown().await.unwrap(); + writer.close().await?; + archive.shutdown().await?; let admin_public_keys = Query::get_all_admin_public_keys(db).await?; From a93549c9067a86aaf8a29bea2a81606d64d5715d Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 15:35:49 +0100 Subject: [PATCH 07/19] fix: portfolio cleanup after unsuccessful request --- api/src/routes/candidate.rs | 6 ++++-- core/src/services/candidate_service.rs | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/api/src/routes/candidate.rs b/api/src/routes/candidate.rs index dd2dd55..be41885 100644 --- a/api/src/routes/candidate.rs +++ b/api/src/routes/candidate.rs @@ -182,9 +182,10 @@ pub async fn submit_portfolio( let submit = CandidateService::add_portfolio(candidate.application, &db).await; if submit.is_err() { - // TODO cleanup + // Cleanup + // TODO: unwrap pryč + CandidateService::delete_portfolio(candidate.application).await.unwrap(); let e = submit.err().unwrap(); - eprintln!("{}", e); return Err(Custom( Status::from_code(e.code()).unwrap_or_default(), e.to_string(), @@ -193,6 +194,7 @@ pub async fn submit_portfolio( Ok("Portfolio submitted".to_string()) } + #[get("/is_prepared")] pub async fn is_portfolio_prepared( session: CandidateAuth, diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 4365db9..d031538 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -191,6 +191,23 @@ impl CandidateService { Ok(()) } + pub async fn delete_portfolio(candidate_id: i32) -> Result<(), ServiceError> { + let path = Path::new(&candidate_id.to_string()).to_path_buf(); + + let portfolio_path = path.join("PORTFOLIO.zip"); + let portfolio_age_path = portfolio_path.with_extension("age"); + + if tokio::fs::metadata(&portfolio_path).await.is_ok() { + tokio::fs::remove_file(&portfolio_path).await?; + } + + if tokio::fs::metadata(&portfolio_age_path).await.is_ok() { + tokio::fs::remove_file(&portfolio_age_path).await?; + } + + Ok(()) + } + pub async fn is_portfolio_submitted(candidate_id: i32) -> bool { let path = Path::new(&candidate_id.to_string()).join("PORTFOLIO.age"); From 20028846bb68519098ecb774510dd9d770f99afb Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 16:09:05 +0100 Subject: [PATCH 08/19] fix: delete only on critical error --- api/src/routes/candidate.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/api/src/routes/candidate.rs b/api/src/routes/candidate.rs index be41885..1a3e39c 100644 --- a/api/src/routes/candidate.rs +++ b/api/src/routes/candidate.rs @@ -112,7 +112,8 @@ pub async fn upload_cover_letter( ) -> Result> { let candidate: entity::candidate::Model = session.into(); - let candidate = CandidateService::add_cover_letter_to_cache(candidate.application, letter.into()).await; + let candidate = + CandidateService::add_cover_letter_to_cache(candidate.application, letter.into()).await; if candidate.is_err() { // TODO cleanup @@ -182,10 +183,15 @@ pub async fn submit_portfolio( let submit = CandidateService::add_portfolio(candidate.application, &db).await; if submit.is_err() { - // Cleanup - // TODO: unwrap pryč - CandidateService::delete_portfolio(candidate.application).await.unwrap(); let e = submit.err().unwrap(); + // Delete on critical error + // TODO: Více kontrol? + if e.code() == 500 { + // Cleanup + CandidateService::delete_portfolio(candidate.application) + .await + .unwrap(); + } return Err(Custom( Status::from_code(e.code()).unwrap_or_default(), e.to_string(), From 7e00d302d22709639becea5d55b9ec71eb5e1c2c Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 16:09:28 +0100 Subject: [PATCH 09/19] fix: correct error for incomplete portfolio --- core/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/error.rs b/core/src/error.rs index 5aebb06..4c8f4f1 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -89,7 +89,7 @@ impl ServiceError { ServiceError::AesError(_) => 500, ServiceError::ArgonConfigError(_) => 500, //TODO: Correct code - ServiceError::IncompletePortfolio => 500, + ServiceError::IncompletePortfolio => 406, ServiceError::ZipError(_) => 500, } } From 530994f33dc414928224c8ee6925d189d7778b54 Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 16:21:03 +0100 Subject: [PATCH 10/19] feat: load cache into memory before deleting --- api/src/routes/candidate.rs | 8 ++--- core/src/services/candidate_service.rs | 42 ++++++++++++++++++++------ 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/api/src/routes/candidate.rs b/api/src/routes/candidate.rs index 1a3e39c..040433d 100644 --- a/api/src/routes/candidate.rs +++ b/api/src/routes/candidate.rs @@ -202,9 +202,7 @@ pub async fn submit_portfolio( } #[get("/is_prepared")] -pub async fn is_portfolio_prepared( - session: CandidateAuth, -) -> Result> { +pub async fn is_portfolio_prepared(session: CandidateAuth) -> Result> { let candidate: entity::candidate::Model = session.into(); let is_ok = CandidateService::is_portfolio_prepared(candidate.application).await; @@ -221,9 +219,7 @@ pub async fn is_portfolio_prepared( } #[get("/is_submitted")] -pub async fn is_portfolio_submitted( - session: CandidateAuth, -) -> Result> { +pub async fn is_portfolio_submitted(session: CandidateAuth) -> Result> { let candidate: entity::candidate::Model = session.into(); let is_ok = CandidateService::is_portfolio_submitted(candidate.application).await; diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index d031538..b44509d 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -103,7 +103,10 @@ impl CandidateService { Ok(()) } - pub async fn add_cover_letter_to_cache(candidate_id: i32, letter: Vec) -> Result<(), ServiceError> { + pub async fn add_cover_letter_to_cache( + candidate_id: i32, + letter: Vec, + ) -> Result<(), ServiceError> { Self::write_portfolio_file(candidate_id, letter, "MOTIVACNI_DOPIS.pdf").await } @@ -114,7 +117,10 @@ impl CandidateService { Self::write_portfolio_file(candidate_id, letter, "PORTFOLIO.pdf").await } - pub async fn add_portfolio_zip_to_cache(candidate_id: i32, zip: Vec) -> Result<(), ServiceError> { + pub async fn add_portfolio_zip_to_cache( + candidate_id: i32, + zip: Vec, + ) -> Result<(), ServiceError> { Self::write_portfolio_file(candidate_id, zip, "PORTFOLIO.zip").await } @@ -132,6 +138,14 @@ impl CandidateService { .is_ok() } + pub async fn delete_cache(candidate_id: i32) -> Result<(), ServiceError> { + let cache_path = Path::new(&candidate_id.to_string()).join("cache"); + + tokio::fs::remove_dir_all(cache_path).await?; + + Ok(()) + } + pub async fn add_portfolio(candidate_id: i32, db: &DbConn) -> Result<(), ServiceError> { let path = Path::new(&candidate_id.to_string()).to_path_buf(); let cache_path = path.join("cache"); @@ -144,17 +158,27 @@ impl CandidateService { let mut writer = async_zip::write::ZipFileWriter::new(&mut archive); - for entry in vec!["MOTIVACNI_DOPIS.pdf", "PORTFOLIO.pdf", "PORTFOLIO.zip"] { - let mut entry_file = tokio::fs::File::open(cache_path.join(entry)).await?; + let mut buffer = vec![vec![], vec![], vec![]]; - let mut contents_buffer = vec![]; + let filenames = vec!["MOTIVACNI_DOPIS.pdf", "PORTFOLIO.pdf", "PORTFOLIO.zip"]; - entry_file.read_to_end(&mut contents_buffer).await?; + for (index, entry) in buffer.iter_mut().enumerate() { + let filename = filenames[index]; + let mut entry_file = tokio::fs::File::open(cache_path.join(filename)).await?; - let builder = - async_zip::ZipEntryBuilder::new(entry.to_string(), async_zip::Compression::Deflate); + entry_file.read_to_end(entry).await?; + } - writer.write_entry_whole(builder, &contents_buffer).await?; + Self::delete_cache(candidate_id).await?; + + for (index, entry) in buffer.iter_mut().enumerate() { + let filename = filenames[index]; + let builder = async_zip::ZipEntryBuilder::new( + filename.to_string(), + async_zip::Compression::Deflate, + ); + + writer.write_entry_whole(builder, &entry).await?; } writer.close().await?; From e9f3a8042c31f522dd5aa96f3f0df94a914f5542 Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 16:26:42 +0100 Subject: [PATCH 11/19] refactor: refactor is_portfolio_prepared --- core/src/services/candidate_service.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index b44509d..affd052 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -127,15 +127,15 @@ impl CandidateService { pub async fn is_portfolio_prepared(candidate_id: i32) -> bool { let cache_path = Path::new(&candidate_id.to_string()).join("cache"); - tokio::fs::metadata(cache_path.join("MOTIVACNI_DOPIS.pdf")) - .await - .is_ok() - && tokio::fs::metadata(cache_path.join("PORTFOLIO.pdf")) - .await - .is_ok() - && tokio::fs::metadata(cache_path.join("PORTFOLIO.zip")) - .await - .is_ok() + let filenames = vec!["MOTIVACNI_DOPIS.pdf", "PORTFOLIO.pdf", "PORTFOLIO.zip"]; + + for filename in filenames { + if !tokio::fs::metadata(cache_path.join(filename)).await.is_ok() { + return false; + } + } + + true } pub async fn delete_cache(candidate_id: i32) -> Result<(), ServiceError> { From fc614eadb8af683485a01547eedd1de0055becbe Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 16:38:23 +0100 Subject: [PATCH 12/19] feat: add cache validation functions --- api/src/routes/candidate.rs | 30 ++++++++++++++++++++++++++ core/src/services/candidate_service.rs | 18 ++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/api/src/routes/candidate.rs b/api/src/routes/candidate.rs index 040433d..3d2dcd2 100644 --- a/api/src/routes/candidate.rs +++ b/api/src/routes/candidate.rs @@ -127,6 +127,16 @@ pub async fn upload_cover_letter( Ok("Letter added".to_string()) } +// TODO: JSON +#[get["/is_cover_letter"]] +pub async fn is_cover_letter(session: CandidateAuth) -> Result> { + let candidate: entity::candidate::Model = session.into(); + + let exists = CandidateService::is_cover_letter(candidate.application).await; + + Ok(exists.to_string()) +} + #[post("/portfolio_letter", data = "")] pub async fn upload_portfolio_letter( session: CandidateAuth, @@ -149,6 +159,16 @@ pub async fn upload_portfolio_letter( Ok("Letter added".to_string()) } +// TODO: JSON +#[get["/is_portfolio_letter"]] +pub async fn is_portfolio_letter(session: CandidateAuth) -> Result> { + let candidate: entity::candidate::Model = session.into(); + + let exists = CandidateService::is_portfolio_letter(candidate.application).await; + + Ok(exists.to_string()) +} + #[post("/portfolio_zip", data = "")] pub async fn upload_portfolio_zip( session: CandidateAuth, @@ -171,6 +191,16 @@ pub async fn upload_portfolio_zip( Ok("Portfolio added".to_string()) } +// TODO: JSON +#[get["/is_portfolio_zip"]] +pub async fn is_portfolio_zip(session: CandidateAuth) -> Result> { + let candidate: entity::candidate::Model = session.into(); + + let exists = CandidateService::is_portfolio_zip(candidate.application).await; + + Ok(exists.to_string()) +} + #[post("/submit")] pub async fn submit_portfolio( conn: Connection<'_, Db>, diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index affd052..44a4045 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -110,6 +110,12 @@ impl CandidateService { Self::write_portfolio_file(candidate_id, letter, "MOTIVACNI_DOPIS.pdf").await } + pub async fn is_cover_letter(candidate_id: i32) -> bool { + let cache_path = Path::new(&candidate_id.to_string()).join("cache"); + + tokio::fs::metadata(cache_path.join(cache_path.join("MOTIVACNI_DOPIS.pdf"))).await.is_ok() + } + pub async fn add_portfolio_letter_to_cache( candidate_id: i32, letter: Vec, @@ -117,6 +123,12 @@ impl CandidateService { Self::write_portfolio_file(candidate_id, letter, "PORTFOLIO.pdf").await } + pub async fn is_portfolio_letter(candidate_id: i32) -> bool { + let cache_path = Path::new(&candidate_id.to_string()).join("cache"); + + tokio::fs::metadata(cache_path.join(cache_path.join("PORTFOLIO.pdf"))).await.is_ok() + } + pub async fn add_portfolio_zip_to_cache( candidate_id: i32, zip: Vec, @@ -124,6 +136,12 @@ impl CandidateService { Self::write_portfolio_file(candidate_id, zip, "PORTFOLIO.zip").await } + pub async fn is_portfolio_zip(candidate_id: i32) -> bool { + let cache_path = Path::new(&candidate_id.to_string()).join("cache"); + + tokio::fs::metadata(cache_path.join(cache_path.join("PORTFOLIO.zip"))).await.is_ok() + } + pub async fn is_portfolio_prepared(candidate_id: i32) -> bool { let cache_path = Path::new(&candidate_id.to_string()).join("cache"); From 8560c0b3a6745cf1b5c82c1be862e0f3a9425723 Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 16:39:24 +0100 Subject: [PATCH 13/19] fix: do not remove cache dir completely --- core/src/services/candidate_service.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 44a4045..6a24241 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -159,7 +159,9 @@ impl CandidateService { pub async fn delete_cache(candidate_id: i32) -> Result<(), ServiceError> { let cache_path = Path::new(&candidate_id.to_string()).join("cache"); - tokio::fs::remove_dir_all(cache_path).await?; + tokio::fs::remove_dir_all(&cache_path).await?; + // Recreate blank cache directory + tokio::fs::create_dir_all(&cache_path).await?; Ok(()) } From eb5fc405e2de591e4feabcf751b6b53ab380c5ef Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 16:42:18 +0100 Subject: [PATCH 14/19] refactor: are_candidate_details_complete name --- core/src/services/candidate_service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 6a24241..4126ae8 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -76,7 +76,7 @@ impl CandidateService { Ok(model) } - pub fn are_candidate_details_complete(candidate: &candidate::Model) -> bool { + pub fn is_candidate_info(candidate: &candidate::Model) -> bool { candidate.name.is_some() && candidate.surname.is_some() && candidate.birthplace.is_some() From e4dda4581d858149636c449c38c55bb4f829dff3 Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 16:56:16 +0100 Subject: [PATCH 15/19] feat: specify file root dir in env --- core/src/services/candidate_service.rs | 47 ++++++++++++++++---------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 4126ae8..4e3b7ec 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use entity::candidate; use sea_orm::{prelude::Uuid, DbConn}; @@ -18,6 +18,12 @@ const FIELD_OF_STUDY_PREFIXES: [&str; 3] = ["101", "102", "103"]; pub struct CandidateService; impl CandidateService { + // Get root path or local directory + fn get_file_store_path() -> PathBuf { + dotenv::dotenv().ok(); + Path::new(&std::env::var("STORE_PATH").unwrap_or_else(|_| "".to_string())).to_path_buf() + } + /// Creates a new candidate with: /// Encrypted personal identification number /// Hashed password @@ -52,8 +58,7 @@ impl CandidateService { let hashed_personal_id_number = hash_password(personal_id_number).await?; - // TODO: Specify root path in config? - tokio::fs::create_dir_all(Path::new(&application_id.to_string()).join("cache")).await?; + tokio::fs::create_dir_all(Self::get_file_store_path().join(&application_id.to_string()).join("cache")).await?; let candidate = Mutation::create_candidate( db, @@ -94,7 +99,7 @@ impl CandidateService { data: Vec, filename: &str, ) -> Result<(), ServiceError> { - let cache_path = Path::new(&candidate_id.to_string()).join("cache"); + let cache_path = Self::get_file_store_path().join(&candidate_id.to_string()).join("cache"); let mut file = tokio::fs::File::create(cache_path.join(filename)).await?; @@ -111,9 +116,11 @@ impl CandidateService { } pub async fn is_cover_letter(candidate_id: i32) -> bool { - let cache_path = Path::new(&candidate_id.to_string()).join("cache"); + let cache_path = Self::get_file_store_path().join(&candidate_id.to_string()).join("cache"); - tokio::fs::metadata(cache_path.join(cache_path.join("MOTIVACNI_DOPIS.pdf"))).await.is_ok() + tokio::fs::metadata(cache_path.join(cache_path.join("MOTIVACNI_DOPIS.pdf"))) + .await + .is_ok() } pub async fn add_portfolio_letter_to_cache( @@ -124,9 +131,11 @@ impl CandidateService { } pub async fn is_portfolio_letter(candidate_id: i32) -> bool { - let cache_path = Path::new(&candidate_id.to_string()).join("cache"); + let cache_path = Self::get_file_store_path().join(&candidate_id.to_string()).join("cache"); - tokio::fs::metadata(cache_path.join(cache_path.join("PORTFOLIO.pdf"))).await.is_ok() + tokio::fs::metadata(cache_path.join(cache_path.join("PORTFOLIO.pdf"))) + .await + .is_ok() } pub async fn add_portfolio_zip_to_cache( @@ -137,13 +146,15 @@ impl CandidateService { } pub async fn is_portfolio_zip(candidate_id: i32) -> bool { - let cache_path = Path::new(&candidate_id.to_string()).join("cache"); + let cache_path = Self::get_file_store_path().join(&candidate_id.to_string()).join("cache"); - tokio::fs::metadata(cache_path.join(cache_path.join("PORTFOLIO.zip"))).await.is_ok() + tokio::fs::metadata(cache_path.join(cache_path.join("PORTFOLIO.zip"))) + .await + .is_ok() } pub async fn is_portfolio_prepared(candidate_id: i32) -> bool { - let cache_path = Path::new(&candidate_id.to_string()).join("cache"); + let cache_path = Self::get_file_store_path().join(&candidate_id.to_string()).join("cache"); let filenames = vec!["MOTIVACNI_DOPIS.pdf", "PORTFOLIO.pdf", "PORTFOLIO.zip"]; @@ -157,7 +168,7 @@ impl CandidateService { } pub async fn delete_cache(candidate_id: i32) -> Result<(), ServiceError> { - let cache_path = Path::new(&candidate_id.to_string()).join("cache"); + let cache_path = Self::get_file_store_path().join(&candidate_id.to_string()).join("cache"); tokio::fs::remove_dir_all(&cache_path).await?; // Recreate blank cache directory @@ -167,7 +178,7 @@ impl CandidateService { } pub async fn add_portfolio(candidate_id: i32, db: &DbConn) -> Result<(), ServiceError> { - let path = Path::new(&candidate_id.to_string()).to_path_buf(); + let path = Self::get_file_store_path().join(&candidate_id.to_string()).to_path_buf(); let cache_path = path.join("cache"); if Self::is_portfolio_prepared(candidate_id).await == false { @@ -236,7 +247,7 @@ impl CandidateService { } pub async fn delete_portfolio(candidate_id: i32) -> Result<(), ServiceError> { - let path = Path::new(&candidate_id.to_string()).to_path_buf(); + let path = Self::get_file_store_path().join(&candidate_id.to_string()).to_path_buf(); let portfolio_path = path.join("PORTFOLIO.zip"); let portfolio_age_path = portfolio_path.with_extension("age"); @@ -253,19 +264,21 @@ impl CandidateService { } pub async fn is_portfolio_submitted(candidate_id: i32) -> bool { - let path = Path::new(&candidate_id.to_string()).join("PORTFOLIO.age"); + let path = Self::get_file_store_path().join(&candidate_id.to_string()).to_path_buf(); - tokio::fs::metadata(path).await.is_ok() + tokio::fs::metadata(path.join("PORTFOLIO.age")).await.is_ok() } pub async fn get_portfolio(candidate_id: i32, db: &DbConn) -> Result, ServiceError> { + let path = Self::get_file_store_path().join(&candidate_id.to_string()).to_path_buf(); + let candidate = Query::find_candidate_by_id(db, candidate_id) .await? .ok_or(ServiceError::CandidateNotFound)?; let candidate_public_key = candidate.public_key; - let path = Path::new(&candidate_id.to_string()).join("PORTFOLIO.age"); + let path = path.join("PORTFOLIO.age"); let buffer = crypto::decrypt_file_with_private_key_as_buffer(path, &candidate_public_key).await?; From b9f842f25860fb7067dc4cb3438f79513ca6e364 Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 17:08:53 +0100 Subject: [PATCH 16/19] feat: init file write tests --- core/src/services/candidate_service.rs | 87 ++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 4e3b7ec..c6fe20c 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -458,4 +458,91 @@ mod tests { assert_eq!(dec_details.name, "test"); // TODO: test every element assert_eq!(dec_details.parent_surname, "test"); } + + #[tokio::test] + async fn test_folder_creation() { + let db = get_memory_sqlite_connection().await; + let plain_text_password = "test".to_string(); + + let temp_dir = std::env::temp_dir().join("portfolio_test_tempdir"); + std::env::set_var("STORE_PATH", temp_dir.to_str().unwrap()); + + CandidateService::create(&db, 103151, &plain_text_password, "".to_string()) + .await + .ok() + .unwrap(); + + assert!(tokio::fs::metadata(temp_dir.join("103151")).await.is_ok()); + assert!(tokio::fs::metadata(temp_dir.join("103151").join("cache")).await.is_ok()); + + tokio::fs::remove_dir_all(temp_dir).await.unwrap(); + } + + #[tokio::test] + async fn test_write_portfolio_file() { + let temp_dir = std::env::temp_dir().join("portfolio_test_tempdir"); + std::env::set_var("STORE_PATH", temp_dir.to_str().unwrap()); + + tokio::fs::create_dir_all(temp_dir.join("103151").join("cache")) + .await + .unwrap(); + + + CandidateService::write_portfolio_file(103151, vec![0], "test").await.unwrap(); + + assert!(tokio::fs::metadata(temp_dir.join("103151").join("cache").join("test")).await.is_ok()); + + tokio::fs::remove_dir_all(temp_dir).await.unwrap(); + } + + #[tokio::test] + async fn test_add_cover_letter_to_cache() { + let temp_dir = std::env::temp_dir().join("portfolio_test_tempdir"); + std::env::set_var("STORE_PATH", temp_dir.to_str().unwrap()); + + tokio::fs::create_dir_all(temp_dir.join("103151").join("cache")) + .await + .unwrap(); + + + CandidateService::add_cover_letter_to_cache(103151, vec![0]).await.unwrap(); + + assert!(tokio::fs::metadata(temp_dir.join("103151").join("cache").join("MOTIVACNI_DOPIS.pdf")).await.is_ok()); + + tokio::fs::remove_dir_all(temp_dir).await.unwrap(); + } + + #[tokio::test] + async fn test_add_portfolio_letter_to_cache() { + let temp_dir = std::env::temp_dir().join("portfolio_test_tempdir"); + std::env::set_var("STORE_PATH", temp_dir.to_str().unwrap()); + + tokio::fs::create_dir_all(temp_dir.join("103151").join("cache")) + .await + .unwrap(); + + + CandidateService::add_portfolio_letter_to_cache(103151, vec![0]).await.unwrap(); + + assert!(tokio::fs::metadata(temp_dir.join("103151").join("cache").join("PORTFOLIO.pdf")).await.is_ok()); + + tokio::fs::remove_dir_all(temp_dir).await.unwrap(); + } + + #[tokio::test] + async fn test_add_portfolio_zip_to_cache() { + let temp_dir = std::env::temp_dir().join("portfolio_test_tempdir"); + std::env::set_var("STORE_PATH", temp_dir.to_str().unwrap()); + + tokio::fs::create_dir_all(temp_dir.join("103151").join("cache")) + .await + .unwrap(); + + + CandidateService::add_portfolio_zip_to_cache(103151, vec![0]).await.unwrap(); + + assert!(tokio::fs::metadata(temp_dir.join("103151").join("cache").join("PORTFOLIO.zip")).await.is_ok()); + + tokio::fs::remove_dir_all(temp_dir).await.unwrap(); + } } From ee12ad3925b8fe273900faa45ebfa625aa029d57 Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 17:45:12 +0100 Subject: [PATCH 17/19] refactor: refactor write tests --- core/src/services/candidate_service.rs | 63 +++++++++++++++----------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index c6fe20c..7336c1d 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -354,6 +354,9 @@ mod tests { use crate::candidate_details::ApplicationDetails; use crate::services::application_service::ApplicationService; + use std::path::{PathBuf}; + + #[tokio::test] async fn test_application_id_validation() { assert!(CandidateService::is_application_id_valid(101_101)); @@ -459,6 +462,27 @@ mod tests { assert_eq!(dec_details.parent_surname, "test"); } + #[cfg(test)] + async fn create_data_store_temp_dir(application_id: i32) -> (PathBuf, PathBuf, PathBuf) { + + let random_number: u32 = rand::Rng::gen(&mut rand::thread_rng()); + + let temp_dir = std::env::temp_dir().join("portfolio_test_tempdir").join(random_number.to_string()); + let application_dir = temp_dir.join(application_id.to_string()); + let application_cache_dir = application_dir.join("cache"); + + tokio::fs::create_dir_all(application_cache_dir.clone()).await.unwrap(); + + std::env::set_var("STORE_PATH", temp_dir.to_str().unwrap()); + + (temp_dir, application_dir, application_cache_dir) + } + + #[cfg(test)] + async fn clear_data_store_temp_dir(temp_dir: PathBuf) { + tokio::fs::remove_dir_all(temp_dir).await.unwrap(); + } + #[tokio::test] async fn test_folder_creation() { let db = get_memory_sqlite_connection().await; @@ -497,52 +521,37 @@ mod tests { #[tokio::test] async fn test_add_cover_letter_to_cache() { - let temp_dir = std::env::temp_dir().join("portfolio_test_tempdir"); - std::env::set_var("STORE_PATH", temp_dir.to_str().unwrap()); + const APPLICATION_ID: i32 = 103151; + let (temp_dir, _, application_cache_dir) = create_data_store_temp_dir(APPLICATION_ID).await; - tokio::fs::create_dir_all(temp_dir.join("103151").join("cache")) - .await - .unwrap(); - - CandidateService::add_cover_letter_to_cache(103151, vec![0]).await.unwrap(); - assert!(tokio::fs::metadata(temp_dir.join("103151").join("cache").join("MOTIVACNI_DOPIS.pdf")).await.is_ok()); + assert!(tokio::fs::metadata(application_cache_dir.join("MOTIVACNI_DOPIS.pdf")).await.is_ok()); - tokio::fs::remove_dir_all(temp_dir).await.unwrap(); + clear_data_store_temp_dir(temp_dir).await; } #[tokio::test] async fn test_add_portfolio_letter_to_cache() { - let temp_dir = std::env::temp_dir().join("portfolio_test_tempdir"); - std::env::set_var("STORE_PATH", temp_dir.to_str().unwrap()); - - tokio::fs::create_dir_all(temp_dir.join("103151").join("cache")) - .await - .unwrap(); - + const APPLICATION_ID: i32 = 103151; + let (temp_dir, _, application_cache_dir) = create_data_store_temp_dir(APPLICATION_ID).await; CandidateService::add_portfolio_letter_to_cache(103151, vec![0]).await.unwrap(); - assert!(tokio::fs::metadata(temp_dir.join("103151").join("cache").join("PORTFOLIO.pdf")).await.is_ok()); + assert!(tokio::fs::metadata(application_cache_dir.join("PORTFOLIO.pdf")).await.is_ok()); - tokio::fs::remove_dir_all(temp_dir).await.unwrap(); + clear_data_store_temp_dir(temp_dir).await; } #[tokio::test] async fn test_add_portfolio_zip_to_cache() { - let temp_dir = std::env::temp_dir().join("portfolio_test_tempdir"); - std::env::set_var("STORE_PATH", temp_dir.to_str().unwrap()); + const APPLICATION_ID: i32 = 103151; + let (temp_dir, _, application_cache_dir) = create_data_store_temp_dir(APPLICATION_ID).await; - tokio::fs::create_dir_all(temp_dir.join("103151").join("cache")) - .await - .unwrap(); - - CandidateService::add_portfolio_zip_to_cache(103151, vec![0]).await.unwrap(); - assert!(tokio::fs::metadata(temp_dir.join("103151").join("cache").join("PORTFOLIO.zip")).await.is_ok()); + assert!(tokio::fs::metadata(application_cache_dir.join("PORTFOLIO.zip")).await.is_ok()); - tokio::fs::remove_dir_all(temp_dir).await.unwrap(); + clear_data_store_temp_dir(temp_dir).await; } } From 5431335a2a11f01e135e7ee6c5126d06908140f7 Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 20:22:43 +0100 Subject: [PATCH 18/19] feat: add serial macro for tests, add more tests --- Cargo.lock | 27 ++++ core/Cargo.toml | 3 +- core/src/services/candidate_service.rs | 200 +++++++++++++++++++++++-- 3 files changed, 215 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91afc04..6ae13ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2025,6 +2025,7 @@ dependencies = [ "sea-orm", "secrecy", "serde", + "serial_test", "thiserror", "tokio", ] @@ -2708,6 +2709,32 @@ dependencies = [ "serde", ] +[[package]] +name = "serial_test" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92761393ee4dc3ff8f4af487bd58f4307c9329bbedea02cac0089ad9c411e153" +dependencies = [ + "dashmap", + "futures", + "lazy_static", + "log", + "parking_lot 0.12.1", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6f5d1c3087fb119617cff2966fe3808a80e5eb59a8c1601d5994d66f4346a5" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha1" version = "0.10.5" diff --git a/core/Cargo.toml b/core/Cargo.toml index bf6c2ac..b2f28bc 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -47,4 +47,5 @@ features = [ [dev-dependencies] tokio = { version = "^1.21", features = ["macros"] } -async-tempfile = "^0.2" \ No newline at end of file +async-tempfile = "^0.2" +serial_test = "0.9.0" \ No newline at end of file diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 7336c1d..137a6ef 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -343,6 +343,7 @@ impl CandidateService { #[cfg(test)] mod tests { use sea_orm::{Database, DbConn}; + use serial_test::serial; use crate::util::get_memory_sqlite_connection; use crate::{crypto, services::candidate_service::CandidateService, Mutation}; @@ -484,47 +485,53 @@ mod tests { } #[tokio::test] + #[serial] async fn test_folder_creation() { + const APPLICATION_ID: i32 = 103151; + let db = get_memory_sqlite_connection().await; let plain_text_password = "test".to_string(); - let temp_dir = std::env::temp_dir().join("portfolio_test_tempdir"); + let temp_dir = std::env::temp_dir().join("portfolio_test_tempdir").join("create_folder"); std::env::set_var("STORE_PATH", temp_dir.to_str().unwrap()); - CandidateService::create(&db, 103151, &plain_text_password, "".to_string()) + eprintln!("{}", std::env::var("STORE_PATH").unwrap()); + CandidateService::create(&db, APPLICATION_ID, &plain_text_password, "".to_string()) .await .ok() .unwrap(); - assert!(tokio::fs::metadata(temp_dir.join("103151")).await.is_ok()); - assert!(tokio::fs::metadata(temp_dir.join("103151").join("cache")).await.is_ok()); + eprintln!("{}", std::env::var("STORE_PATH").unwrap()); + + assert!(tokio::fs::metadata(temp_dir.join(APPLICATION_ID.to_string())).await.is_ok()); + assert!(tokio::fs::metadata(temp_dir.join(APPLICATION_ID.to_string()).join("cache")).await.is_ok()); + + eprintln!("{}", std::env::var("STORE_PATH").unwrap()); tokio::fs::remove_dir_all(temp_dir).await.unwrap(); } #[tokio::test] + #[serial] async fn test_write_portfolio_file() { - let temp_dir = std::env::temp_dir().join("portfolio_test_tempdir"); - std::env::set_var("STORE_PATH", temp_dir.to_str().unwrap()); + const APPLICATION_ID: i32 = 103151; - tokio::fs::create_dir_all(temp_dir.join("103151").join("cache")) - .await - .unwrap(); + let (temp_dir, _, application_cache_dir) = create_data_store_temp_dir(APPLICATION_ID).await; + CandidateService::write_portfolio_file(APPLICATION_ID, vec![0], "test").await.unwrap(); - CandidateService::write_portfolio_file(103151, vec![0], "test").await.unwrap(); - - assert!(tokio::fs::metadata(temp_dir.join("103151").join("cache").join("test")).await.is_ok()); + assert!(tokio::fs::metadata(application_cache_dir.join("test")).await.is_ok()); - tokio::fs::remove_dir_all(temp_dir).await.unwrap(); + clear_data_store_temp_dir(temp_dir).await; } #[tokio::test] + #[serial] async fn test_add_cover_letter_to_cache() { const APPLICATION_ID: i32 = 103151; let (temp_dir, _, application_cache_dir) = create_data_store_temp_dir(APPLICATION_ID).await; - CandidateService::add_cover_letter_to_cache(103151, vec![0]).await.unwrap(); + CandidateService::add_cover_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); assert!(tokio::fs::metadata(application_cache_dir.join("MOTIVACNI_DOPIS.pdf")).await.is_ok()); @@ -532,6 +539,20 @@ mod tests { } #[tokio::test] + #[serial] + async fn test_is_cover_letter() { + const APPLICATION_ID: i32 = 103151; + let (temp_dir, _, _) = create_data_store_temp_dir(APPLICATION_ID).await; + + CandidateService::add_cover_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + + assert!(CandidateService::is_cover_letter(APPLICATION_ID).await); + + clear_data_store_temp_dir(temp_dir).await; + } + + #[tokio::test] + #[serial] async fn test_add_portfolio_letter_to_cache() { const APPLICATION_ID: i32 = 103151; let (temp_dir, _, application_cache_dir) = create_data_store_temp_dir(APPLICATION_ID).await; @@ -544,6 +565,20 @@ mod tests { } #[tokio::test] + #[serial] + async fn test_is_portfolio_letter() { + const APPLICATION_ID: i32 = 103151; + let (temp_dir, _, _) = create_data_store_temp_dir(APPLICATION_ID).await; + + CandidateService::add_portfolio_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + + assert!(CandidateService::is_portfolio_letter(APPLICATION_ID).await); + + clear_data_store_temp_dir(temp_dir).await; + } + + #[tokio::test] + #[serial] async fn test_add_portfolio_zip_to_cache() { const APPLICATION_ID: i32 = 103151; let (temp_dir, _, application_cache_dir) = create_data_store_temp_dir(APPLICATION_ID).await; @@ -554,4 +589,141 @@ mod tests { clear_data_store_temp_dir(temp_dir).await; } + + #[tokio::test] + #[serial] + async fn test_is_portfolio_zip() { + const APPLICATION_ID: i32 = 103151; + let (temp_dir, _, _) = create_data_store_temp_dir(APPLICATION_ID).await; + + CandidateService::add_portfolio_zip_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + + assert!(CandidateService::is_portfolio_zip(APPLICATION_ID).await); + + clear_data_store_temp_dir(temp_dir).await; + } + + #[tokio::test] + #[serial] + async fn test_is_portfolio_prepared() { + const APPLICATION_ID: i32 = 103151; + let (temp_dir, _, _) = create_data_store_temp_dir(APPLICATION_ID).await; + + CandidateService::add_cover_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + CandidateService::add_portfolio_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + CandidateService::add_portfolio_zip_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + + assert!(CandidateService::is_portfolio_prepared(APPLICATION_ID).await); + + clear_data_store_temp_dir(temp_dir).await; + + let (temp_dir, _, _) = create_data_store_temp_dir(APPLICATION_ID).await; + + CandidateService::add_cover_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + //CandidateService::add_portfolio_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + CandidateService::add_portfolio_zip_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + + assert!(!CandidateService::is_portfolio_prepared(APPLICATION_ID).await); + + clear_data_store_temp_dir(temp_dir).await; + } + + #[tokio::test] + #[serial] + async fn test_delete_cache() { + const APPLICATION_ID: i32 = 103151; + let (temp_dir, _, _) = create_data_store_temp_dir(APPLICATION_ID).await; + + CandidateService::add_portfolio_zip_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + + assert!(CandidateService::is_portfolio_zip(APPLICATION_ID).await); + + CandidateService::delete_cache(APPLICATION_ID).await.unwrap(); + + assert!(!CandidateService::is_portfolio_zip(APPLICATION_ID).await); + + clear_data_store_temp_dir(temp_dir).await; + } + + #[tokio::test] + #[serial] + async fn test_add_portfolio() { + const APPLICATION_ID: i32 = 103151; + let (temp_dir, application_dir, _) = create_data_store_temp_dir(APPLICATION_ID).await; + + let db = get_memory_sqlite_connection().await; + put_user_data(&db).await; + + CandidateService::add_cover_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + CandidateService::add_portfolio_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + CandidateService::add_portfolio_zip_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + + CandidateService::add_portfolio(APPLICATION_ID, &db).await.unwrap(); + + assert!(tokio::fs::metadata(application_dir.join("PORTFOLIO.age")).await.is_ok()); + + clear_data_store_temp_dir(temp_dir).await; + } + + #[tokio::test] + #[serial] + async fn test_delete_portfolio() { + const APPLICATION_ID: i32 = 103151; + let (temp_dir, application_dir, _) = create_data_store_temp_dir(APPLICATION_ID).await; + + let db = get_memory_sqlite_connection().await; + put_user_data(&db).await; + + CandidateService::add_cover_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + CandidateService::add_portfolio_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + CandidateService::add_portfolio_zip_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + + CandidateService::add_portfolio(APPLICATION_ID, &db).await.unwrap(); + + assert!(tokio::fs::metadata(application_dir.join("PORTFOLIO.age")).await.is_ok()); + + CandidateService::delete_portfolio(APPLICATION_ID).await.unwrap(); + + assert!(!tokio::fs::metadata(application_dir.join("PORTFOLIO.age")).await.is_ok()); + + clear_data_store_temp_dir(temp_dir).await; + } + + #[tokio::test] + #[serial] + async fn test_is_portfolio_submitted() { + const APPLICATION_ID: i32 = 103151; + let (temp_dir, _, _) = create_data_store_temp_dir(APPLICATION_ID).await; + + let db = get_memory_sqlite_connection().await; + put_user_data(&db).await; + + CandidateService::add_cover_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + CandidateService::add_portfolio_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + CandidateService::add_portfolio_zip_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + + CandidateService::add_portfolio(APPLICATION_ID, &db).await.unwrap(); + + assert!(CandidateService::is_portfolio_submitted(APPLICATION_ID).await); + + clear_data_store_temp_dir(temp_dir).await; + + let (temp_dir, _, _) = create_data_store_temp_dir(APPLICATION_ID).await; + + let db = get_memory_sqlite_connection().await; + put_user_data(&db).await; + + CandidateService::add_cover_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + //CandidateService::add_portfolio_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + CandidateService::add_portfolio_zip_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + + CandidateService::add_portfolio(APPLICATION_ID, &db).await.unwrap(); + + assert!(!CandidateService::is_portfolio_submitted(APPLICATION_ID).await); + + clear_data_store_temp_dir(temp_dir).await; + + + } + } From e2e95461203575d727c5ff8d43fbb658ef8eae65 Mon Sep 17 00:00:00 2001 From: EETagent Date: Wed, 16 Nov 2022 20:29:59 +0100 Subject: [PATCH 19/19] fix: fix tests, same APPLICATION_ID for all test cases --- core/src/services/candidate_service.rs | 43 +++++++------------------- 1 file changed, 11 insertions(+), 32 deletions(-) diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 137a6ef..f57dba9 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -357,6 +357,7 @@ mod tests { use std::path::{PathBuf}; + const APPLICATION_ID: i32 = 103151; #[tokio::test] async fn test_application_id_validation() { @@ -377,12 +378,12 @@ mod tests { let secret_message = "trnka".to_string(); - let candidate = CandidateService::create(&db, 103151, &plain_text_password, "".to_string()) + let candidate = CandidateService::create(&db, APPLICATION_ID, &plain_text_password, "".to_string()) .await .ok() .unwrap(); - Mutation::create_parent(&db, 103151).await.unwrap(); + Mutation::create_parent(&db, APPLICATION_ID).await.unwrap(); let encrypted_message = crypto::encrypt_password_with_recipients(&secret_message, &vec![&candidate.public_key]) @@ -405,9 +406,9 @@ mod tests { #[cfg(test)] async fn put_user_data(db: &DbConn) -> (candidate::Model, parent::Model) { let plain_text_password = "test".to_string(); - let (candidate, parent) = ApplicationService::create_candidate_with_parent( + let (candidate, _parent) = ApplicationService::create_candidate_with_parent( &db, - 103151, + APPLICATION_ID, &plain_text_password, "".to_string(), ) @@ -465,7 +466,6 @@ mod tests { #[cfg(test)] async fn create_data_store_temp_dir(application_id: i32) -> (PathBuf, PathBuf, PathBuf) { - let random_number: u32 = rand::Rng::gen(&mut rand::thread_rng()); let temp_dir = std::env::temp_dir().join("portfolio_test_tempdir").join(random_number.to_string()); @@ -487,35 +487,26 @@ mod tests { #[tokio::test] #[serial] async fn test_folder_creation() { - const APPLICATION_ID: i32 = 103151; - let db = get_memory_sqlite_connection().await; let plain_text_password = "test".to_string(); let temp_dir = std::env::temp_dir().join("portfolio_test_tempdir").join("create_folder"); std::env::set_var("STORE_PATH", temp_dir.to_str().unwrap()); - eprintln!("{}", std::env::var("STORE_PATH").unwrap()); CandidateService::create(&db, APPLICATION_ID, &plain_text_password, "".to_string()) .await .ok() .unwrap(); - eprintln!("{}", std::env::var("STORE_PATH").unwrap()); - assert!(tokio::fs::metadata(temp_dir.join(APPLICATION_ID.to_string())).await.is_ok()); assert!(tokio::fs::metadata(temp_dir.join(APPLICATION_ID.to_string()).join("cache")).await.is_ok()); - eprintln!("{}", std::env::var("STORE_PATH").unwrap()); - tokio::fs::remove_dir_all(temp_dir).await.unwrap(); } #[tokio::test] #[serial] async fn test_write_portfolio_file() { - const APPLICATION_ID: i32 = 103151; - let (temp_dir, _, application_cache_dir) = create_data_store_temp_dir(APPLICATION_ID).await; CandidateService::write_portfolio_file(APPLICATION_ID, vec![0], "test").await.unwrap(); @@ -528,7 +519,6 @@ mod tests { #[tokio::test] #[serial] async fn test_add_cover_letter_to_cache() { - const APPLICATION_ID: i32 = 103151; let (temp_dir, _, application_cache_dir) = create_data_store_temp_dir(APPLICATION_ID).await; CandidateService::add_cover_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); @@ -541,7 +531,6 @@ mod tests { #[tokio::test] #[serial] async fn test_is_cover_letter() { - const APPLICATION_ID: i32 = 103151; let (temp_dir, _, _) = create_data_store_temp_dir(APPLICATION_ID).await; CandidateService::add_cover_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); @@ -554,10 +543,9 @@ mod tests { #[tokio::test] #[serial] async fn test_add_portfolio_letter_to_cache() { - const APPLICATION_ID: i32 = 103151; let (temp_dir, _, application_cache_dir) = create_data_store_temp_dir(APPLICATION_ID).await; - CandidateService::add_portfolio_letter_to_cache(103151, vec![0]).await.unwrap(); + CandidateService::add_portfolio_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); assert!(tokio::fs::metadata(application_cache_dir.join("PORTFOLIO.pdf")).await.is_ok()); @@ -567,7 +555,6 @@ mod tests { #[tokio::test] #[serial] async fn test_is_portfolio_letter() { - const APPLICATION_ID: i32 = 103151; let (temp_dir, _, _) = create_data_store_temp_dir(APPLICATION_ID).await; CandidateService::add_portfolio_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); @@ -580,10 +567,9 @@ mod tests { #[tokio::test] #[serial] async fn test_add_portfolio_zip_to_cache() { - const APPLICATION_ID: i32 = 103151; let (temp_dir, _, application_cache_dir) = create_data_store_temp_dir(APPLICATION_ID).await; - CandidateService::add_portfolio_zip_to_cache(103151, vec![0]).await.unwrap(); + CandidateService::add_portfolio_zip_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); assert!(tokio::fs::metadata(application_cache_dir.join("PORTFOLIO.zip")).await.is_ok()); @@ -593,7 +579,6 @@ mod tests { #[tokio::test] #[serial] async fn test_is_portfolio_zip() { - const APPLICATION_ID: i32 = 103151; let (temp_dir, _, _) = create_data_store_temp_dir(APPLICATION_ID).await; CandidateService::add_portfolio_zip_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); @@ -606,7 +591,6 @@ mod tests { #[tokio::test] #[serial] async fn test_is_portfolio_prepared() { - const APPLICATION_ID: i32 = 103151; let (temp_dir, _, _) = create_data_store_temp_dir(APPLICATION_ID).await; CandidateService::add_cover_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); @@ -631,7 +615,6 @@ mod tests { #[tokio::test] #[serial] async fn test_delete_cache() { - const APPLICATION_ID: i32 = 103151; let (temp_dir, _, _) = create_data_store_temp_dir(APPLICATION_ID).await; CandidateService::add_portfolio_zip_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); @@ -648,7 +631,6 @@ mod tests { #[tokio::test] #[serial] async fn test_add_portfolio() { - const APPLICATION_ID: i32 = 103151; let (temp_dir, application_dir, _) = create_data_store_temp_dir(APPLICATION_ID).await; let db = get_memory_sqlite_connection().await; @@ -668,7 +650,6 @@ mod tests { #[tokio::test] #[serial] async fn test_delete_portfolio() { - const APPLICATION_ID: i32 = 103151; let (temp_dir, application_dir, _) = create_data_store_temp_dir(APPLICATION_ID).await; let db = get_memory_sqlite_connection().await; @@ -692,7 +673,6 @@ mod tests { #[tokio::test] #[serial] async fn test_is_portfolio_submitted() { - const APPLICATION_ID: i32 = 103151; let (temp_dir, _, _) = create_data_store_temp_dir(APPLICATION_ID).await; let db = get_memory_sqlite_connection().await; @@ -708,16 +688,15 @@ mod tests { clear_data_store_temp_dir(temp_dir).await; - let (temp_dir, _, _) = create_data_store_temp_dir(APPLICATION_ID).await; - - let db = get_memory_sqlite_connection().await; - put_user_data(&db).await; + let (temp_dir, application_dir, _) = create_data_store_temp_dir(APPLICATION_ID).await; CandidateService::add_cover_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); - //CandidateService::add_portfolio_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); + CandidateService::add_portfolio_letter_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); CandidateService::add_portfolio_zip_to_cache(APPLICATION_ID, vec![0]).await.unwrap(); CandidateService::add_portfolio(APPLICATION_ID, &db).await.unwrap(); + + tokio::fs::remove_file(application_dir.join("PORTFOLIO.age")).await.unwrap(); assert!(!CandidateService::is_portfolio_submitted(APPLICATION_ID).await);