diff --git a/editoast/openapi.yaml b/editoast/openapi.yaml index e6eb16f1d91..a150373e76e 100644 --- a/editoast/openapi.yaml +++ b/editoast/openapi.yaml @@ -5140,9 +5140,10 @@ components: - $ref: '#/components/schemas/EditoastOperationErrorInvalidPatch' - $ref: '#/components/schemas/EditoastOperationErrorModifyId' - $ref: '#/components/schemas/EditoastOperationErrorObjectNotFound' - - $ref: '#/components/schemas/EditoastPacedTrainErrorBatchPacedTrainNotFound' + - $ref: '#/components/schemas/EditoastPacedTrainErrorBatchNotFound' - $ref: '#/components/schemas/EditoastPacedTrainErrorDatabase' - - $ref: '#/components/schemas/EditoastPacedTrainErrorPacedTrainNotFound' + - $ref: '#/components/schemas/EditoastPacedTrainErrorInfraNotFound' + - $ref: '#/components/schemas/EditoastPacedTrainErrorNotFound' - $ref: '#/components/schemas/EditoastPaginationErrorInvalidPage' - $ref: '#/components/schemas/EditoastPaginationErrorInvalidPageSize' - $ref: '#/components/schemas/EditoastPathfindingErrorInfraNotFound' @@ -5637,7 +5638,7 @@ components: type: string enum: - editoast:operation:ObjectNotFound - EditoastPacedTrainErrorBatchPacedTrainNotFound: + EditoastPacedTrainErrorBatchNotFound: type: object required: - type @@ -5660,7 +5661,7 @@ components: type: type: string enum: - - editoast:paced_train:BatchPacedTrainNotFound + - editoast:paced_train:BatchNotFound EditoastPacedTrainErrorDatabase: type: object required: @@ -5680,7 +5681,31 @@ components: type: string enum: - editoast:paced_train:Database - EditoastPacedTrainErrorPacedTrainNotFound: + EditoastPacedTrainErrorInfraNotFound: + type: object + required: + - type + - status + - message + properties: + context: + type: object + required: + - infra_id + properties: + infra_id: + type: integer + message: + type: string + status: + type: integer + enum: + - 404 + type: + type: string + enum: + - editoast:paced_train:InfraNotFound + EditoastPacedTrainErrorNotFound: type: object required: - type @@ -5703,7 +5728,7 @@ components: type: type: string enum: - - editoast:paced_train:PacedTrainNotFound + - editoast:paced_train:NotFound EditoastPaginationErrorInvalidPage: type: object required: @@ -8570,7 +8595,7 @@ components: timetable_id: type: integer format: int64 - description: Timetable attached to the train schedule + description: Timetable attached to the paced train nullable: true PacedTrainResult: allOf: diff --git a/editoast/src/models/paced_train.rs b/editoast/src/models/paced_train.rs index 014e766443a..2981ffd6ed0 100644 --- a/editoast/src/models/paced_train.rs +++ b/editoast/src/models/paced_train.rs @@ -1,3 +1,4 @@ +use crate::models::train_schedule::TrainSchedule; use chrono::DateTime; use chrono::Duration as ChronoDuration; use chrono::Utc; @@ -50,6 +51,28 @@ pub struct PacedTrain { pub step: ChronoDuration, } +impl PacedTrain { + pub fn into_first_occurrence(self) -> TrainSchedule { + TrainSchedule { + id: self.id, + train_name: self.train_name, + labels: self.labels.into(), + rolling_stock_name: self.rolling_stock_name, + timetable_id: self.timetable_id, + path: self.path, + start_time: self.start_time, + schedule: self.schedule, + margins: self.margins, + initial_speed: self.initial_speed, + comfort: self.comfort, + constraint_distribution: self.constraint_distribution, + speed_limit_tag: self.speed_limit_tag, + power_restrictions: self.power_restrictions, + options: self.options, + } + } +} + impl From for PacedTrainChangeset { fn from( PacedTrainBase { diff --git a/editoast/src/views/mod.rs b/editoast/src/views/mod.rs index 5b322489d57..37c10154873 100644 --- a/editoast/src/views/mod.rs +++ b/editoast/src/views/mod.rs @@ -77,6 +77,7 @@ use crate::client::get_app_version; use crate::core::mq_client; use crate::core::pathfinding::PathfindingInputError; use crate::core::pathfinding::PathfindingNotFound; +use crate::core::simulation::SimulationResponse; use crate::core::version::CoreVersionRequest; use crate::core::AsCoreRequest; use crate::core::CoreClient; @@ -93,6 +94,7 @@ use crate::map::MapLayers; use crate::models; use crate::models::auth::PgAuthDriver; use crate::valkey_utils::ValkeyConfig; +use crate::views::path::pathfinding::PathfindingFailure; use crate::ValkeyClient; crate::routes! { @@ -171,6 +173,7 @@ pub struct InfraIdQueryParam { } #[derive(Debug, Serialize, ToSchema)] +#[cfg_attr(test, derive(PartialEq, Deserialize))] #[serde(tag = "status", rename_all = "snake_case")] enum SimulationSummaryResult { /// Minimal information on a simulation's result @@ -201,6 +204,47 @@ enum SimulationSummaryResult { PathfindingInputError(PathfindingInputError), } +impl From for SimulationSummaryResult { + fn from(sim: SimulationResponse) -> Self { + match sim { + SimulationResponse::Success { + final_output, + provisional, + base, + .. + } => { + let report = final_output.report_train; + Self::Success { + length: *report.positions.last().unwrap(), + time: *report.times.last().unwrap(), + energy_consumption: report.energy_consumption, + path_item_times_final: report.path_item_times.clone(), + path_item_times_provisional: provisional.path_item_times.clone(), + path_item_times_base: base.path_item_times.clone(), + } + } + SimulationResponse::PathfindingFailed { pathfinding_failed } => { + match pathfinding_failed { + PathfindingFailure::InternalError { core_error } => { + Self::PathfindingFailure { core_error } + } + + PathfindingFailure::PathfindingInputError(input_error) => { + Self::PathfindingInputError(input_error) + } + + PathfindingFailure::PathfindingNotFound(not_found) => { + Self::PathfindingNotFound(not_found) + } + } + } + SimulationResponse::SimulationFailed { core_error } => Self::SimulationFailed { + error_type: core_error.get_type().into(), + }, + } + } +} + /// Represents the bundle of information about the issuer of a request /// that can be extracted form recognized headers. #[derive(Debug, Clone)] @@ -662,6 +706,71 @@ mod tests { use super::test_app::TestAppBuilder; use crate::core::mocking::MockingClient; + #[cfg(test)] + pub fn mocked_core_pathfinding_sim_and_proj(train_id: i64) -> MockingClient { + 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": [0,1,2,3], + "length": 1, + "status": "success" + })) + .finish(); + core.stub("/v2/standalone_simulation") + .method(reqwest::Method::POST) + .response(StatusCode::OK) + .json(json!({ + "status": "success", + "base": { + "positions": [], + "times": [], + "speeds": [], + "energy_consumption": 0.0, + "path_item_times": [0, 1000, 2000, 3000] + }, + "provisional": { + "positions": [], + "times": [], + "speeds": [], + "energy_consumption": 0.0, + "path_item_times": [0, 1000, 2000, 3000] + }, + "final_output": { + "positions": [0], + "times": [0], + "speeds": [], + "energy_consumption": 0.0, + "path_item_times": [0, 1000, 2000, 3000], + "signal_critical_positions": [], + "zone_updates": [], + "spacing_requirements": [], + "routing_requirements": [] + }, + "mrsp": { + "boundaries": [], + "values": [] + }, + "electrical_profiles": { + "boundaries": [], + "values": [] + } + })) + .finish(); + core.stub("/v2/signal_projection") + .method(reqwest::Method::POST) + .response(StatusCode::OK) + .json(json!({ + "signal_updates": {train_id.to_string(): [] }, + })) + .finish(); + core + } + #[rstest] async fn health() { let app = TestAppBuilder::default_app(); diff --git a/editoast/src/views/paced_train.rs b/editoast/src/views/paced_train.rs index 9bc670b776e..8c5f2f5435e 100644 --- a/editoast/src/views/paced_train.rs +++ b/editoast/src/views/paced_train.rs @@ -1,6 +1,13 @@ 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; @@ -11,6 +18,7 @@ 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; @@ -19,17 +27,13 @@ use utoipa::ToSchema; use super::path::pathfinding::PathfindingResult; use super::projection::ProjectPathForm; +use super::train_schedule::train_simulation_batch; 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::views::projection::ProjectPathTrainResult; use crate::views::AuthorizationError; -use crate::views::ListId; crate::routes! { "/paced_train" => { @@ -56,10 +60,13 @@ editoast_common::schemas! { enum PacedTrainError { #[error("{count} paced train(s) could not be found")] #[editoast_error(status = 404)] - BatchPacedTrainNotFound { count: usize }, + BatchNotFound { count: usize }, + #[error("Paced train '{paced_train_id}', could not be found")] #[editoast_error(status = 404)] - #[error("Paced train {paced_train_id} does not exist")] - PacedTrainNotFound { paced_train_id: i64 }, + NotFound { paced_train_id: i64 }, + #[error("Infra '{infra_id}', could not be found")] + #[editoast_error(status = 404)] + InfraNotFound { infra_id: i64 }, #[error(transparent)] #[editoast_error(status = 500)] Database(#[from] editoast_models::model::Error), @@ -67,7 +74,7 @@ enum PacedTrainError { #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct PacedTrainForm { - /// Timetable attached to the train schedule + /// Timetable attached to the paced train pub timetable_id: Option, #[serde(flatten)] pub paced_train_base: PacedTrainBase, @@ -121,7 +128,7 @@ async fn get_by_id( let conn = &mut db_pool.get().await?; let paced_train = PacedTrain::retrieve_or_fail(conn, paced_train_id, || { - PacedTrainError::PacedTrainNotFound { paced_train_id } + PacedTrainError::NotFound { paced_train_id } }) .await?; @@ -156,7 +163,7 @@ async fn delete( let conn = &mut db_pool.get().await?; PacedTrain::delete_batch_or_fail(conn, paced_train_ids, |count| { - PacedTrainError::BatchPacedTrainNotFound { count } + PacedTrainError::BatchNotFound { count } }) .await?; @@ -202,19 +209,64 @@ struct SimulationBatchForm { )] async fn simulation_summary( State(AppState { - db_pool: _db_pool, - valkey: _valkey_client, - core_client: _core, + db_pool, + valkey: valkey_client, + core_client, .. }): State, - Extension(_auth): AuthenticationExt, + Extension(auth): AuthenticationExt, Json(SimulationBatchForm { - infra_id: _infra_id, - electrical_profile_set_id: _electrical_profile_set_id, - ids: _paced_train_ids, + infra_id, + electrical_profile_set_id, + ids: paced_train_ids, }): Json, ) -> Result>> { - todo!(); + let authorized = auth + .check_roles([BuiltinRole::OperationalStudies].into()) + .await + .map_err(AuthorizationError::AuthError)?; + if !authorized { + return Err(AuthorizationError::Forbidden.into()); + } + + let conn = &mut db_pool.get().await?; + + let infra = Infra::retrieve_or_fail(conn, infra_id, || PacedTrainError::InfraNotFound { + infra_id, + }) + .await?; + + let paced_trains: Vec = + PacedTrain::retrieve_batch_or_fail(conn, paced_train_ids, |missing| { + PacedTrainError::BatchNotFound { + count: missing.len(), + } + }) + .await?; + let paced_trains_to_ts: Vec = paced_trains + .iter() + .cloned() + .map(PacedTrain::into_first_occurrence) + .collect(); + + let simulations = train_simulation_batch( + conn, + valkey_client, + core_client, + &paced_trains_to_ts, + &infra, + electrical_profile_set_id, + ) + .await?; + + // Transform simulations to simulation summary + let simulation_summaries = paced_trains + .into_iter() + .zip(simulations) + .map(|(paced_train, (sim, _))| (paced_train.id, SimulationSummaryResult::from(sim))) + .collect(); + + Ok(Json(simulation_summaries)) } /// Get a path from a paced train given an infrastructure id and a paced train id @@ -231,7 +283,7 @@ async fn get_path( State(AppState { db_pool: _db_pool, valkey: _valkey_client, - core_client: _core, + core_client: _core_client, .. }): State, Extension(_auth): AuthenticationExt, @@ -262,23 +314,53 @@ pub struct ElectricalProfileSetIdQueryParam { )] async fn simulation( State(AppState { - valkey: _valkey_client, - core_client: _core_client, - db_pool: _db_pool, + valkey: valkey_client, + core_client, + db_pool, .. }): 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, Query(ElectricalProfileSetIdQueryParam { - electrical_profile_set_id: _electrical_profile_set_id, + electrical_profile_set_id, }): Query, ) -> Result> { - todo!(); + let authorized = auth + .check_roles([BuiltinRole::OperationalStudies].into()) + .await + .map_err(AuthorizationError::AuthError)?; + if !authorized { + return Err(AuthorizationError::Forbidden.into()); + } + + // Retrieve infra or fail + let infra = Infra::retrieve_or_fail(&mut db_pool.get().await?, infra_id, || { + PacedTrainError::InfraNotFound { infra_id } + }) + .await?; + + // Retrieve paced_train or fail + let paced_train = + PacedTrain::retrieve_or_fail(&mut db_pool.get().await?, paced_train_id, || { + PacedTrainError::NotFound { paced_train_id } + }) + .await?; + + // Compute simulation of a paced_train + let (simulation, _) = train_simulation_batch( + &mut db_pool.get().await?, + valkey_client, + core_client, + &[paced_train.into_first_occurrence()], + &infra, + electrical_profile_set_id, + ) + .await? + .pop() + .unwrap(); + + Ok(Json(simulation)) } /// Projects the space time curves and paths of a number of paced trains onto a given path @@ -315,19 +397,37 @@ async fn project_path( #[cfg(test)] mod tests { - use axum::http::StatusCode; - use pretty_assertions::assert_eq; + use std::collections::HashMap; + + 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 rstest::rstest; use serde_json::json; - use crate::error::InternalError; + use crate::core::simulation::CompleteReportTrain; + use crate::core::simulation::ElectricalProfiles; + use crate::core::simulation::ReportTrain; + use crate::core::simulation::SimulationResponse; + use crate::core::simulation::SpeedLimitProperties; + use crate::models::fixtures::create_fast_rolling_stock; use crate::models::fixtures::create_simple_paced_train; + use crate::models::fixtures::create_small_infra; use crate::models::fixtures::create_timetable; use crate::models::fixtures::simple_paced_train_base; use crate::models::paced_train::PacedTrain; + use crate::models::paced_train::PacedTrainChangeset; use crate::models::prelude::*; use crate::views::paced_train::PacedTrainResult; + 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() { @@ -377,12 +477,8 @@ mod tests { .assert_status(StatusCode::NOT_FOUND) .json_into(); - assert_eq!( - &response.error_type, - "editoast:paced_train:PacedTrainNotFound" - ) + assert_eq!(&response.error_type, "editoast:paced_train:NotFound") } - #[rstest] async fn get_paced_train() { let app = TestAppBuilder::default_app(); @@ -406,4 +502,146 @@ mod tests { ); assert_eq!(paced_train.step, response.paced_train.paced.step.into()); } + + async fn app_infra_id_paced_train_id_for_simulation_tests() -> (TestApp, i64, i64) { + let db_pool = DbConnectionPoolV2::for_tests(); + let small_infra = create_small_infra(&mut db_pool.get_ok()).await; + let timetable = create_timetable(&mut db_pool.get_ok()).await; + let rolling_stock = + create_fast_rolling_stock(&mut db_pool.get_ok(), "simulation_rolling_stock").await; + let paced_train_base: PacedTrainBase = PacedTrainBase { + train_schedule_base: TrainScheduleBase { + rolling_stock_name: rolling_stock.name.clone(), + ..serde_json::from_str(include_str!("../tests/train_schedules/simple.json")) + .expect("Unable to parse") + }, + paced: Paced { + duration: Duration::hours(1).try_into().unwrap(), + step: Duration::minutes(15).try_into().unwrap(), + }, + }; + let paced_train: PacedTrainChangeset = paced_train_base.into(); + let paced_train = paced_train + .timetable_id(timetable.id) + .create(&mut db_pool.get_ok()) + .await + .expect("Failed to create paced train"); + let core = mocked_core_pathfinding_sim_and_proj(paced_train.id); + let app = TestAppBuilder::new() + .db_pool(db_pool.clone()) + .core_client(core.into()) + .build(); + (app, small_infra.id, paced_train.id) + } + + #[rstest] + async fn paced_train_simulation() { + let (app, infra_id, train_schedule_id) = + app_infra_id_paced_train_id_for_simulation_tests().await; + let request = app.get( + format!( + "/paced_train/{}/simulation/?infra_id={}", + train_schedule_id, infra_id + ) + .as_str(), + ); + let response: SimulationResponse = + app.fetch(request).assert_status(StatusCode::OK).json_into(); + + assert_eq!( + response, + SimulationResponse::Success { + base: ReportTrain { + positions: vec![], + times: vec![], + speeds: vec![], + energy_consumption: 0.0, + path_item_times: vec![0, 1000, 2000, 3000] + }, + provisional: ReportTrain { + positions: vec![], + times: vec![], + speeds: vec![], + energy_consumption: 0.0, + path_item_times: vec![0, 1000, 2000, 3000] + }, + final_output: CompleteReportTrain { + report_train: ReportTrain { + positions: vec![0], + times: vec![0], + speeds: vec![], + energy_consumption: 0.0, + path_item_times: vec![0, 1000, 2000, 3000] + }, + signal_critical_positions: vec![], + zone_updates: vec![], + spacing_requirements: vec![], + routing_requirements: vec![] + }, + mrsp: SpeedLimitProperties { + boundaries: vec![], + values: vec![] + }, + electrical_profiles: ElectricalProfiles { + boundaries: vec![], + values: vec![] + } + } + ); + } + + #[rstest] + async fn paced_train_simulation_not_found() { + let (app, infra_id, _paced_train_id) = + app_infra_id_paced_train_id_for_simulation_tests().await; + let request = + app.get(format!("/paced_train/{}/simulation/?infra_id={}", 0, infra_id).as_str()); + + 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 paced_train_simulation_summary() { + let (app, infra_id, paced_train_id) = + app_infra_id_paced_train_id_for_simulation_tests().await; + let request = app.post("/paced_train/simulation_summary").json(&json!({ + "infra_id": infra_id, + "ids": vec![paced_train_id], + })); + let response: HashMap = + app.fetch(request).assert_status(StatusCode::OK).json_into(); + assert_eq!(response.len(), 1); + assert_eq!( + *response.get(&paced_train_id).unwrap(), + SimulationSummaryResult::Success { + length: 0, + time: 0, + energy_consumption: 0.0, + path_item_times_final: vec![0, 1000, 2000, 3000], + path_item_times_provisional: vec![0, 1000, 2000, 3000], + path_item_times_base: vec![0, 1000, 2000, 3000] + } + ); + } + + #[rstest] + async fn paced_train_simulation_summary_not_found() { + let (app, infra_id, _paced_train_id) = + app_infra_id_paced_train_id_for_simulation_tests().await; + let request = app.post("/paced_train/simulation_summary").json(&json!({ + "infra_id": infra_id, + "ids": vec![0], + })); + let response: InternalError = app + .fetch(request) + .assert_status(StatusCode::NOT_FOUND) + .json_into(); + + assert_eq!(&response.error_type, "editoast:paced_train:BatchNotFound") + } } diff --git a/editoast/src/views/train_schedule.rs b/editoast/src/views/train_schedule.rs index 565cce6e9e7..eec1768e3d7 100644 --- a/editoast/src/views/train_schedule.rs +++ b/editoast/src/views/train_schedule.rs @@ -49,7 +49,6 @@ use crate::models::prelude::*; use crate::models::train_schedule::TrainSchedule; use crate::models::train_schedule::TrainScheduleChangeset; use crate::views::path::pathfinding::pathfinding_from_train; -use crate::views::path::pathfinding::PathfindingFailure; use crate::views::path::pathfinding::PathfindingResult; use crate::views::path::pathfinding_from_train_batch; use crate::views::path::projection::PathProjection; @@ -696,44 +695,7 @@ async fn simulation_summary( let mut simulation_summaries = HashMap::new(); for (train_schedule, sim) in train_schedules.iter().zip(simulations) { let (sim, _) = sim; - let simulation_summary_result = match sim { - SimulationResponse::Success { - final_output, - provisional, - base, - .. - } => { - let report = final_output.report_train; - SimulationSummaryResult::Success { - length: *report.positions.last().unwrap(), - time: *report.times.last().unwrap(), - energy_consumption: report.energy_consumption, - path_item_times_final: report.path_item_times.clone(), - path_item_times_provisional: provisional.path_item_times.clone(), - path_item_times_base: base.path_item_times.clone(), - } - } - SimulationResponse::PathfindingFailed { pathfinding_failed } => { - match pathfinding_failed { - PathfindingFailure::InternalError { core_error } => { - SimulationSummaryResult::PathfindingFailure { core_error } - } - - PathfindingFailure::PathfindingInputError(input_error) => { - SimulationSummaryResult::PathfindingInputError(input_error) - } - - PathfindingFailure::PathfindingNotFound(not_found) => { - SimulationSummaryResult::PathfindingNotFound(not_found) - } - } - } - SimulationResponse::SimulationFailed { core_error } => { - SimulationSummaryResult::SimulationFailed { - error_type: core_error.get_type().into(), - } - } - }; + let simulation_summary_result = SimulationSummaryResult::from(sim); simulation_summaries.insert(train_schedule.id, simulation_summary_result); } @@ -996,7 +958,7 @@ async fn project_path( } #[cfg(test)] -mod tests { +pub mod tests { use axum::http::StatusCode; use chrono::DateTime; use chrono::Utc; @@ -1005,7 +967,6 @@ mod tests { use serde_json::json; use super::*; - use crate::core::mocking::MockingClient; use crate::models::fixtures::create_fast_rolling_stock; use crate::models::fixtures::create_simple_train_schedule; use crate::models::fixtures::create_small_infra; @@ -1013,6 +974,7 @@ mod tests { use crate::models::fixtures::simple_train_schedule_base; use crate::views::test_app::TestApp; use crate::views::test_app::TestAppBuilder; + use crate::views::tests::mocked_core_pathfinding_sim_and_proj; #[rstest] async fn train_schedule_get() { @@ -1105,70 +1067,6 @@ mod tests { ) } - fn mocked_core_pathfinding_sim_and_proj(train_id: i64) -> MockingClient { - 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": [0,1,2,3], - "length": 1, - "status": "success" - })) - .finish(); - core.stub("/v2/standalone_simulation") - .method(reqwest::Method::POST) - .response(StatusCode::OK) - .json(json!({ - "status": "success", - "base": { - "positions": [], - "times": [], - "speeds": [], - "energy_consumption": 0.0, - "path_item_times": [0, 1000, 2000, 3000] - }, - "provisional": { - "positions": [], - "times": [], - "speeds": [], - "energy_consumption": 0.0, - "path_item_times": [0, 1000, 2000, 3000] - }, - "final_output": { - "positions": [0], - "times": [0], - "speeds": [], - "energy_consumption": 0.0, - "path_item_times": [0, 1000, 2000, 3000], - "signal_critical_positions": [], - "zone_updates": [], - "spacing_requirements": [], - "routing_requirements": [] - }, - "mrsp": { - "boundaries": [], - "values": [] - }, - "electrical_profiles": { - "boundaries": [], - "values": [] - } - })) - .finish(); - core.stub("/v2/signal_projection") - .method(reqwest::Method::POST) - .response(StatusCode::OK) - .json(json!({ - "signal_updates": {train_id.to_string(): [] }, - })) - .finish(); - core - } - async fn app_infra_id_train_schedule_id_for_simulation_tests() -> (TestApp, i64, i64) { let db_pool = DbConnectionPoolV2::for_tests(); let small_infra = create_small_infra(&mut db_pool.get_ok()).await; diff --git a/front/public/locales/en/errors.json b/front/public/locales/en/errors.json index c67412b5f62..8789c998b8b 100644 --- a/front/public/locales/en/errors.json +++ b/front/public/locales/en/errors.json @@ -241,8 +241,9 @@ }, "paced_train": { "Database": "Internal error (database)", - "BatchPacedTrainNotFound": "Some Paced trains could not be found", - "PacedTrainNotFound": "Paced train '{{paced_train_id}}' not found" + "BatchNotFound": "'{{count}}' paced train(s) could not be found", + "NotFound": "Paced train '{{paced_train_id}}' could not be found", + "InfraNotFound": "Infrastructure '{{infra_id}}' could not be found" }, "url": { "InvalidUrl": "Invalid url '{{url}}'" diff --git a/front/public/locales/fr/errors.json b/front/public/locales/fr/errors.json index e433ed94c18..7152ebbc15f 100644 --- a/front/public/locales/fr/errors.json +++ b/front/public/locales/fr/errors.json @@ -238,8 +238,9 @@ }, "paced_train": { "Database": "Erreur interne (base de données)", - "BatchPacedTrainNotFound": "Certaines missions sont introuvables", - "PacedTrainNotFound": "Mission '{{paced_train_id}}' non trouvée" + "BatchNotFound": "'{{count}}' mission(s) sont introuvable(s)", + "NotFound": "Mission '{{paced_train_id}}' non trouvée", + "InfraNotFound": "Infrastructure '{{infra_id}}' non trouvée" }, "url": { "InvalidUrl": "Url invalide '{{url}}'" diff --git a/front/src/common/api/generatedEditoastApi.ts b/front/src/common/api/generatedEditoastApi.ts index ab8bba6e601..f347220a4ed 100644 --- a/front/src/common/api/generatedEditoastApi.ts +++ b/front/src/common/api/generatedEditoastApi.ts @@ -493,7 +493,7 @@ const injectedRtkApi = api }), invalidatesTags: ['paced_train'], }), - postPacedTrainSimulationSummary: build.mutation< + postPacedTrainSimulationSummary: build.query< PostPacedTrainSimulationSummaryApiResponse, PostPacedTrainSimulationSummaryApiArg >({ @@ -502,7 +502,7 @@ const injectedRtkApi = api method: 'POST', body: queryArg.body, }), - invalidatesTags: ['paced_train'], + providesTags: ['paced_train'], }), getPacedTrainById: build.query({ query: (queryArg) => ({ url: `/paced_train/${queryArg.id}` }), @@ -3250,7 +3250,7 @@ export type PacedTrainResult = PacedTrainBase & { timetable_id: number; }; export type PacedTrainForm = PacedTrainBase & { - /** Timetable attached to the train schedule */ + /** Timetable attached to the paced train */ timetable_id?: number | null; }; export type ReportTrain = { diff --git a/front/src/config/openapi-editoast-config.cts b/front/src/config/openapi-editoast-config.cts index dd5f3cd7b71..12b2cf7ce7a 100644 --- a/front/src/config/openapi-editoast-config.cts +++ b/front/src/config/openapi-editoast-config.cts @@ -15,7 +15,7 @@ const config: ConfigFile = { 'postInfraByInfraIdPathfinding', 'postInfraByInfraIdPathfindingBlocks', 'postInfraByInfraIdPathProperties', - 'postTrainSchedule', + 'postPacedTrainSimulationSummary', 'postTrainScheduleSimulationSummary', 'postTrainScheduleProjectPath', 'postWorkSchedulesProjectPath',