From dcdc8a1737e9ef15914670c8e8d8c58b2a10b9dd Mon Sep 17 00:00:00 2001 From: Manuel Gugger Date: Fri, 13 Jan 2023 14:54:16 +0100 Subject: [PATCH] add more tests --- src/routes/delete.rs | 27 ++- src/routes/mod.rs | 2 +- tests/crud_get_resp_is_success.rs | 48 ----- tests/delete_is_success.rs | 162 +++++++++++++++++ tests/get_request_is_success.rs | 164 ++++++++++++++++++ ....rs => post_create_and_edit_is_success.rs} | 77 ++++---- tests/test_setup/comment.rs | 1 + tests/test_setup/helper.rs | 110 ++++++++---- tests/test_setup/mod.rs | 13 ++ 9 files changed, 473 insertions(+), 131 deletions(-) delete mode 100644 tests/crud_get_resp_is_success.rs create mode 100644 tests/delete_is_success.rs create mode 100644 tests/get_request_is_success.rs rename tests/{crud_post_resp_is_success.rs => post_create_and_edit_is_success.rs} (76%) diff --git a/src/routes/delete.rs b/src/routes/delete.rs index 47c2ba8..6c05402 100644 --- a/src/routes/delete.rs +++ b/src/routes/delete.rs @@ -57,7 +57,7 @@ pub async fn delete_many session: Session, _req: HttpRequest, data: web::Data, - text: String, + form: web::Form>, ) -> Result { let actix_admin = data.get_actix_admin(); let entity_name = E::get_entity_name(); @@ -73,14 +73,11 @@ pub async fn delete_many let db = &data.get_db(); let entity_name = E::get_entity_name(); - let entity_ids: Vec = text - .split("&") - .filter(|id| !id.is_empty()) - .map(|id_str| id_str.replace("ids=", "").parse::().unwrap()) - .collect(); + + let ids: Vec = form.iter().map(|el| el.1).collect(); // TODO: implement delete_many - for id in entity_ids { + for id in ids { let model_result = E::get_entity(db, id).await; let delete_result = E::delete_entity(db, id).await; match (delete_result, model_result) { @@ -91,13 +88,15 @@ pub async fn delete_many let file_name = model .get_value::(&field.field_name, true, true) .unwrap_or_default(); - let file_path = format!( - "{}/{}/{}", - actix_admin.configuration.file_upload_directory, - E::get_entity_name(), - file_name.unwrap_or_default() - ); - std::fs::remove_file(file_path)?; + if file_name.is_some() { + let file_path = format!( + "{}/{}/{}", + actix_admin.configuration.file_upload_directory, + E::get_entity_name(), + file_name.unwrap_or_default() + ); + std::fs::remove_file(file_path)?; + } } } } diff --git a/src/routes/mod.rs b/src/routes/mod.rs index d560b39..adcd615 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -8,7 +8,7 @@ mod index; pub use index::{ index, not_found, get_admin_ctx }; mod list; -pub use list::list; +pub use list::{ list }; mod show; pub use show::show; diff --git a/tests/crud_get_resp_is_success.rs b/tests/crud_get_resp_is_success.rs deleted file mode 100644 index c89302e..0000000 --- a/tests/crud_get_resp_is_success.rs +++ /dev/null @@ -1,48 +0,0 @@ -mod test_setup; -use test_setup::helper::{AppState, create_tables_and_get_connection, create_actix_admin_builder}; - -#[cfg(test)] -mod tests { - extern crate serde_derive; - use actix_admin::prelude::*; - use actix_web::test; - use actix_web::{web, App}; - use super::create_app; - - #[actix_web::test] - async fn admin_index_get() { - test_get_is_success("/admin/").await - } - - #[actix_web::test] - async fn post_list_get() { - test_get_is_success("/admin/post/list").await - } - - #[actix_web::test] - async fn comment_list_get() { - test_get_is_success("/admin/comment/list").await - } - - #[actix_web::test] - async fn post_create_get() { - test_get_is_success("/admin/post/create").await - } - - #[actix_web::test] - async fn comment_create_get() { - test_get_is_success("/admin/comment/create").await - } - - async fn test_get_is_success(url: &str) { - let db = super::create_tables_and_get_connection().await; - let app = create_app!(db); - - let req = test::TestRequest::get() - .uri(url) - .to_request(); - let resp = test::call_service(&app, req).await; - - assert!(resp.status().is_success()); - } -} diff --git a/tests/delete_is_success.rs b/tests/delete_is_success.rs new file mode 100644 index 0000000..267f930 --- /dev/null +++ b/tests/delete_is_success.rs @@ -0,0 +1,162 @@ +mod test_setup; +use test_setup::prelude::*; + +#[cfg(test)] +mod post_delete_is_success { + use actix_admin::prelude::*; + use actix_web::{http::header::ContentType, test, App}; + use itertools::Itertools; + use sea_orm::{ + sea_query::{Expr, Value}, + ColumnTrait, EntityTrait, QueryFilter, + }; + + use crate::create_app; + + #[actix_web::test] + async fn post_delete() { + let db = super::setup_db(true).await; + let app = create_app!(db); + let id = 1; + let entity = super::test_setup::Post::find_by_id(id) + .one(&db) + .await + .unwrap(); + assert!(entity.is_some()); + + let uri = format!("/admin/post/delete/{}", id); + let req = test::TestRequest::delete().uri(&uri).to_request(); + let resp = test::call_service(&app, req).await; + + // Delete should fail due to foreign key + assert!(!resp.status().is_success()); + + let comment_delete_res = super::test_setup::Comment::delete_by_id(id) + .exec(&db) + .await + .unwrap(); + assert_eq!(comment_delete_res.rows_affected, 1); + + let uri = format!("/admin/post/delete/{}", id); + let req = test::TestRequest::delete().uri(&uri).to_request(); + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_success()); + + let entity_after_delete = super::test_setup::Post::find_by_id(id) + .one(&db) + .await + .unwrap(); + assert!(entity_after_delete.is_none()); + } + + #[actix_web::test] + async fn comment_delete() { + let db = super::setup_db(true).await; + let app = create_app!(db); + let id = 1; + let entity = super::test_setup::Comment::find_by_id(id) + .one(&db) + .await + .unwrap(); + assert!(entity.is_some()); + + let uri = format!("/admin/comment/delete/{}", id); + let req = test::TestRequest::delete().uri(&uri).to_request(); + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_success()); + + let entity_after_delete = super::test_setup::Comment::find_by_id(id) + .one(&db) + .await + .unwrap(); + assert!(entity_after_delete.is_none()); + } + + #[actix_web::test] + async fn comment_delete_many() { + let db = super::setup_db(true).await; + let app = create_app!(db); + let ids = vec![1, 2, 3]; + for id in &ids { + let entity = super::test_setup::Comment::find_by_id(*id) + .one(&db) + .await + .unwrap(); + assert!(entity.is_some()); + } + + let payload: String = ids.iter().map(|i| format!("ids={}", i)).join("&"); + let ids_payload = payload.into_bytes(); + let req = test::TestRequest::delete() + .uri("/admin/comment/delete") + .insert_header(ContentType::form_url_encoded()) + .set_payload(ids_payload) + .to_request(); + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_redirection()); + + for id in ids { + let entity_after_delete = super::test_setup::Comment::find_by_id(id) + .one(&db) + .await + .unwrap(); + assert!(entity_after_delete.is_none()); + } + } + + #[actix_web::test] + async fn post_delete_many() { + let db = super::setup_db(true).await; + let app = create_app!(db); + let ids = vec![1, 2, 3]; + for id in &ids { + let entity = super::test_setup::Post::find_by_id(*id) + .one(&db) + .await + .unwrap(); + assert!(entity.is_some()); + } + + let payload: String = ids.iter().map(|i| format!("ids={}", i)).join("&"); + let ids_payload = payload.into_bytes(); + let req = test::TestRequest::delete() + .uri("/admin/post/delete") + .insert_header(ContentType::form_url_encoded()) + .set_payload(ids_payload.clone()) + .to_request(); + let resp = test::call_service(&app, req).await; + + // Fails because of FK constraints + assert!(resp.status().is_server_error()); + + // Remove FK + let update_res = super::test_setup::Comment::update_many() + .col_expr( + super::test_setup::comment::Column::PostId, + Expr::value(Value::Int(None)), + ) + .filter(super::test_setup::comment::Column::PostId.is_in(ids.clone())) + .exec(&db) + .await; + assert!(update_res.is_ok()); + + // Delete again + let req = test::TestRequest::delete() + .uri("/admin/post/delete") + .insert_header(ContentType::form_url_encoded()) + .set_payload(ids_payload) + .to_request(); + let resp = test::call_service(&app, req).await; + + // Should not fail anymore and redirect correctly + assert!(resp.status().is_redirection()); + + for id in ids { + let entity_after_delete = super::test_setup::Post::find_by_id(id) + .one(&db) + .await + .unwrap(); + assert!(entity_after_delete.is_none()); + } + } +} diff --git a/tests/get_request_is_success.rs b/tests/get_request_is_success.rs new file mode 100644 index 0000000..8e15bcc --- /dev/null +++ b/tests/get_request_is_success.rs @@ -0,0 +1,164 @@ +mod test_setup; +use test_setup::prelude::*; + +#[cfg(test)] +mod get_request_is_success { + extern crate serde_derive; + use actix_admin::prelude::*; + use actix_web::body::to_bytes; + use actix_web::test; + use actix_web::App; + use sea_orm::DatabaseConnection; + use sea_orm::EntityTrait; + use sea_orm::PaginatorTrait; + use sea_orm::QueryOrder; + use super::create_app; + use super::BodyTest; + + #[actix_web::test] + async fn get_admin_index() { + let db = super::setup_db(false).await; + test_get_is_success("/admin/", &db).await + } + + #[actix_web::test] + async fn get_post_list() { + let db = super::setup_db(true).await; + let url = format!("/admin/{}/list", crate::Post::get_entity_name()); + test_get_is_success(url.as_str(), &db).await + } + + #[actix_web::test] + async fn get_post_list_page() { + let db = super::setup_db(true).await; + let page = 5; + let page_size = 50; // Verify with default size in list.rs + let url = format!("/admin/{}/list?page={}&entities_per_page={}", crate::Post::get_entity_name(), page, page_size); + + let entities = crate::Post::find() + .order_by_asc(crate::post::Column::Id) + .paginate(&db, page_size) + .fetch_page(page-1) + .await + .unwrap(); + + let verify_titles = entities.iter().map(|e| e.title.to_string()).collect(); + test_response_contains(url.as_str(), &db, verify_titles).await + } + + #[actix_web::test] + async fn get_post_list_search() { + let db = super::setup_db(true).await; + let url = format!("/admin/{}/list?search=Test%20155", crate::Post::get_entity_name()); + + test_response_contains(url.as_str(), &db, vec!("Test 155".to_string())).await + } + + #[actix_web::test] + async fn get_comment_list_search() { + let db = super::setup_db(true).await; + let search_string_encoded = "Test%2015"; + let entities_per_page = 11; + let url = format!("/admin/{}/list?search={}&entities_per_page={}", crate::Comment::get_entity_name(), search_string_encoded, entities_per_page); + + let mut elements_to_verify = Vec::new(); + elements_to_verify.push("Test 15".to_string()); + for i in 150..159 { + elements_to_verify.push(format!("Test {}", i)); + } + + test_response_contains(url.as_str(), &db, elements_to_verify).await + } + + #[actix_web::test] + async fn get_comment_list_page() { + let db = super::setup_db(true).await; + let page = 17; + + let page_size = 20; // Verify with default size in list.rs + let url = format!("/admin/{}/list?page={}&entities_per_page={}", crate::Comment::get_entity_name(), page, page_size); + + let entities = crate::Comment::find() + .order_by_asc(crate::comment::Column::Id) + .paginate(&db, page_size) + .fetch_page(page-1) + .await + .unwrap(); + + let verify_comments = entities.iter().map(|e| e.comment.to_string()).collect(); + test_response_contains(url.as_str(), &db, verify_comments).await + } + + #[actix_web::test] + async fn get_comment_list() { + let db = super::setup_db(true).await; + let url = format!("/admin/{}/list", crate::Comment::get_entity_name()); + test_get_is_success(url.as_str(), &db).await + } + + #[actix_web::test] + async fn get_post_create() { + let db = super::setup_db(false).await; + let url = format!("/admin/{}/create", crate::Post::get_entity_name()); + test_get_is_success(url.as_str(), &db).await + } + + #[actix_web::test] + async fn get_comment_create() { + let db = super::setup_db(false).await; + let url = format!("/admin/{}/create", crate::Comment::get_entity_name()); + test_get_is_success(url.as_str(), &db).await + } + + #[actix_web::test] + async fn get_comment_show() { + let db = super::setup_db(true).await; + + let url = format!( + "/admin/{}/show/{}", + crate::Comment::get_entity_name(), + 1 + ); + test_get_is_success(url.as_str(), &db).await + } + + #[actix_web::test] + async fn get_post_show() { + let db = super::setup_db(true).await; + + let url = format!( + "/admin/{}/show/{}", + crate::Comment::get_entity_name(), + 1 + ); + test_get_is_success(url.as_str(), &db).await + } + + async fn test_response_contains(url: &str, db: &DatabaseConnection, elements_to_verify: Vec) { + let app = create_app!(db); + + let req = test::TestRequest::get() + .uri(url) + .to_request(); + + let resp = test::call_service(&app, req).await; + let body = to_bytes(resp.into_body()).await.unwrap(); + let body = body.as_str(); + for element in elements_to_verify { + assert!(body.contains(&element), "Body did not contain element {}: \n{}", element, body); + } + + } + + async fn test_get_is_success(url: &str, db: &DatabaseConnection) { + let app = create_app!(db); + + let req = test::TestRequest::get() + .uri(url) + .to_request(); + let resp = test::call_service(&app, req).await; + + assert!(resp.status().is_success()); + } +} + diff --git a/tests/crud_post_resp_is_success.rs b/tests/post_create_and_edit_is_success.rs similarity index 76% rename from tests/crud_post_resp_is_success.rs rename to tests/post_create_and_edit_is_success.rs index abd3946..641ddb5 100644 --- a/tests/crud_post_resp_is_success.rs +++ b/tests/post_create_and_edit_is_success.rs @@ -1,37 +1,45 @@ mod test_setup; -use test_setup::helper::{create_actix_admin_builder, create_tables_and_get_connection, AppState}; +use test_setup::prelude::*; #[cfg(test)] -mod tests { +mod post_create_and_edit_is_success { use actix_admin::prelude::*; - use actix_web::http::header::ContentType; - use actix_web::test; - use actix_web::{web, App}; - use chrono::NaiveDate; - use chrono::NaiveDateTime; + use actix_web::{ + test, + App, + http::header::ContentType + }; + use chrono::{ NaiveDateTime, NaiveDate }; use serde::{Serialize}; - use sea_orm::EntityTrait; - use sea_orm::PaginatorTrait; - use sea_orm::prelude::Decimal; + use sea_orm::{ PaginatorTrait, EntityTrait, prelude::Decimal}; + + use crate::{create_app}; - use crate::create_app; + #[derive(Serialize, Clone)] + pub struct CommentModel { + id: &'static str, + insert_date: &'static str, + comment: &'static str, + user: &'static str, + is_visible: &'static str, + post_id: Option<&'static str>, + my_decimal: &'static str + } + + #[derive(Serialize, Clone)] + pub struct PostModel { + id: &'static str, + title: &'static str, + text: &'static str, + tea_mandatory: &'static str, + insert_date: &'static str, + } #[actix_web::test] async fn comment_create_and_edit() { - let db = super::create_tables_and_get_connection().await; + let db = super::setup_db(false).await; let app = create_app!(db); - #[derive(Serialize, Clone)] - pub struct CommentModel { - id: &'static str, - insert_date: &'static str, - comment: &'static str, - user: &'static str, - is_visible: &'static str, - post_id: Option<&'static str>, - my_decimal: &'static str - } - // create entity let mut model = CommentModel { id: "0", @@ -58,15 +66,15 @@ mod tests { .await .expect("could not retrieve entities"); - assert!(entities.len() == 1, "After post, db does not contain 1 model"); + assert_eq!(entities.len(), 1, "After post, db does not contain 1 model"); let entity = entities.first().unwrap(); - assert!(entity.id == 1); - assert!(entity.comment == "test"); - assert!(entity.user == "test"); + assert_eq!(entity.id, 1); + assert_eq!(entity.comment,"test"); + assert_eq!(entity.user, "test"); assert!(entity.is_visible); assert!(entity.post_id.is_none()); - assert!(entity.my_decimal == Decimal::new(113141, 3)); - assert!(entity.insert_date == NaiveDateTime::parse_from_str("1977-04-01T14:00", "%Y-%m-%dT%H:%M").unwrap()); + assert_eq!(entity.my_decimal, Decimal::new(113141, 3)); + assert_eq!(entity.insert_date, NaiveDateTime::parse_from_str("1977-04-01T14:00", "%Y-%m-%dT%H:%M").unwrap()); // update entity model.my_decimal = "213.141"; @@ -103,18 +111,9 @@ mod tests { #[actix_web::test] async fn post_create_and_edit() { - let db = super::create_tables_and_get_connection().await; + let db = super::setup_db(false).await; let app = create_app!(db); - #[derive(Serialize, Clone)] - pub struct PostModel { - id: &'static str, - title: &'static str, - text: &'static str, - tea_mandatory: &'static str, - insert_date: &'static str, - } - let mut model = PostModel { id: "0", insert_date: "1977-04-01", diff --git a/tests/test_setup/comment.rs b/tests/test_setup/comment.rs index ee56210..8c01c2b 100644 --- a/tests/test_setup/comment.rs +++ b/tests/test_setup/comment.rs @@ -12,6 +12,7 @@ pub struct Model { #[serde(skip_deserializing)] #[actix_admin(primary_key)] pub id: i32, + #[actix_admin(searchable)] pub comment: String, #[sea_orm(column_type = "Text")] #[actix_admin(html_input_type = "email")] diff --git a/tests/test_setup/helper.rs b/tests/test_setup/helper.rs index fc9a057..94a7989 100644 --- a/tests/test_setup/helper.rs +++ b/tests/test_setup/helper.rs @@ -1,19 +1,53 @@ -use sea_orm::{ConnectOptions, DatabaseConnection}; use actix_admin::prelude::*; -use actix_web::Error; use actix_session::Session; +use actix_web::web; +use actix_web::Error; use actix_web::HttpResponse; -use actix_web::{web}; +use actix_web::web::Bytes; +use chrono::Local; +use sea_orm::prelude::Decimal; +use sea_orm::{ConnectOptions, DatabaseConnection, EntityTrait, Set}; -use super::{Post, Comment, create_tables}; +use super::{comment, create_tables, post, Comment, Post}; -pub async fn create_tables_and_get_connection() -> DatabaseConnection { +pub async fn setup_db(create_entities: bool) -> DatabaseConnection { let opt = ConnectOptions::new("sqlite::memory:".to_owned()); - let conn = sea_orm::Database::connect(opt).await.unwrap(); - let _ = create_tables(&conn).await; + let db = sea_orm::Database::connect(opt).await.unwrap(); + let _ = create_tables(&db).await; - conn + if create_entities { + for i in 1..1000 { + let row = post::ActiveModel { + title: Set(format!("Test {}", i)), + text: Set("some content".to_string()), + tea_mandatory: Set(post::Tea::EverydayTea), + tea_optional: Set(None), + insert_date: Set(Local::now().date_naive()), + ..Default::default() + }; + let insert_res = Post::insert(row) + .exec(&db) + .await + .expect("could not insert post"); + + let row = comment::ActiveModel { + comment: Set(format!("Test {}", i)), + user: Set("me@home.com".to_string()), + my_decimal: Set(Decimal::new(105, 0)), + insert_date: Set(Local::now().naive_utc()), + is_visible: Set(i % 2 == 0), + post_id: Set(Some(insert_res.last_insert_id as i32)), + ..Default::default() + }; + let _res = Comment::insert(row) + .exec(&db) + .await + .expect("could not insert comment"); + } + } + + db } #[macro_export] @@ -26,17 +60,16 @@ macro_rules! create_app ( db: conn, actix_admin, }; - + test::init_service( App::new() - .app_data(web::Data::new(app_state.clone())) + .app_data(actix_web::web::Data::new(app_state.clone())) .service(actix_admin_builder.get_scope::()) ) .await }); ); - #[derive(Clone)] pub struct AppState { pub db: DatabaseConnection, @@ -62,7 +95,7 @@ pub fn create_actix_admin_builder() -> ActixAdminBuilder { user_is_logged_in: None, login_link: None, logout_link: None, - file_upload_directory: "./file_uploads" + file_upload_directory: "./file_uploads", }; let mut admin_builder = ActixAdminBuilder::new(configuration); @@ -71,31 +104,36 @@ pub fn create_actix_admin_builder() -> ActixAdminBuilder { admin_builder.add_custom_handler_for_entity::( "Create Comment From Plaintext", - "/create_post_from_plaintext", - web::post().to(create_post_from_plaintext::), false); + "/create_post_from_plaintext", + web::post().to(create_post_from_plaintext::), + false, + ); admin_builder.add_custom_handler_for_entity::( "Create Post From Plaintext", - "/create_post_from_plaintext", - web::post().to(create_post_from_plaintext::), false); + "/create_post_from_plaintext", + web::post().to(create_post_from_plaintext::), + false, + ); admin_builder.add_custom_handler_for_entity::( "Edit Post From Plaintext", - "/edit_post_from_plaintext/{id}", - web::post().to(edit_post_from_plaintext::), false); + "/edit_post_from_plaintext/{id}", + web::post().to(edit_post_from_plaintext::), + false, + ); admin_builder.add_custom_handler_for_entity::( "Edit Comment From Plaintext", - "/edit_post_from_plaintext/{id}", - web::post().to(edit_post_from_plaintext::), false); + "/edit_post_from_plaintext/{id}", + web::post().to(edit_post_from_plaintext::), + false, + ); admin_builder } -async fn create_post_from_plaintext< - T: ActixAdminAppDataTrait, - E: ActixAdminViewModelTrait, ->( +async fn create_post_from_plaintext( session: Session, data: web::Data, text: String, @@ -105,10 +143,7 @@ async fn create_post_from_plaintext< create_or_edit_post::(&session, &data, Ok(model), None, actix_admin).await } -async fn edit_post_from_plaintext< - T: ActixAdminAppDataTrait, - E: ActixAdminViewModelTrait, ->( +async fn edit_post_from_plaintext( session: Session, data: web::Data, text: String, @@ -116,5 +151,22 @@ async fn edit_post_from_plaintext< ) -> Result { let actix_admin = data.get_actix_admin(); let model = ActixAdminModel::from(text); - create_or_edit_post::(&session, &data, Ok(model), Some(id.into_inner()), actix_admin).await + create_or_edit_post::( + &session, + &data, + Ok(model), + Some(id.into_inner()), + actix_admin, + ) + .await +} + +pub trait BodyTest { + fn as_str(&self) -> &str; +} + +impl BodyTest for Bytes { + fn as_str(&self) -> &str { + std::str::from_utf8(self).unwrap() + } } \ No newline at end of file diff --git a/tests/test_setup/mod.rs b/tests/test_setup/mod.rs index c981952..ff163a3 100644 --- a/tests/test_setup/mod.rs +++ b/tests/test_setup/mod.rs @@ -7,6 +7,19 @@ pub mod helper; pub use comment::Entity as Comment; pub use post::Entity as Post; +pub mod prelude { + pub use crate::test_setup::helper::{ + create_actix_admin_builder, + setup_db, + AppState, + BodyTest + }; + pub use super::comment; + pub use super::post; + pub use super::Comment; + pub use super::Post; +} + // setup async fn create_table(db: &DbConn, stmt: &TableCreateStatement) -> Result { let builder = db.get_database_backend();