From a6ecd8eea15612f282f481cfc2a2cb5c74cfc1be Mon Sep 17 00:00:00 2001 From: Egor Berezovskiy Date: Wed, 5 Mar 2025 12:12:48 +0100 Subject: [PATCH] editoast: get paced train path endpoint Signed-off-by: Egor Berezovskiy --- editoast/openapi.yaml | 1 - editoast/src/models/paced_train.rs | 5 +- editoast/src/views/paced_train.rs | 156 ++++++++++++++++--- editoast/src/views/timetable.rs | 2 +- front/src/common/api/generatedEditoastApi.ts | 2 +- 5 files changed, 139 insertions(+), 27 deletions(-) diff --git a/editoast/openapi.yaml b/editoast/openapi.yaml index a8b03ecb925..1f67ce2dcd2 100644 --- a/editoast/openapi.yaml +++ b/editoast/openapi.yaml @@ -3020,7 +3020,6 @@ paths: get: tags: - timetable - - paced_train summary: Return a specific timetable with its associated paced trains parameters: - name: id diff --git a/editoast/src/models/paced_train.rs b/editoast/src/models/paced_train.rs index 2981ffd6ed0..9b2a7856f63 100644 --- a/editoast/src/models/paced_train.rs +++ b/editoast/src/models/paced_train.rs @@ -1,4 +1,3 @@ -use crate::models::train_schedule::TrainSchedule; use chrono::DateTime; use chrono::Duration as ChronoDuration; use chrono::Utc; @@ -14,9 +13,9 @@ use editoast_schemas::train_schedule::ScheduleItem; use editoast_schemas::train_schedule::TrainScheduleBase; use editoast_schemas::train_schedule::TrainScheduleOptions; -use crate::models::prelude::*; - use super::Tags; +use crate::models::prelude::*; +use crate::models::train_schedule::TrainSchedule; #[derive(Debug, Clone, Model)] #[model(table = editoast_models::tables::paced_train)] diff --git a/editoast/src/views/paced_train.rs b/editoast/src/views/paced_train.rs index 8c5f2f5435e..c0429a74248 100644 --- a/editoast/src/views/paced_train.rs +++ b/editoast/src/views/paced_train.rs @@ -1,13 +1,6 @@ use std::collections::HashMap; use std::collections::HashSet; -use crate::core::simulation::SimulationResponse; -use crate::error::Result; -use crate::models::prelude::*; -use crate::models::train_schedule::TrainSchedule; -use crate::models::Infra; -use crate::views::projection::ProjectPathTrainResult; -use crate::views::ListId; use axum::extract::Json; use axum::extract::Path; use axum::extract::Query; @@ -18,13 +11,13 @@ use editoast_authz::BuiltinRole; use editoast_derive::EditoastError; use editoast_models::DbConnectionPoolV2; use editoast_schemas::paced_train::PacedTrainBase; - use serde::Deserialize; use serde::Serialize; use thiserror::Error; use utoipa::IntoParams; use utoipa::ToSchema; +use super::path::pathfinding::pathfinding_from_train; use super::path::pathfinding::PathfindingResult; use super::projection::ProjectPathForm; use super::train_schedule::train_simulation_batch; @@ -32,8 +25,15 @@ use super::AppState; use super::AuthenticationExt; use super::InfraIdQueryParam; use super::SimulationSummaryResult; +use crate::core::simulation::SimulationResponse; +use crate::error::Result; use crate::models::paced_train::PacedTrain; +use crate::models::prelude::*; +use crate::models::train_schedule::TrainSchedule; +use crate::models::Infra; +use crate::views::projection::ProjectPathTrainResult; use crate::views::AuthorizationError; +use crate::views::ListId; crate::routes! { "/paced_train" => { @@ -281,20 +281,43 @@ async fn simulation_summary( )] async fn get_path( State(AppState { - db_pool: _db_pool, - valkey: _valkey_client, - core_client: _core_client, + db_pool, + valkey: valkey_client, + core_client, .. }): State, - Extension(_auth): AuthenticationExt, - Path(PacedTrainIdParam { - id: _paced_train_id, - }): Path, - Query(InfraIdQueryParam { - infra_id: _infra_id, - }): Query, + Extension(auth): AuthenticationExt, + Path(PacedTrainIdParam { id: paced_train_id }): Path, + Query(InfraIdQueryParam { infra_id }): Query, ) -> Result> { - todo!(); + let authorized = auth + .check_roles([BuiltinRole::OperationalStudies, BuiltinRole::Stdcm].into()) + .await + .map_err(AuthorizationError::AuthError)?; + if !authorized { + return Err(AuthorizationError::Forbidden.into()); + } + let conn = &mut db_pool.get().await?; + let mut valkey_conn = valkey_client.get_connection().await?; + + let infra = Infra::retrieve_or_fail(conn, infra_id, || PacedTrainError::InfraNotFound { + infra_id, + }) + .await?; + let paced_train = PacedTrain::retrieve_or_fail(conn, paced_train_id, || { + PacedTrainError::NotFound { paced_train_id } + }) + .await?; + Ok(Json( + pathfinding_from_train( + conn, + &mut valkey_conn, + core_client, + &infra, + paced_train.into_first_occurrence(), + ) + .await?, + )) } #[derive(Debug, Default, Clone, Serialize, Deserialize, IntoParams, ToSchema)] @@ -399,14 +422,18 @@ async fn project_path( mod tests { use std::collections::HashMap; + use axum::http::StatusCode; use chrono::Duration; use editoast_models::DbConnectionPoolV2; use editoast_schemas::paced_train::Paced; use editoast_schemas::paced_train::PacedTrainBase; use editoast_schemas::train_schedule::TrainScheduleBase; + use pretty_assertions::assert_eq; use rstest::rstest; use serde_json::json; + use crate::core::mocking::MockingClient; + use crate::core::pathfinding::PathfindingResultSuccess; use crate::core::simulation::CompleteReportTrain; use crate::core::simulation::ElectricalProfiles; use crate::core::simulation::ReportTrain; @@ -421,13 +448,12 @@ mod tests { use crate::models::paced_train::PacedTrainChangeset; use crate::models::prelude::*; use crate::views::paced_train::PacedTrainResult; + use crate::views::path::pathfinding::PathfindingResult; use crate::views::test_app::TestApp; use crate::views::test_app::TestAppBuilder; use crate::views::tests::mocked_core_pathfinding_sim_and_proj; use crate::views::InternalError; use crate::views::SimulationSummaryResult; - use axum::http::StatusCode; - use pretty_assertions::assert_eq; #[rstest] async fn paced_train_post() { @@ -644,4 +670,92 @@ mod tests { assert_eq!(&response.error_type, "editoast:paced_train:BatchNotFound") } + + #[rstest] + async fn get_paced_train_path_infra_not_found() { + let app = TestAppBuilder::default_app(); + let pool = app.db_pool(); + let timetable = create_timetable(&mut pool.get_ok()).await; + let paced_train = create_simple_paced_train(&mut pool.get_ok(), timetable.id).await; + + let request = app.get(&format!( + "/paced_train/{}/path?infra_id={}", + paced_train.id, 0 + )); + + let response: InternalError = app + .fetch(request) + .assert_status(StatusCode::NOT_FOUND) + .json_into(); + + assert_eq!(&response.error_type, "editoast:paced_train:InfraNotFound") + } + + #[rstest] + async fn get_paced_train_path_not_found() { + let app = TestAppBuilder::default_app(); + let pool = app.db_pool(); + let small_infra = create_small_infra(&mut pool.get_ok()).await; + + let request = app.get(&format!( + "/paced_train/{}/path?infra_id={}", + 0, small_infra.id + )); + + let response: InternalError = app + .fetch(request) + .assert_status(StatusCode::NOT_FOUND) + .json_into(); + + assert_eq!(&response.error_type, "editoast:paced_train:NotFound"); + } + + #[rstest] + async fn get_paced_train_path() { + let db_pool = DbConnectionPoolV2::for_tests(); + let mut core = MockingClient::new(); + core.stub("/v2/pathfinding/blocks") + .method(reqwest::Method::POST) + .response(StatusCode::OK) + .json(json!({ + "blocks":[], + "routes": [], + "track_section_ranges": [], + "path_item_positions": [], + "length": 1, + "status": "success" + })) + .finish(); + let app = TestAppBuilder::new() + .db_pool(db_pool.clone()) + .core_client(core.into()) + .build(); + let pool = app.db_pool(); + + create_fast_rolling_stock(&mut db_pool.get_ok(), "R2D2").await; + let timetable = create_timetable(&mut pool.get_ok()).await; + let paced_train = create_simple_paced_train(&mut pool.get_ok(), timetable.id).await; + let small_infra = create_small_infra(&mut db_pool.get_ok()).await; + + let request = app.get(&format!( + "/paced_train/{}/path?infra_id={}", + paced_train.id, small_infra.id + )); + + let response = app + .fetch(request) + .assert_status(StatusCode::OK) + .json_into::(); + + assert_eq!( + response, + PathfindingResult::Success(PathfindingResultSuccess { + blocks: vec![], + routes: vec![], + track_section_ranges: vec![], + path_item_positions: vec![], + length: 1 + }) + ) + } } diff --git a/editoast/src/views/timetable.rs b/editoast/src/views/timetable.rs index 57e8797b041..43482bccc69 100644 --- a/editoast/src/views/timetable.rs +++ b/editoast/src/views/timetable.rs @@ -333,7 +333,7 @@ struct ListPacedTrainsResponse { /// Return a specific timetable with its associated paced trains #[utoipa::path( get, path = "", - tag = "timetable,paced_train", + tag = "timetable", params(TimetableIdParam, PaginationQueryParams), responses( (status = 200, description = "Timetable with paced train ids", body = inline(ListPacedTrainsResponse)), diff --git a/front/src/common/api/generatedEditoastApi.ts b/front/src/common/api/generatedEditoastApi.ts index 7da472a8b0b..387df869427 100644 --- a/front/src/common/api/generatedEditoastApi.ts +++ b/front/src/common/api/generatedEditoastApi.ts @@ -957,7 +957,7 @@ const injectedRtkApi = api page_size: queryArg.pageSize, }, }), - providesTags: ['timetable', 'paced_train'], + providesTags: ['timetable'], }), postTimetableByIdPacedTrains: build.mutation< PostTimetableByIdPacedTrainsApiResponse,