From 0b1b79f9575fe1915f34d1a1e00638f6a7748b5c Mon Sep 17 00:00:00 2001 From: Egor Date: Thu, 6 Jun 2024 12:34:53 +0200 Subject: [PATCH] editoast: refactor scenario V2 tests --- editoast/src/modelsv2/fixtures.rs | 74 ++++++- editoast/src/modelsv2/scenario.rs | 1 + editoast/src/views/v2/scenario.rs | 326 ++++++++++++++++------------- editoast/src/views/v2/timetable.rs | 1 + 4 files changed, 247 insertions(+), 155 deletions(-) diff --git a/editoast/src/modelsv2/fixtures.rs b/editoast/src/modelsv2/fixtures.rs index 3c7917487fc..9cb93f458ac 100644 --- a/editoast/src/modelsv2/fixtures.rs +++ b/editoast/src/modelsv2/fixtures.rs @@ -14,6 +14,7 @@ use crate::modelsv2::ElectricalProfileSet; use crate::modelsv2::Infra; use crate::modelsv2::Project; use crate::modelsv2::RollingStockModel; +use crate::modelsv2::Scenario; use crate::modelsv2::Study; use crate::modelsv2::Tags; use crate::views::rolling_stocks::rolling_stock_form::RollingStockForm; @@ -53,6 +54,71 @@ pub async fn create_study(conn: &mut DbConnection, name: &str, project_id: i64) .expect("Failed to create study") } +pub async fn create_timetable(conn: &mut DbConnection) -> Timetable { + Timetable::changeset() + .electrical_profile_set_id(None) + .create(conn) + .await + .expect("Failed to create timetable") +} + +pub fn scenario_changeset( + name: &str, + study_id: i64, + timetable_id: i64, + infra_id: i64, +) -> Changeset { + Scenario::changeset() + .name(name.to_string()) + .description("test_scenario_v2 description".to_string()) + .creation_date(Utc::now().naive_utc()) + .last_modification(Utc::now().naive_utc()) + .tags(Tags::default()) + .timetable_id(timetable_id) + .study_id(study_id) + .infra_id(infra_id) +} + +pub async fn create_scenario( + conn: &mut DbConnection, + name: &str, + study_id: i64, + timetable_id: i64, + infra_id: i64, +) -> Scenario { + let scenario = scenario_changeset(name, study_id, timetable_id, infra_id); + scenario + .create(conn) + .await + .expect("Failed to create scenario") +} + +pub struct ScenarioFixtureSet { + pub project: Project, + pub study: Study, + pub scenario: Scenario, + pub timetable: Timetable, + pub infra: Infra, +} + +pub async fn create_scenario_fixtures_set( + conn: &mut DbConnection, + name: &str, +) -> ScenarioFixtureSet { + let project = create_project(conn, &format!("project_test_name_with_{name}")).await; + let study = create_study(conn, &format!("study_test_name_with_{name}"), project.id).await; + let infra = create_empty_infra(conn).await; + let timetable = create_timetable(conn).await; + let scenario = create_scenario(conn, name, study.id, timetable.id, infra.id).await; + ScenarioFixtureSet { + project, + study, + scenario, + timetable, + infra, + } +} + pub fn fast_rolling_stock_form(name: &str) -> RollingStockForm { let mut rolling_stock_form: RollingStockForm = serde_json::from_str(include_str!("../tests/example_rolling_stock_1.json")) @@ -151,14 +217,6 @@ pub async fn create_electrical_profile_set(conn: &mut DbConnection) -> Electrica .expect("Failed to create electrical profile set") } -pub async fn create_timetable(conn: &mut DbConnection) -> Timetable { - Timetable::changeset() - .electrical_profile_set_id(None) - .create(conn) - .await - .expect("Failed to create timetable") -} - pub async fn create_empty_infra(conn: &mut DbConnection) -> Infra { Infra::changeset() .name("empty_infra".to_owned()) diff --git a/editoast/src/modelsv2/scenario.rs b/editoast/src/modelsv2/scenario.rs index e79976eb639..5b01549d3f0 100644 --- a/editoast/src/modelsv2/scenario.rs +++ b/editoast/src/modelsv2/scenario.rs @@ -14,6 +14,7 @@ use crate::modelsv2::Tags; #[derive(Debug, Clone, ModelV2, Deserialize, Serialize, ToSchema)] #[schema(as = ScenarioV2)] #[model(table = crate::tables::scenario_v2)] +#[cfg_attr(test, derive(PartialEq))] pub struct Scenario { pub id: i64, pub infra_id: i64, diff --git a/editoast/src/views/v2/scenario.rs b/editoast/src/views/v2/scenario.rs index f6722213c9a..6532b15f1fc 100644 --- a/editoast/src/views/v2/scenario.rs +++ b/editoast/src/views/v2/scenario.rs @@ -27,7 +27,7 @@ use crate::modelsv2::prelude::*; use crate::modelsv2::scenario::Scenario; use crate::modelsv2::timetable::Timetable; use crate::modelsv2::DbConnection; -use crate::modelsv2::DbConnectionPool; +use crate::modelsv2::DbConnectionPoolV2; use crate::modelsv2::Infra; use crate::modelsv2::Project; use crate::modelsv2::Study; @@ -36,10 +36,10 @@ use crate::views::operational_studies::OperationalStudiesOrderingParam; use crate::views::pagination::PaginatedList as _; use crate::views::pagination::PaginationQueryParam; use crate::views::pagination::PaginationStats; +use crate::views::projects::ProjectError; use crate::views::projects::ProjectIdParam; -use crate::views::scenario::check_project_study; -use crate::views::scenario::check_project_study_conn; use crate::views::scenario::ScenarioIdParam; +use crate::views::study::StudyError; use crate::views::study::StudyIdParam; crate::routes! { @@ -97,6 +97,23 @@ impl From for Changeset { } } +pub async fn check_project_study( + conn: &mut DbConnection, + project_id: i64, + study_id: i64, +) -> Result<(Project, Study)> { + let project = + Project::retrieve_or_fail(conn, project_id, || ProjectError::NotFound { project_id }) + .await?; + let study = + Study::retrieve_or_fail(conn, study_id, || StudyError::NotFound { study_id }).await?; + + if study.project_id != project_id { + return Err(StudyError::NotFound { study_id }.into()); + } + Ok((project, study)) +} + #[derive(Debug, Error, EditoastError)] #[editoast_error(base_id = "scenario")] #[allow(clippy::enum_variant_names)] @@ -175,7 +192,7 @@ impl ScenarioResponse { )] #[post("")] async fn create( - db_pool: Data, + db_pool: Data, data: Json, path: Path<(i64, i64)>, ) -> Result> { @@ -184,14 +201,13 @@ async fn create( let infra_id = data.infra_id; let scenario: Changeset = data.into_inner().into(); - let mut tx = db_pool.get().await?; - - let scenarios_response = tx + let scenarios_response = db_pool + .get() + .await? .transaction::<_, InternalError, _>(|conn| { async { // Check if the project and the study exist - let (mut project, study) = - check_project_study_conn(conn, project_id, study_id).await?; + let (mut project, study) = check_project_study(conn, project_id, study_id).await?; // Check if the timetable exists let _ = Timetable::retrieve_or_fail(conn, timetable_id, || { @@ -240,40 +256,41 @@ async fn create( #[delete("")] async fn delete( path: Path, - db_pool: Data, + db_pool: Data, ) -> Result { let ScenarioPathParam { project_id, study_id, scenario_id, } = path.into_inner(); - let mut tx = db_pool.get().await?; - - let db_pool = db_pool.into_inner(); - tx.transaction::<_, InternalError, _>(|conn| { - async { - // Check if the project and the study exist - let (mut project, study) = check_project_study(db_pool.clone(), project_id, study_id) - .await - .unwrap(); - - // Delete scenario - Scenario::delete_static_or_fail(conn, scenario_id, || ScenarioError::NotFound { - scenario_id, - }) - .await?; - // Update project last_modification field - project.update_last_modified(conn).await?; + db_pool + .get() + .await? + .transaction::<_, InternalError, _>(|conn| { + async { + // Check if the project and the study exist + let (mut project, study) = check_project_study(conn, project_id, study_id) + .await + .unwrap(); + + // Delete scenario + Scenario::delete_static_or_fail(conn, scenario_id, || ScenarioError::NotFound { + scenario_id, + }) + .await?; - // Update study last_modification field - study.clone().update_last_modified(conn).await?; + // Update project last_modification field + project.update_last_modified(conn).await?; - Ok(()) - } - .scope_boxed() - }) - .await?; + // Update study last_modification field + study.clone().update_last_modified(conn).await?; + + Ok(()) + } + .scope_boxed() + }) + .await?; Ok(HttpResponse::NoContent().finish()) } @@ -313,7 +330,7 @@ impl From for ::Changeset async fn patch( data: Json, path: Path, - db_pool: Data, + db_pool: Data, ) -> Result> { let ScenarioPathParam { project_id, @@ -321,14 +338,13 @@ async fn patch( scenario_id, } = path.into_inner(); - let mut tx = db_pool.get().await?; - - let scenarios_response = tx + let scenarios_response = db_pool + .get() + .await? .transaction::<_, InternalError, _>(|conn| { async { // Check if project and study exist - let (mut project, study) = - check_project_study_conn(conn, project_id, study_id).await?; + let (mut project, study) = check_project_study(conn, project_id, study_id).await?; // Check if the infra exists if let Some(infra_id) = data.0.infra_id { @@ -377,20 +393,18 @@ async fn patch( )] #[get("")] async fn get( - db_pool: Data, + db_pool: Data, path: Path, ) -> Result> { - use crate::modelsv2::Retrieve; - let ScenarioPathParam { project_id, study_id, scenario_id, } = path.into_inner(); - let db_pool = db_pool.into_inner(); - let (project, study) = check_project_study(db_pool.clone(), project_id, study_id).await?; let conn = &mut db_pool.get().await?; + + let (project, study) = check_project_study(conn, project_id, study_id).await?; // Return the scenarios let scenario = Scenario::retrieve_or_fail(conn, scenario_id, || ScenarioError::NotFound { scenario_id, @@ -426,14 +440,13 @@ struct ListScenariosResponse { )] #[get("")] async fn list( - db_pool: Data, + db_pool: Data, path: Path<(i64, i64)>, Query(pagination_params): Query, Query(OperationalStudiesOrderingParam { ordering }): Query, ) -> Result> { - let db_pool = db_pool.into_inner(); let (project_id, study_id) = path.into_inner(); - let _ = check_project_study(db_pool.clone(), project_id, study_id).await?; + let _ = check_project_study(db_pool.get().await?.deref_mut(), project_id, study_id).await?; let settings = pagination_params .validate(1000)? @@ -461,20 +474,17 @@ mod tests { use actix_web::test::call_and_read_body_json; use actix_web::test::call_service; use actix_web::test::TestRequest; + use pretty_assertions::assert_eq; use rstest::rstest; use serde_json::json; - use std::sync::Arc; use super::*; - use crate::fixtures::tests::db_pool; - use crate::fixtures::tests::scenario_v2_fixture_set; - use crate::fixtures::tests::small_infra; - use crate::fixtures::tests::timetable_v2; - use crate::fixtures::tests::ScenarioV2FixtureSet; - use crate::fixtures::tests::TestFixture; - use crate::modelsv2::timetable::Timetable as TimetableV2; - use crate::modelsv2::Infra; - use crate::views::tests::create_test_service; + use crate::modelsv2::fixtures::create_empty_infra; + use crate::modelsv2::fixtures::create_project; + use crate::modelsv2::fixtures::create_scenario_fixtures_set; + use crate::modelsv2::fixtures::create_study; + use crate::modelsv2::fixtures::create_timetable; + use crate::views::test_app::TestAppBuilder; pub fn scenario_url(project_id: i64, study_id: i64, scenario_id: Option) -> String { format!( @@ -486,32 +496,38 @@ mod tests { } #[rstest] - async fn get_scenario(#[future] scenario_v2_fixture_set: ScenarioV2FixtureSet) { - let service = create_test_service().await; - let fixtures = scenario_v2_fixture_set.await; + async fn get_scenario() { + let app = TestAppBuilder::default_app(); + let pool = app.db_pool(); + + let fixtures = + create_scenario_fixtures_set(pool.get_ok().deref_mut(), "test_scenario_name").await; let url = scenario_url( - fixtures.project.id(), - fixtures.study.id(), - Some(fixtures.scenario.id()), + fixtures.project.id, + fixtures.study.id, + Some(fixtures.scenario.id), ); - let request = TestRequest::get().uri(&url).to_request(); - let response: ScenarioResponse = call_and_read_body_json(&service, request).await; - assert_eq!(response.scenario.id, fixtures.scenario.id()); - assert_eq!(response.scenario.name, fixtures.scenario.model.name); + let response: ScenarioResponse = call_and_read_body_json(&app.service, request).await; + + assert_eq!(response.scenario, fixtures.scenario); } #[rstest] - async fn get_scenarios(#[future] scenario_v2_fixture_set: ScenarioV2FixtureSet) { - let service = create_test_service().await; - let fixtures = scenario_v2_fixture_set.await; + async fn get_scenarios() { + let app = TestAppBuilder::default_app(); + let pool = app.db_pool(); - let url = scenario_url(fixtures.project.id(), fixtures.study.id(), None); + let fixtures = + create_scenario_fixtures_set(pool.get_ok().deref_mut(), "test_scenario_name").await; + + let url = scenario_url(fixtures.project.id, fixtures.study.id, None); let request = TestRequest::get().uri(&url).to_request(); - let mut response: ListScenariosResponse = call_and_read_body_json(&service, request).await; + let mut response: ListScenariosResponse = + call_and_read_body_json(&app.service, request).await; assert!(!response.results.is_empty()); assert_eq!( @@ -525,40 +541,37 @@ mod tests { } #[rstest] - async fn get_scenarios_with_wrong_study( - #[future] scenario_v2_fixture_set: ScenarioV2FixtureSet, - ) { - let service = create_test_service().await; - let fixtures = scenario_v2_fixture_set.await; + async fn get_scenarios_with_wrong_study() { + let app = TestAppBuilder::default_app(); + let pool = app.db_pool(); - let url = scenario_url( - fixtures.project.id(), - 99999999, - Some(fixtures.scenario.id()), - ); + let fixtures = + create_scenario_fixtures_set(pool.get_ok().deref_mut(), "test_scenario_name").await; + + let url = scenario_url(fixtures.project.id, 99999999, Some(fixtures.scenario.id)); let request = TestRequest::get().uri(&url).to_request(); - let response = call_service(&service, request).await; + let response = call_service(&app.service, request).await; assert_eq!(response.status(), StatusCode::NOT_FOUND); } #[rstest] - async fn post_scenario( - #[future] scenario_v2_fixture_set: ScenarioV2FixtureSet, - #[future] timetable_v2: TestFixture, - db_pool: Arc, - ) { - let service = create_test_service().await; - let fixtures = scenario_v2_fixture_set.await; - let timetable_v2 = timetable_v2.await; - - let url = scenario_url(fixtures.project.id(), fixtures.study.id(), None); - - let study_name = "new created scenario V2"; - let study_description = "new created scenario description V2"; - let study_timetable_id = timetable_v2.id(); - let study_infra_id = fixtures.infra.id(); + async fn post_scenario() { + let app = TestAppBuilder::default_app(); + let pool = app.db_pool(); + + let project = create_project(pool.get_ok().deref_mut(), "project_test_name").await; + let study = create_study(pool.get_ok().deref_mut(), "study_test_name", project.id).await; + let infra = create_empty_infra(pool.get_ok().deref_mut()).await; + let timetable = create_timetable(pool.get_ok().deref_mut()).await; + + let url = scenario_url(project.id, study.id, None); + + let study_name = "new created scenario"; + let study_description = "new created scenario description"; + let study_timetable_id = timetable.id; + let study_infra_id = infra.id; let study_tags = Tags::new(vec!["tag1".to_string(), "tag2".to_string()]); // Insert scenario @@ -572,35 +585,42 @@ mod tests { "tags": study_tags })) .to_request(); - let response: ScenarioResponse = call_and_read_body_json(&service, request).await; - - // Delete the scenario - assert!( - Scenario::delete_static(&mut db_pool.get().await.unwrap(), response.scenario.id) - .await - .unwrap() - ); + let response: ScenarioResponse = call_and_read_body_json(&app.service, request).await; assert_eq!(response.scenario.name, study_name); assert_eq!(response.scenario.description, study_description); assert_eq!(response.scenario.infra_id, study_infra_id); assert_eq!(response.scenario.timetable_id, study_timetable_id); assert_eq!(response.scenario.tags, study_tags); + + let created_scenario = Scenario::retrieve(pool.get_ok().deref_mut(), response.scenario.id) + .await + .expect("Failed to retrieve scenario") + .expect("Scenario not found"); + + assert_eq!(created_scenario.name, study_name); + assert_eq!(created_scenario.description, study_description); + assert_eq!(created_scenario.infra_id, study_infra_id); + assert_eq!(created_scenario.timetable_id, study_timetable_id); + assert_eq!(created_scenario.tags, study_tags); } #[rstest] - async fn patch_scenario(#[future] scenario_v2_fixture_set: ScenarioV2FixtureSet) { - let service = create_test_service().await; - let fixtures = scenario_v2_fixture_set.await; + async fn patch_scenario() { + let app = TestAppBuilder::default_app(); + let pool = app.db_pool(); + + let fixtures = + create_scenario_fixtures_set(pool.get_ok().deref_mut(), "test_scenario_name").await; let url = scenario_url( - fixtures.project.id(), - fixtures.study.id(), - Some(fixtures.scenario.id()), + fixtures.project.id, + fixtures.study.id, + Some(fixtures.scenario.id), ); - let study_name = "new patched scenario V2"; - let study_description = "new patched scenario description V2"; + let study_name = "new patched scenario"; + let study_description = "new patched scenario description"; let study_tags = Tags::new(vec!["patched_tag1".to_string(), "patched_tag2".to_string()]); // Update scenario @@ -612,7 +632,7 @@ mod tests { "tags": study_tags })) .to_request(); - let response: ScenarioResponse = call_and_read_body_json(&service, request).await; + let response: ScenarioResponse = call_and_read_body_json(&app.service, request).await; assert_eq!(response.scenario.name, study_name); assert_eq!(response.scenario.description, study_description); @@ -620,16 +640,17 @@ mod tests { } #[rstest] - async fn patch_scenario_with_unavailable_infra( - #[future] scenario_v2_fixture_set: ScenarioV2FixtureSet, - ) { - let service = create_test_service().await; - let fixtures = scenario_v2_fixture_set.await; + async fn patch_scenario_with_unavailable_infra() { + let app = TestAppBuilder::default_app(); + let pool = app.db_pool(); + + let fixtures = + create_scenario_fixtures_set(pool.get_ok().deref_mut(), "test_scenario_name").await; let url = scenario_url( - fixtures.project.id(), - fixtures.study.id(), - Some(fixtures.scenario.id()), + fixtures.project.id, + fixtures.study.id, + Some(fixtures.scenario.id), ); // Update scenario @@ -639,56 +660,67 @@ mod tests { "infra_id": 999999999, })) .to_request(); - let response = call_service(&service, request).await; + + let response = call_service(&app.service, request).await; assert_eq!(response.status(), StatusCode::NOT_FOUND); } #[rstest] - async fn patch_infra_id_scenario( - #[future] scenario_v2_fixture_set: ScenarioV2FixtureSet, - #[future] small_infra: TestFixture, - ) { - let service = create_test_service().await; - let small_infra = small_infra.await; - let fixtures = scenario_v2_fixture_set.await; + async fn patch_infra_id_scenario() { + let app = TestAppBuilder::default_app(); + let pool = app.db_pool(); + + let fixtures = + create_scenario_fixtures_set(pool.get_ok().deref_mut(), "test_scenario_name").await; + let other_infra = create_empty_infra(pool.get_ok().deref_mut()).await; - assert_eq!(fixtures.scenario.model.infra_id, fixtures.infra.id()); + assert_eq!(fixtures.scenario.infra_id, fixtures.infra.id); + assert_ne!(fixtures.scenario.infra_id, other_infra.id); let url = scenario_url( - fixtures.project.id(), - fixtures.study.id(), - Some(fixtures.scenario.id()), + fixtures.project.id, + fixtures.study.id, + Some(fixtures.scenario.id), ); let study_name = "new patched scenario V2"; - let study_small_infra_infra_id = small_infra.id(); + let study_other_infra_id = other_infra.id; let request = TestRequest::patch() .uri(&url) .set_json(json!({ "name": study_name, - "infra_id": study_small_infra_infra_id, + "infra_id": study_other_infra_id, })) .to_request(); - let response: ScenarioResponse = call_and_read_body_json(&service, request).await; + let response: ScenarioResponse = call_and_read_body_json(&app.service, request).await; - assert_eq!(response.scenario.infra_id, study_small_infra_infra_id); + assert_eq!(response.scenario.infra_id, study_other_infra_id); assert_eq!(response.scenario.name, study_name); } #[rstest] - async fn delete_scenario(#[future] scenario_v2_fixture_set: ScenarioV2FixtureSet) { - let service = create_test_service().await; - let fixtures = scenario_v2_fixture_set.await; + async fn delete_scenario() { + let app = TestAppBuilder::default_app(); + let pool = app.db_pool(); + + let fixtures = + create_scenario_fixtures_set(pool.get_ok().deref_mut(), "test_scenario_name").await; let url = scenario_url( - fixtures.project.id(), - fixtures.study.id(), - Some(fixtures.scenario.id()), + fixtures.project.id, + fixtures.study.id, + Some(fixtures.scenario.id), ); let request = TestRequest::delete().uri(&url).to_request(); - let response = call_service(&service, request).await; + let response = call_service(&app.service, request).await; assert_eq!(response.status(), StatusCode::NO_CONTENT); + + let exists = Scenario::exists(pool.get_ok().deref_mut(), fixtures.scenario.id) + .await + .expect("Failed to check if scenario exists"); + + assert!(!exists); } } diff --git a/editoast/src/views/v2/timetable.rs b/editoast/src/views/v2/timetable.rs index 53ec59ca279..4af349c437b 100644 --- a/editoast/src/views/v2/timetable.rs +++ b/editoast/src/views/v2/timetable.rs @@ -388,6 +388,7 @@ mod tests { use actix_web::test::call_service; use actix_web::test::read_body_json; use actix_web::test::TestRequest; + use pretty_assertions::assert_eq; use reqwest::StatusCode; use rstest::rstest; use serde_json::json;