From a62da4f46f2e52ef9b0fbf7fa5c04e8d718e8ce8 Mon Sep 17 00:00:00 2001 From: Egor Date: Thu, 23 May 2024 17:13:09 +0200 Subject: [PATCH] editoast: refactor projects tests --- editoast/src/modelsv2/projects.rs | 119 +++++++++++-------- editoast/src/views/pagination.rs | 2 +- editoast/src/views/projects.rs | 190 +++++++++++++++++++----------- 3 files changed, 193 insertions(+), 118 deletions(-) diff --git a/editoast/src/modelsv2/projects.rs b/editoast/src/modelsv2/projects.rs index 1a4102d7be7..f76656a3884 100644 --- a/editoast/src/modelsv2/projects.rs +++ b/editoast/src/modelsv2/projects.rs @@ -118,62 +118,96 @@ impl Project { #[cfg(test)] pub mod test { use rstest::rstest; - use std::sync::Arc; + use std::ops::DerefMut; use super::*; - use crate::fixtures::tests::db_pool; - use crate::fixtures::tests::project; - use crate::fixtures::tests::TestFixture; - use crate::modelsv2::DbConnectionPool; - use crate::modelsv2::DeleteStatic; + use crate::modelsv2::prelude::*; + use crate::modelsv2::DbConnectionPoolV2; use crate::modelsv2::Model; - use crate::modelsv2::Retrieve; + + pub fn project_changeset(name: &str) -> Changeset { + Project::changeset() + .name(name.to_owned()) + .budget(Some(0)) + .creation_date(Utc::now().naive_utc()) + .last_modification(Utc::now().naive_utc()) + .tags(Tags::default()) + } + + async fn create_project(db_pool: &DbConnectionPoolV2, name: &str) -> Project { + project_changeset(name) + .create(db_pool.get_ok().deref_mut()) + .await + .expect("Failed to create project") + } #[rstest] - async fn create_delete_project( - #[future] project: TestFixture, - db_pool: Arc, - ) { - let project = project.await; - let conn = &mut db_pool.get().await.unwrap(); - // Delete the project - assert!(Project::delete_static(conn, project.id()).await.unwrap()); - // Second delete should be false - assert!(!Project::delete_static(conn, project.id()).await.unwrap()); + async fn project_creation() { + let db_pool = DbConnectionPoolV2::for_tests(); + let project_name = "test_project_name"; + let created_project = create_project(&db_pool, project_name).await; + assert_eq!(created_project.name, project_name); } #[rstest] - async fn get_project(#[future] project: TestFixture, db_pool: Arc) { - let fixture_project = &project.await.model; - let conn = &mut db_pool.get().await.unwrap(); + async fn project_retrieve() { + let db_pool = DbConnectionPoolV2::for_tests(); + let created_project = create_project(&db_pool, "test_project_name").await; // Get a project - let project = Project::retrieve(conn, fixture_project.id) + let project = Project::retrieve(db_pool.get_ok().deref_mut(), created_project.id) .await - .unwrap() - .unwrap(); - assert_eq!(fixture_project, &project); - assert!(Project::list(conn, SelectionSettings::new()).await.is_ok()); + .expect("Failed to retrieve project") + .expect("Project not found"); + + assert_eq!(&created_project, &project); + + assert!( + Project::list(db_pool.get_ok().deref_mut(), SelectionSettings::new()) + .await + .is_ok() + ); } #[rstest] - async fn sort_project(#[future] project: TestFixture, db_pool: Arc) { - let project = project.await; - let project_2 = project - .model + async fn project_update() { + let db_pool = DbConnectionPoolV2::for_tests(); + let created_project = create_project(&db_pool, "test_project_name").await; + + let project_name = "update_name"; + let project_budget = Some(1000); + + // Patch a project + let update_project = created_project .clone() .into_changeset() - .name(project.model.name.clone() + "_bis"); + .name(project_name.to_owned()) + .budget(project_budget) + .update(db_pool.get_ok().deref_mut(), created_project.id) + .await + .expect("Failed to update project") + .expect("Project not found"); - let _: TestFixture = TestFixture::create(project_2, db_pool.clone()).await; + let project = Project::retrieve(db_pool.get_ok().deref_mut(), update_project.id) + .await + .unwrap() + .unwrap(); + assert_eq!(project.name, project_name); + assert_eq!(project.budget, project_budget); + } + + #[rstest] + async fn sort_project() { + let db_pool = DbConnectionPoolV2::for_tests(); + let _created_project_1 = create_project(&db_pool, "test_project_name_1").await; + let _created_project_2 = create_project(&db_pool, "test_project_name_2").await; - let conn = &mut db_pool.get().await.unwrap(); let projects = Project::list( - conn, + db_pool.get_ok().deref_mut(), SelectionSettings::new().order_by(|| Project::NAME.desc()), ) .await - .unwrap(); + .expect("Failed to retrieve projects"); for (p1, p2) in projects.iter().zip(projects.iter().skip(1)) { let name_1 = p1.name.to_lowercase(); @@ -181,21 +215,4 @@ pub mod test { assert!(name_1.ge(&name_2)); } } - - #[rstest] - async fn update_project( - #[future] project: TestFixture, - db_pool: Arc, - ) { - let project_fixture = project.await; - let conn = &mut db_pool.get().await.unwrap(); - - // Patch a project - let mut project = project_fixture.model.clone(); - project.name = "update_name".into(); - project.budget = Some(1000); - project.save(conn).await.unwrap(); - let project = Project::retrieve(conn, project.id).await.unwrap().unwrap(); - assert_eq!(project.name, String::from("update_name")); - } } diff --git a/editoast/src/views/pagination.rs b/editoast/src/views/pagination.rs index 1d797edd3c4..ce68885ed67 100644 --- a/editoast/src/views/pagination.rs +++ b/editoast/src/views/pagination.rs @@ -47,7 +47,7 @@ const DEFAULT_PAGE_SIZE: u64 = 25; /// /// We named the data field `result` to cope with the old pagination schema which /// enforced this name. For new paginated responses, the field name is up to your imagination :) -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, ToSchema)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] pub struct PaginationStats { /// The total number of items #[schema(minimum = 0)] diff --git a/editoast/src/views/projects.rs b/editoast/src/views/projects.rs index c53c99c0904..02cac152c11 100644 --- a/editoast/src/views/projects.rs +++ b/editoast/src/views/projects.rs @@ -27,7 +27,7 @@ use crate::modelsv2::projects::Tags; use crate::modelsv2::Changeset; use crate::modelsv2::Create; use crate::modelsv2::DbConnection; -use crate::modelsv2::DbConnectionPool; +use crate::modelsv2::DbConnectionPoolV2; use crate::modelsv2::Document; use crate::modelsv2::Model; use crate::modelsv2::Project; @@ -104,8 +104,7 @@ impl From for Changeset { } } -async fn check_image_content(db_pool: Data, document_key: i64) -> Result<()> { - let conn = &mut db_pool.get().await?; +async fn check_image_content(conn: &mut DbConnection, document_key: i64) -> Result<()> { let doc = Document::retrieve_or_fail(conn, document_key, || ProjectError::ImageNotFound { document_key, }) @@ -120,7 +119,7 @@ async fn check_image_content(db_pool: Data, document_key: i64) Ok(()) } -#[derive(Debug, Clone, Serialize, ToSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] #[schema(as = ProjectWithStudies)] pub struct ProjectWithStudyCount { #[serde(flatten)] @@ -148,22 +147,22 @@ impl ProjectWithStudyCount { )] #[post("")] async fn create( - db_pool: Data, + db_pool: Data, data: Json, ) -> Result> { + let conn = &mut db_pool.get().await?; let project_create_form = data.into_inner(); if let Some(image) = project_create_form.image { - check_image_content(db_pool.clone(), image).await?; + check_image_content(conn, image).await?; } let project: Changeset = project_create_form.into(); - let conn = &mut db_pool.get().await?; let project = project.create(conn).await?; let project_with_studies = ProjectWithStudyCount::try_fetch(conn, project).await?; Ok(Json(project_with_studies)) } -#[derive(serde::Serialize, utoipa::ToSchema)] +#[derive(Serialize, Deserialize, ToSchema)] struct ProjectWithStudyCountList { #[schema(value_type = Vec)] results: Vec, @@ -181,7 +180,7 @@ struct ProjectWithStudyCountList { )] #[get("")] async fn list( - db_pool: Data, + db_pool: Data, pagination_params: Query, Query(ordering_params): Query, ) -> Result> { @@ -191,11 +190,13 @@ async fn list( .warn_page_size(100) .into_selection_settings() .order_by(move || ordering.as_project_ordering()); + let (projects, stats) = Project::list_paginated(db_pool.get().await?.deref_mut(), settings).await?; + let results = projects .into_iter() - .zip(std::iter::repeat(&db_pool).map(|p| p.get())) + .zip(db_pool.iter_conn()) .map(|(project, conn)| async move { ProjectWithStudyCount::try_fetch(conn.await?.deref_mut(), project).await }); @@ -222,7 +223,7 @@ pub struct ProjectIdParam { )] #[get("")] async fn get( - db_pool: Data, + db_pool: Data, project: Path, ) -> Result> { let project_id = project.into_inner(); @@ -243,7 +244,7 @@ async fn get( ) )] #[delete("")] -async fn delete(project: Path, db_pool: Data) -> Result { +async fn delete(project: Path, db_pool: Data) -> Result { let project_id = project.into_inner(); let conn = &mut db_pool.get().await?; if Project::delete_and_prune_document(conn, project_id).await? { @@ -301,102 +302,159 @@ impl From for Changeset { async fn patch( data: Json, project_id: Path, - db_pool: Data, + db_pool: Data, ) -> Result> { + let conn = &mut db_pool.get().await?; let data = data.into_inner(); let project_id = project_id.into_inner(); if let Some(image) = data.image { - check_image_content(db_pool.clone(), image).await?; + check_image_content(conn, image).await?; } let project_changeset: Changeset = data.into(); - let conn = &mut db_pool.get().await?; let project = Project::update_and_prune_document(conn, project_changeset, project_id).await?; Ok(Json(ProjectWithStudyCount::try_fetch(conn, project).await?)) } #[cfg(test)] pub mod test { - use actix_http::Request; + use std::ops::DerefMut; + use actix_web::http::StatusCode; - use actix_web::test as actix_test; + use actix_web::test::call_and_read_body_json; use actix_web::test::call_service; - use actix_web::test::read_body_json; use actix_web::test::TestRequest; use rstest::rstest; use serde_json::json; - use std::sync::Arc; use super::*; - use crate::fixtures::tests::db_pool; - use crate::fixtures::tests::project; - use crate::fixtures::tests::TestFixture; - use crate::modelsv2::DeleteStatic; - use crate::views::tests::create_test_service; - - fn delete_project_request(project_id: i64) -> Request { - TestRequest::delete() - .uri(format!("/projects/{project_id}").as_str()) - .to_request() + use crate::modelsv2::prelude::*; + use crate::views::test_app::TestAppBuilder; + + pub fn project_changeset() -> Changeset { + Project::changeset() + .name("test_project".to_owned()) + .budget(Some(0)) + .creation_date(Utc::now().naive_utc()) + .last_modification(Utc::now().naive_utc()) + .tags(Tags::default()) + } + + async fn create_project(pool: &DbConnectionPoolV2) -> Project { + project_changeset() + .create(pool.get_ok().deref_mut()) + .await + .expect("Failed to create project") } #[rstest] - async fn project_create_delete(db_pool: Arc) { - let app = create_test_service().await; - let req = TestRequest::post() + async fn project_post() { + let app = TestAppBuilder::default_app(); + let pool = app.db_pool(); + + let project_name = "test_project"; + + let request = TestRequest::post() .uri("/projects") .set_json(json!({ - "name": "test_project", + "name": project_name, "description": "", "objectives": "", "funders": "", })) .to_request(); - let response = call_service(&app, req).await; - assert_eq!(response.status(), StatusCode::OK); - let project: Project = read_body_json(response).await; - let conn = &mut db_pool.get().await.unwrap(); - Project::delete_static(conn, project.id).await.unwrap(); + + let response: ProjectWithStudyCount = call_and_read_body_json(&app.service, request).await; + + let project = Project::retrieve(pool.get_ok().deref_mut(), response.project.id) + .await + .expect("Failed to retrieve project") + .expect("Project not found"); + + assert_eq!(project.name, project_name); } #[rstest] - async fn project_delete(#[future] project: TestFixture) { - let app = create_test_service().await; - let project = project.await; - let response = call_service(&app, delete_project_request(project.id())).await; - assert_eq!(response.status(), StatusCode::NO_CONTENT); - let response = call_service(&app, delete_project_request(project.id())).await; - assert_eq!(response.status(), StatusCode::NOT_FOUND); + async fn project_list() { + let app = TestAppBuilder::default_app(); + let pool = app.db_pool(); + + let created_project = create_project(&pool).await; + + let request = TestRequest::get().uri("/projects/").to_request(); + let response: ProjectWithStudyCountList = + call_and_read_body_json(&app.service, request).await; + + let project_retreived = response + .results + .iter() + .find(|p| p.project.id == created_project.id) + .unwrap(); + + assert_eq!(created_project.name, project_retreived.project.name); } - #[actix_test] - async fn project_list() { - let app = create_test_service().await; - let req = TestRequest::get().uri("/projects/").to_request(); - let response = call_service(&app, req).await; - assert_eq!(response.status(), StatusCode::OK); + #[rstest] + async fn project_get() { + let app = TestAppBuilder::default_app(); + let pool = app.db_pool(); + + let created_project = create_project(&pool).await; + + let request = TestRequest::get() + .uri(format!("/projects/{}", created_project.id).as_str()) + .to_request(); + let response: ProjectWithStudyCount = call_and_read_body_json(&app.service, request).await; + + assert_eq!(response.project.id, created_project.id); + assert_eq!(response.project.name, created_project.name); + assert_eq!(response.project.description, created_project.description); + assert_eq!(response.project.budget, created_project.budget); + assert_eq!(response.project.funders, created_project.funders); + assert_eq!(response.project.objectives, created_project.objectives); } #[rstest] - async fn project_get(#[future] project: TestFixture) { - let app = create_test_service().await; - let project = project.await; + async fn project_delete() { + let app = TestAppBuilder::default_app(); + let pool = app.db_pool(); + + let created_project = create_project(&pool).await; - let req = TestRequest::get() - .uri(format!("/projects/{}", project.id()).as_str()) + let request = TestRequest::delete() + .uri(format!("/projects/{}", created_project.id).as_str()) .to_request(); - let response = call_service(&app, req).await; - assert_eq!(response.status(), StatusCode::OK); + + let response = call_service(&app.service, request).await; + assert_eq!(response.status(), StatusCode::NO_CONTENT); + + let exists = Project::exists(pool.get_ok().deref_mut(), created_project.id) + .await + .expect("Failed to check if project exists"); + + assert!(!exists); } #[rstest] - async fn project_patch(#[future] project: TestFixture) { - let app = create_test_service().await; - let project = project.await; - let req = TestRequest::patch() - .uri(format!("/projects/{}", project.id()).as_str()) - .set_json(json!({"name": "rename_test", "budget":20000})) + async fn project_patch() { + let app = TestAppBuilder::default_app(); + let pool = app.db_pool(); + + let created_project = create_project(&pool).await; + + let updated_name = "rename_test"; + let updated_budget = 20000; + + let request = TestRequest::patch() + .uri(format!("/projects/{}", created_project.id).as_str()) + .set_json(json!({ + "name": updated_name, + "budget": updated_budget + })) .to_request(); - let response = call_service(&app, req).await; - assert_eq!(response.status(), StatusCode::OK); + + let response: ProjectWithStudyCount = call_and_read_body_json(&app.service, request).await; + + assert_eq!(response.project.name, updated_name); + assert_eq!(response.project.budget, Some(updated_budget)); } }