diff --git a/editoast/openapi.yaml b/editoast/openapi.yaml index 7964e836f30..09bf1b1fa53 100644 --- a/editoast/openapi.yaml +++ b/editoast/openapi.yaml @@ -859,9 +859,7 @@ components: name: type: string power_restrictions: - additionalProperties: - type: string - type: object + $ref: '#/components/schemas/DieselJson' railjson_version: type: string rolling_resistance: @@ -1874,10 +1872,7 @@ components: name: type: string power_restrictions: - additionalProperties: - type: string - description: Mapping of power restriction code to power class - type: object + $ref: '#/components/schemas/DieselJson' raise_pantograph_time: description: The time it takes to raise this train's pantograph in seconds. Is null if the train is not electric. example: 15.0 diff --git a/editoast/src/models/rolling_stock/light_rolling_stock.rs b/editoast/src/models/rolling_stock/light_rolling_stock.rs index 85f0efa65f3..4e92f8d327f 100644 --- a/editoast/src/models/rolling_stock/light_rolling_stock.rs +++ b/editoast/src/models/rolling_stock/light_rolling_stock.rs @@ -3,7 +3,9 @@ use crate::models::rolling_stock::rolling_stock_livery::RollingStockLiveryMetada use crate::schema::rolling_stock::light_rolling_stock::{ LightEffortCurves, LightRollingStock, LightRollingStockWithLiveries, }; -use crate::schema::rolling_stock::{EnergySource, Gamma, RollingResistance, RollingStockMetadata}; +use crate::schema::rolling_stock::{ + EnergySource, Gamma, PowerRestriction, RollingResistance, RollingStockMetadata, +}; use crate::tables::rolling_stock; use crate::views::pagination::{Paginate, PaginatedResponse}; use crate::DbPool; @@ -14,7 +16,6 @@ use diesel_async::RunQueryDsl; use diesel_json::Json as DieselJson; use editoast_derive::Model; use serde::Serialize; -use serde_json::Value as JsonValue; use utoipa::ToSchema; #[derive(Debug, Model, Queryable, QueryableByName, Serialize, ToSchema)] @@ -43,8 +44,8 @@ pub struct LightRollingStockModel { rolling_resistance: DieselJson, #[schema(value_type = LoadingGaugeType)] loading_gauge: String, - #[schema(value_type = HashMap)] - power_restrictions: Option, + #[schema(value_type = PowerRestriction)] + power_restrictions: Option>, #[schema(value_type = Vec)] energy_sources: DieselJson>, locked: bool, diff --git a/editoast/src/models/rolling_stock/mod.rs b/editoast/src/models/rolling_stock/mod.rs index b630a5eaa44..2120b75024b 100644 --- a/editoast/src/models/rolling_stock/mod.rs +++ b/editoast/src/models/rolling_stock/mod.rs @@ -12,8 +12,8 @@ use crate::error::{InternalError, Result}; use crate::models::rolling_stock::rolling_stock_livery::RollingStockLiveryMetadata; use crate::models::{Create, Identifiable, TextArray, Update}; use crate::schema::rolling_stock::{ - EffortCurves, EnergySource, Gamma, RollingResistance, RollingStock, RollingStockCommon, - RollingStockMetadata, RollingStockWithLiveries, + EffortCurves, EnergySource, Gamma, PowerRestriction, RollingResistance, RollingStock, + RollingStockCommon, RollingStockMetadata, RollingStockWithLiveries, }; use crate::schema::track_section::LoadingGaugeType; use crate::tables::rolling_stock; @@ -27,7 +27,6 @@ use diesel_async::{AsyncPgConnection as PgConnection, RunQueryDsl}; use diesel_json::Json as DieselJson; use editoast_derive::Model; use serde::{Deserialize, Serialize}; -use serde_json::Value as JsonValue; use validator::{Validate, ValidationError}; crate::schemas! { @@ -109,9 +108,9 @@ pub struct RollingStockModel { #[diesel(deserialize_as = String)] #[schema(value_type = LoadingGaugeType)] pub loading_gauge: Option, - #[diesel(deserialize_as = Option)] - #[schema(value_type = Option>)] - pub power_restrictions: Option>, + #[diesel(deserialize_as = Option>)] + #[schema(value_type = Option>)] + pub power_restrictions: Option>, #[diesel(deserialize_as = DieselJson>)] #[schema(value_type = EnergySource)] pub energy_sources: Option>>, @@ -289,7 +288,7 @@ impl From for RollingStockCommon { mass: rolling_stock_model.mass.unwrap(), rolling_resistance: rolling_stock_model.rolling_resistance.unwrap().0, loading_gauge: rolling_stock_model.loading_gauge.unwrap(), - power_restrictions: rolling_stock_model.power_restrictions.unwrap(), + power_restrictions: rolling_stock_model.power_restrictions, energy_sources: rolling_stock_model.energy_sources.unwrap().0, electrical_power_startup_time: rolling_stock_model .electrical_power_startup_time diff --git a/editoast/src/schema/errors.rs b/editoast/src/schema/errors.rs index 580a9e615f7..4a63116e587 100644 --- a/editoast/src/schema/errors.rs +++ b/editoast/src/schema/errors.rs @@ -4,14 +4,13 @@ use serde::{Deserialize, Serialize}; use strum_macros::EnumVariantNames; #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(deny_unknown_fields)] pub struct InfraError { obj_id: String, obj_type: ObjectType, field: Option, is_warning: bool, #[serde(flatten)] - sub_type: InfraErrorType, + error_type: InfraErrorType, } #[derive(Serialize, Deserialize, PartialEq, Debug, EnumVariantNames, Clone)] @@ -71,7 +70,7 @@ impl InfraError { obj_type: obj.get_type(), field: Some(field.as_ref().into()), is_warning: false, - sub_type: InfraErrorType::InvalidReference { reference }, + error_type: InfraErrorType::InvalidReference { reference }, } } @@ -86,7 +85,7 @@ impl InfraError { obj_type: obj.get_type(), field: Some(field.as_ref().into()), is_warning: false, - sub_type: InfraErrorType::OutOfRange { + error_type: InfraErrorType::OutOfRange { position, expected_range, }, @@ -99,7 +98,7 @@ impl InfraError { obj_type: obj.get_type(), field: None, // This error concern the whole object consistency is_warning: false, - sub_type: InfraErrorType::InvalidRoute, + error_type: InfraErrorType::InvalidRoute, } } @@ -109,7 +108,7 @@ impl InfraError { obj_type: obj.get_type(), field: Some(field.as_ref().into()), is_warning: true, - sub_type: InfraErrorType::EmptyObject, + error_type: InfraErrorType::EmptyObject, } } @@ -123,7 +122,7 @@ impl InfraError { obj_type: route.get_type(), field: Some(field.as_ref().into()), is_warning: false, - sub_type: InfraErrorType::ObjectOutOfPath { reference }, + error_type: InfraErrorType::ObjectOutOfPath { reference }, } } @@ -133,7 +132,7 @@ impl InfraError { obj_type: ObjectType::TrackSection, field: Default::default(), is_warning: true, - sub_type: InfraErrorType::MissingRoute, + error_type: InfraErrorType::MissingRoute, } } @@ -148,7 +147,7 @@ impl InfraError { obj_type: obj.get_type(), field: Some(field.as_ref().into()), is_warning: false, - sub_type: InfraErrorType::UnknownPortName { port_name }, + error_type: InfraErrorType::UnknownPortName { port_name }, } } @@ -158,7 +157,7 @@ impl InfraError { obj_type: obj.get_type(), field: Some(field.as_ref().into()), is_warning: false, - sub_type: InfraErrorType::InvalidSwitchPorts, + error_type: InfraErrorType::InvalidSwitchPorts, } } @@ -172,7 +171,7 @@ impl InfraError { obj_type: obj.get_type(), field: Some(field.as_ref().into()), is_warning: true, - sub_type: InfraErrorType::UnusedPort { + error_type: InfraErrorType::UnusedPort { port_name: port_name.as_ref().into(), }, } @@ -188,7 +187,7 @@ impl InfraError { obj_type: obj.get_type(), field: Some(field.as_ref().into()), is_warning: true, - sub_type: InfraErrorType::DuplicatedGroup { + error_type: InfraErrorType::DuplicatedGroup { original_group_path, }, } @@ -205,7 +204,7 @@ impl InfraError { obj_type: obj.get_type(), field: Some(field.as_ref().into()), is_warning: false, - sub_type: InfraErrorType::InvalidGroup { + error_type: InfraErrorType::InvalidGroup { group: group.as_ref().into(), switch_type: switch_type.as_ref().into(), }, @@ -218,7 +217,7 @@ impl InfraError { obj_type: obj.get_type(), field: None, is_warning: true, - sub_type: InfraErrorType::MissingBufferStop { endpoint }, + error_type: InfraErrorType::MissingBufferStop { endpoint }, } } @@ -228,7 +227,7 @@ impl InfraError { obj_type: obj.get_type(), field: None, is_warning: true, - sub_type: InfraErrorType::OddBufferStopLocation, + error_type: InfraErrorType::OddBufferStopLocation, } } @@ -248,7 +247,7 @@ impl InfraError { obj_type: ObjectType::SpeedSection, field: Default::default(), is_warning: true, - sub_type: InfraErrorType::OverlappingSpeedSections { reference }, + error_type: InfraErrorType::OverlappingSpeedSections { reference }, } } @@ -263,7 +262,7 @@ impl InfraError { obj_type: ObjectType::Catenary, field: Default::default(), is_warning: true, - sub_type: InfraErrorType::OverlappingCatenaries { reference }, + error_type: InfraErrorType::OverlappingCatenaries { reference }, } } @@ -274,12 +273,12 @@ impl InfraError { obj_type: obj.get_type(), field: Default::default(), is_warning: false, - sub_type: InfraErrorType::OverlappingSwitches { reference }, + error_type: InfraErrorType::OverlappingSwitches { reference }, } } - pub fn get_sub_type(&self) -> &InfraErrorType { - &self.sub_type + pub fn get_error_type(&self) -> &InfraErrorType { + &self.error_type } } diff --git a/editoast/src/schema/geo_json.rs b/editoast/src/schema/geo_json.rs index 0168b25e4e6..2f562dcaebd 100644 --- a/editoast/src/schema/geo_json.rs +++ b/editoast/src/schema/geo_json.rs @@ -3,7 +3,7 @@ use mvt::GeomType; use serde::{Deserialize, Serialize}; /// GeoJson representation -#[derive(Clone, Serialize, Deserialize, Debug)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] #[serde(tag = "type")] pub enum GeoJson { Point { coordinates: (f64, f64) }, diff --git a/editoast/src/schema/rolling_stock/light_rolling_stock.rs b/editoast/src/schema/rolling_stock/light_rolling_stock.rs index d84cdd11572..b41c46920ff 100644 --- a/editoast/src/schema/rolling_stock/light_rolling_stock.rs +++ b/editoast/src/schema/rolling_stock/light_rolling_stock.rs @@ -1,13 +1,12 @@ use diesel::sql_types::{Array, BigInt, Bool, Double, Jsonb, Nullable, Text}; use diesel_json::Json as DieselJson; use serde::{Deserialize, Serialize}; -use serde_json::Value as JsonValue; use std::collections::HashMap; use utoipa::ToSchema; use crate::models::rolling_stock::rolling_stock_livery::RollingStockLiveryMetadata; -use super::{EnergySource, Gamma, RollingResistance, RollingStockMetadata}; +use super::{EnergySource, Gamma, PowerRestriction, RollingResistance, RollingStockMetadata}; crate::schemas! { LightRollingStock, @@ -60,8 +59,8 @@ pub struct LightRollingStock { #[schema(value_type = RollingStockMetadata)] pub metadata: DieselJson, #[diesel(sql_type = Nullable)] - #[schema(value_type = HashMap)] - pub power_restrictions: Option, + #[schema(value_type = DieselJson)] + pub power_restrictions: Option>, #[diesel(sql_type = Jsonb)] #[schema(value_type = Vec)] pub energy_sources: DieselJson>, diff --git a/editoast/src/schema/rolling_stock/mod.rs b/editoast/src/schema/rolling_stock/mod.rs index 7fa6a89906d..6c9b9b3ae78 100644 --- a/editoast/src/schema/rolling_stock/mod.rs +++ b/editoast/src/schema/rolling_stock/mod.rs @@ -1,8 +1,8 @@ pub mod light_rolling_stock; pub mod rolling_stock_livery; +use diesel_json::Json as DieselJson; use serde::{Deserialize, Deserializer, Serialize}; -use serde_json::Value as JsonValue; use std::collections::HashMap; use strum_macros::{Display, EnumString}; use utoipa::ToSchema; @@ -52,8 +52,8 @@ pub struct RollingStockCommon { #[schema(value_type = LoadingGaugeType)] pub loading_gauge: String, /// Mapping of power restriction code to power class - #[schema(value_type = HashMap)] - pub power_restrictions: Option, + #[schema(value_type = DieselJson)] + pub power_restrictions: Option>, #[serde(default)] pub energy_sources: Vec, /// The time the train takes before actually using electrical power (in seconds). Is null if the train is not electric. @@ -261,6 +261,9 @@ pub enum EnergySource { }, } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct PowerRestriction(HashMap); + #[cfg(test)] mod tests { use crate::schema::rolling_stock::EffortCurve; diff --git a/editoast/src/tests/example_rolling_stock_1.json b/editoast/src/tests/example_rolling_stock_1.json index 01f052639fa..b7621a9b312 100644 --- a/editoast/src/tests/example_rolling_stock_1.json +++ b/editoast/src/tests/example_rolling_stock_1.json @@ -981,7 +981,7 @@ "unit": "" }, "energy_sources": [], - "power_restrictions": {"C2":"2"}, + "power_restrictions": {"C2":"2", "C5":"5"}, "electrical_power_startup_time": 5.0, "raise_pantograph_time": 15.0 } diff --git a/editoast/src/views/infra/auto_fixes.rs b/editoast/src/views/infra/auto_fixes.rs index 12f21635fa4..0f0d0902f61 100644 --- a/editoast/src/views/infra/auto_fixes.rs +++ b/editoast/src/views/infra/auto_fixes.rs @@ -105,7 +105,7 @@ fn get_operations_fixing_error( error: &InfraError, infra_cache: &InfraCache, ) -> Result> { - match error.get_sub_type() { + match error.get_error_type() { InfraErrorType::InvalidReference { reference } => { get_operations_fixing_invalid_reference(error, reference, infra_cache) } @@ -284,11 +284,11 @@ mod test { // Check the only initial issues are "overlapping_speed_sections" warnings let infra_errors_before_all: PaginatedResponse = read_body_json(call_service(&app, errors_request(small_infra_id)).await).await; - assert!(infra_errors_before_all.results.iter().all(|e| e - .information - .get("error_type") - .unwrap() - == "overlapping_speed_sections")); + + assert!(infra_errors_before_all.results.iter().all(|e| matches!( + e.information.get_error_type(), + InfraErrorType::OverlappingSpeedSections { .. } + ))); // Remove a track let deletion = Operation::Delete(DeleteOperation { diff --git a/editoast/src/views/infra/errors.rs b/editoast/src/views/infra/errors.rs index 182c529022f..eef99631831 100644 --- a/editoast/src/views/infra/errors.rs +++ b/editoast/src/views/infra/errors.rs @@ -6,10 +6,9 @@ use actix_web::dev::HttpServiceFactory; use actix_web::get; use actix_web::web::{Data, Json as WebJson, Path, Query}; use diesel::sql_query; -use diesel::sql_types::{BigInt, Json, Text}; +use diesel::sql_types::{BigInt, Jsonb, Text}; use editoast_derive::EditoastError; use serde::{Deserialize, Serialize}; -use serde_json::Value as JsonValue; use strum::VariantNames; use thiserror::Error; @@ -65,10 +64,9 @@ enum ListErrorsErrors { } #[derive(QueryableByName, Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(deny_unknown_fields)] pub struct InfraError { - #[diesel(sql_type = Json)] - pub information: JsonValue, + #[diesel(sql_type = Jsonb)] + pub information: diesel_json::Json, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -87,8 +85,7 @@ async fn get_paginated_infra_errors( per_page: i64, params: &QueryParams, ) -> Result> { - let mut query = - String::from("SELECT information::text FROM infra_layer_error WHERE infra_id = $1"); + let mut query = String::from("SELECT information FROM infra_layer_error WHERE infra_id = $1"); if params.level == Level::Warnings { query += " AND information->>'is_warning' = 'true'" } else if params.level == Level::Errors { diff --git a/editoast/src/views/infra/mod.rs b/editoast/src/views/infra/mod.rs index 475943ca85d..70315106548 100644 --- a/editoast/src/views/infra/mod.rs +++ b/editoast/src/views/infra/mod.rs @@ -35,7 +35,6 @@ use diesel::{sql_query, QueryableByName}; use diesel_async::RunQueryDsl; use editoast_derive::EditoastError; use serde::{Deserialize, Serialize}; -use serde_json::{json, Value as JsonValue}; use std::collections::HashMap; use thiserror::Error; use utoipa::IntoParams; @@ -128,6 +127,11 @@ struct RefreshQueryParams { infras: List, } +#[derive(Debug, Serialize)] +struct RefreshResponse { + infra_refreshed: Vec, +} + /// Refresh infra generated data #[post("/refresh")] async fn refresh( @@ -136,7 +140,7 @@ async fn refresh( query_params: Query, infra_caches: Data>, map_layers: Data, -) -> Result> { +) -> Result> { let mut conn = db_pool.get().await?; // Use a transaction to give scope to infra list lock let mut infras_list = vec![]; @@ -158,7 +162,7 @@ async fn refresh( } // Refresh each infras - let mut refreshed_infra = vec![]; + let mut infra_refreshed = vec![]; for infra in infras_list { let infra_cache = InfraCache::get_or_load(&mut conn, &infra_caches, &infra).await?; @@ -166,12 +170,12 @@ async fn refresh( .refresh(db_pool.clone(), query_params.force, &infra_cache) .await? { - refreshed_infra.push(infra.id.unwrap()); + infra_refreshed.push(infra.id.unwrap()); } } let mut conn = redis_client.get_connection().await?; - for infra_id in refreshed_infra.iter() { + for infra_id in infra_refreshed.iter() { map::invalidate_all( &mut conn, &map_layers.layers.keys().cloned().collect(), @@ -180,7 +184,7 @@ async fn refresh( .await?; } - Ok(Json(json!({ "infra_refreshed": refreshed_infra }))) + Ok(Json(RefreshResponse { infra_refreshed })) } /// Return a list of infras @@ -531,6 +535,7 @@ pub mod tests { use actix_web::test::{call_and_read_body_json, call_service, read_body_json, TestRequest}; use diesel_async::{AsyncConnection, AsyncPgConnection as PgConnection}; use rstest::*; + use serde_json::json; use strum::IntoEnumIterator; fn delete_infra_request(infra_id: i64) -> Request { diff --git a/editoast/src/views/infra/objects.rs b/editoast/src/views/infra/objects.rs index cb4c9040724..5ef29eb541a 100644 --- a/editoast/src/views/infra/objects.rs +++ b/editoast/src/views/infra/objects.rs @@ -6,12 +6,13 @@ use actix_web::web::{Data, Json, Path}; use diesel::sql_types::{Array, BigInt, Jsonb, Nullable, Text}; use diesel::{sql_query, QueryableByName}; use diesel_async::RunQueryDsl; +use diesel_json::Json as DieselJson; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; use thiserror::Error; use crate::error::Result; -use crate::schema::ObjectType; +use crate::schema::{GeoJson, ObjectType}; use crate::DbPool; use editoast_derive::EditoastError; @@ -37,17 +38,16 @@ fn has_unique_ids(obj_ids: &Vec) -> bool { obj_ids_2.len() == obj_ids.len() } -#[derive(QueryableByName, Debug, Clone, Serialize, Deserialize)] +#[derive(QueryableByName, Debug, Clone, Serialize, Deserialize, PartialEq)] struct ObjectQueryable { #[diesel(sql_type = Text)] - #[serde(skip_serializing)] obj_id: String, #[diesel(sql_type = Jsonb)] railjson: JsonValue, #[diesel(sql_type = Nullable)] - geographic: Option, + geographic: Option>, #[diesel(sql_type = Nullable)] - schematic: Option, + schematic: Option>, } /// Return the railjson list of a specific OSRD object @@ -121,11 +121,13 @@ async fn get_objects( #[cfg(test)] mod tests { use actix_web::http::StatusCode; - use actix_web::test::{call_service, TestRequest}; + use actix_web::test::{call_service, read_body_json, TestRequest}; + use serde_json::json; - use crate::fixtures::tests::{empty_infra, TestFixture}; + use crate::fixtures::tests::{empty_infra, small_infra, TestFixture}; use crate::models::Infra; use crate::schema::SwitchType; + use crate::views::infra::objects::ObjectQueryable; use crate::views::infra::tests::create_object_request; use crate::views::tests::create_test_service; use rstest::*; @@ -155,6 +157,68 @@ mod tests { assert_eq!(call_service(&app, req).await.status(), StatusCode::OK); } + #[rstest] + async fn get_objects_should_return_track_section(#[future] small_infra: TestFixture) { + let small_infra = small_infra.await; + let app = create_test_service().await; + + let expected_track_section_object = vec![ObjectQueryable { + obj_id: "TA0".into(), + railjson: json!({ + "curves": [], + "extensions": { + "sncf": { + "line_code": 424242, + "line_name": "West_parking", + "track_name": "V1", + "track_number": 1 + } + }, + "geo": { + "coordinates": [ + [ + -0.4, + 49.5 + ], + [ + -0.365, + 49.5 + ] + ], + "type": "LineString" + }, + "id": "TA0", + "length": 2000.0, + "loading_gauge_limits": [], + "sch": { + "coordinates": [ + [ + -0.4, + 49.5 + ], + [ + -0.365, + 49.5 + ] + ], + "type": "LineString" + }, + "slopes": [] + }), + geographic: None, + schematic: None, + }]; + + let req = TestRequest::post() + .uri(format!("/infra/{}/objects/TrackSection", small_infra.id()).as_str()) + .set_json(vec!["TA0"]) + .to_request(); + let response = call_service(&app, req).await; + assert_eq!(response.status(), StatusCode::OK); + let track_section_object: Vec = read_body_json(response).await; + assert_eq!(track_section_object, expected_track_section_object); + } + #[rstest] async fn get_objects_duplicate_ids(#[future] empty_infra: TestFixture) { let empty_infra = empty_infra.await; diff --git a/editoast/src/views/infra/routes.rs b/editoast/src/views/infra/routes.rs index 858e41206de..942ab59118d 100644 --- a/editoast/src/views/infra/routes.rs +++ b/editoast/src/views/infra/routes.rs @@ -12,7 +12,6 @@ use chashmap::CHashMap; use diesel::sql_query; use diesel::sql_types::{BigInt, Bool, Text}; use diesel_async::RunQueryDsl; -use serde_json::{json, Value as JsonValue}; use serde::{Deserialize, Serialize}; use strum_macros::Display; @@ -36,18 +35,30 @@ enum WaypointType { BufferStop, } +#[derive(Debug, Serialize, Deserialize, PartialEq)] +struct WaypointResponse { + starting: Vec, + ending: Vec, +} + +impl WaypointResponse { + pub fn new(starting: Vec, ending: Vec) -> Self { + Self { starting, ending } + } +} + /// Return the railjson list of a specific OSRD object -#[get("/{waypoint_type}/{waypoint}")] +#[get("/{waypoint_type}/{waypoint_id}")] async fn get_routes_from_waypoint( path: Path<(i64, WaypointType, String)>, db_pool: Data, -) -> Result> { - let (infra, waypoint_type, waypoint) = path.into_inner(); +) -> Result> { + let (infra, waypoint_type, waypoint_id) = path.into_inner(); let mut conn = db_pool.get().await?; let routes: Vec = sql_query(include_str!("sql/get_routes_from_waypoint.sql")) .bind::(infra) - .bind::(waypoint) + .bind::(waypoint_id) .bind::(waypoint_type.to_string()) .load(&mut conn) .await?; @@ -63,9 +74,7 @@ async fn get_routes_from_waypoint( } }); - Ok(Json( - json!({"starting": starting_routes, "ending": ending_routes}), - )) + Ok(Json(WaypointResponse::new(starting_routes, ending_routes))) } #[derive(Debug, Clone, Serialize, PartialEq)] @@ -119,3 +128,78 @@ async fn get_routes_track_ranges<'a>( Ok(Json(result)) } + +#[cfg(test)] +mod tests { + use actix_http::StatusCode; + use actix_web::test::{call_service, read_body_json, TestRequest}; + use rstest::rstest; + + use crate::{ + fixtures::tests::{db_pool, small_infra}, + views::{ + infra::routes::{WaypointResponse, WaypointType}, + tests::create_test_service, + }, + }; + + #[rstest] + async fn get_waypoint_should_return_da2_detector() { + let app = create_test_service().await; + let small_infra = small_infra(db_pool()).await; + let small_infra_id = small_infra.id(); + let waypoint_type = WaypointType::Detector; + let request = TestRequest::get() + .uri(format!("/infra/{small_infra_id}/routes/{waypoint_type}/DA2").as_str()) + .to_request(); + let response = call_service(&app, request).await; + assert_eq!(response.status(), StatusCode::OK); + let waypoint: WaypointResponse = read_body_json(response).await; + assert_eq!( + waypoint, + WaypointResponse::new( + vec!["rt.DA2->DA5".to_string()], + vec!["rt.buffer_stop.0->DA2".to_string()] + ) + ); + } + + #[rstest] + async fn get_waypoint_should_return_buffer_stop_number_4() { + let app = create_test_service().await; + let small_infra = small_infra(db_pool()).await; + let small_infra_id = small_infra.id(); + let waypoint_type = WaypointType::BufferStop; + let request = TestRequest::get() + .uri(format!("/infra/{small_infra_id}/routes/{waypoint_type}/buffer_stop.4").as_str()) + .to_request(); + let response = call_service(&app, request).await; + assert_eq!(response.status(), StatusCode::OK); + let waypoint: WaypointResponse = read_body_json(response).await; + assert_eq!( + waypoint, + WaypointResponse::new( + vec!["rt.buffer_stop.4->DD5".to_string()], + vec!["rt.DE0->buffer_stop.4".to_string()] + ) + ); + } + + #[rstest] + async fn get_waypoint_should_return_empty_response() { + let app = create_test_service().await; + let small_infra = small_infra(db_pool()).await; + let small_infra_id = small_infra.id(); + let waypoint_type = WaypointType::Detector; + let request = TestRequest::get() + .uri( + format!("/infra/{small_infra_id}/routes/{waypoint_type}/NOT_EXISTING_WAYPOINT_ID") + .as_str(), + ) + .to_request(); + let response = call_service(&app, request).await; + assert_eq!(response.status(), StatusCode::OK); + let waypoint: WaypointResponse = read_body_json(response).await; + assert_eq!(waypoint, WaypointResponse::new(vec![], vec![])); + } +} diff --git a/editoast/src/views/layers/mod.rs b/editoast/src/views/layers/mod.rs index c120afac132..9200716b16c 100644 --- a/editoast/src/views/layers/mod.rs +++ b/editoast/src/views/layers/mod.rs @@ -1,5 +1,7 @@ mod mvt_utils; +use std::collections::HashMap; + use crate::client::{get_root_url, MapLayersConfig}; use crate::error::Result; use crate::map::redis_utils::RedisClient; @@ -13,7 +15,6 @@ use diesel_async::RunQueryDsl; use editoast_derive::EditoastError; use mvt_utils::{create_and_fill_mvt_tile, get_geo_json_sql_query, GeoJsonAndData}; use serde::{Deserialize, Serialize}; -use serde_json::{json, Value as JsonValue}; use thiserror::Error; use utoipa::{IntoParams, ToSchema}; @@ -75,7 +76,10 @@ struct LayerViewParams { view_slug: String, } -#[derive(Serialize, ToSchema)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] +struct PromoteIdWrapper(HashMap); + +#[derive(Debug, Serialize, Deserialize, PartialEq, ToSchema)] struct ViewMetadata { #[serde(rename = "type")] data_type: String, @@ -83,7 +87,7 @@ struct ViewMetadata { name: String, #[serde(rename = "promoteId")] #[schema(example = json!({track_sections: "id"}), value_type = Object)] - promote_id: JsonValue, + promote_id: PromoteIdWrapper, #[schema(example = "xyz")] scheme: String, #[schema(example = json!(["http://localhost:7070/tile/track_sections/geo/{z}/{x}/{y}/?infra=1"]))] @@ -131,7 +135,10 @@ async fn layer_view( Ok(Json(ViewMetadata { data_type: "vector".to_owned(), name: layer_slug.to_owned(), - promote_id: json!({layer_slug: layer.id_field}), + promote_id: PromoteIdWrapper(HashMap::from([( + layer_slug, + layer.id_field.clone().unwrap_or_default(), + )])), scheme: "xyz".to_owned(), tiles: vec![tiles_url_pattern], attribution: layer.attribution.clone().unwrap_or_default(), @@ -218,25 +225,32 @@ async fn cache_and_get_mvt_tile( #[cfg(test)] mod tests { - use crate::error::InternalError; + use std::collections::HashMap; + use crate::map::MapLayers; use crate::views::tests::create_test_service; + use crate::{error::InternalError, views::layers::ViewMetadata}; use actix_web::{ http::StatusCode, test::{call_service, read_body_json, TestRequest}, }; use rstest::rstest; - use serde_json::{json, to_value, Value as JsonValue}; + use serde::de::DeserializeOwned; + use serde_json::to_value; - use super::LayersError; + use super::{LayersError, PromoteIdWrapper}; /// Run a simple get query on `uri` and check the status code and json body - async fn test_get_query(uri: &str, expected_status: StatusCode, expected_body: JsonValue) { + async fn test_get_query( + uri: &str, + expected_status: StatusCode, + expected_body: T, + ) { let app = create_test_service().await; let req = TestRequest::get().uri(uri).to_request(); let response = call_service(&app, req).await; assert_eq!(response.status(), expected_status); - let body: JsonValue = read_body_json(response).await; + let body: T = read_body_json(response).await; assert_eq!(expected_body, body) } @@ -245,18 +259,19 @@ mod tests { test_get_query( "/layers/layer/track_sections/mvt/geo?infra=2", StatusCode::OK, - json!({ - "type": "vector", - "name": "track_sections", - "promoteId": { - "track_sections": "id" - }, - "scheme": "xyz", - "tiles": [tiles], - "attribution": "", - "minzoom": 5, - "maxzoom": 18 - }), + ViewMetadata { + data_type: "vector".to_string(), + name: "track_sections".to_string(), + promote_id: PromoteIdWrapper(HashMap::from([( + "track_sections".to_string(), + "id".to_string(), + )])), + scheme: "xyz".to_string(), + tiles: vec![tiles], + attribution: "".to_string(), + minzoom: 5, + maxzoom: 18, + }, ) .await; } diff --git a/editoast/src/views/rolling_stocks/mod.rs b/editoast/src/views/rolling_stocks/mod.rs index b3cb4d29855..972e07194b1 100644 --- a/editoast/src/views/rolling_stocks/mod.rs +++ b/editoast/src/views/rolling_stocks/mod.rs @@ -141,7 +141,7 @@ impl From for RollingStockModel { mass: Some(rolling_stock.common.mass), rolling_resistance: Some(DieselJson(rolling_stock.common.rolling_resistance)), loading_gauge: Some(rolling_stock.common.loading_gauge), - power_restrictions: Some(rolling_stock.common.power_restrictions), + power_restrictions: rolling_stock.common.power_restrictions, energy_sources: Some(DieselJson(rolling_stock.common.energy_sources)), electrical_power_startup_time: Some(rolling_stock.common.electrical_power_startup_time), raise_pantograph_time: Some(rolling_stock.common.raise_pantograph_time), @@ -1008,12 +1008,7 @@ pub mod tests { let rolling_stock = named_fast_rolling_stock("fast_rolling_stock_get_power_restrictions_list", db_pool) .await; - let power_restrictions = rolling_stock - .model - .power_restrictions - .clone() - .unwrap() - .unwrap(); + let power_restrictions = rolling_stock.model.power_restrictions.clone().unwrap(); // WHEN let response = call_service( @@ -1025,8 +1020,12 @@ pub mod tests { .await; // THEN - assert!(power_restrictions.to_string().contains(&"C2".to_string())); + let power_restrictions = serde_json::to_string(&power_restrictions) + .expect("Failed to convert power_restrictions to string"); + assert!(power_restrictions.contains(&"C2".to_string())); + assert!(power_restrictions.contains(&"C5".to_string())); let response_body: Vec = read_body_json(response).await; assert!(response_body.contains(&"C2".to_string())); + assert!(response_body.contains(&"C5".to_string())); } }