Skip to content

Commit

Permalink
editoast: project work_schedules on path endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
anisometropie committed Sep 11, 2024
1 parent ef349bf commit 0382418
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 6 deletions.
5 changes: 4 additions & 1 deletion editoast/editoast_authz/src/builtin_role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub enum BuiltinRole {

#[strum(serialize = "work_schedule:write")]
WorkScheduleWrite,
#[strum(serialize = "work_schedule:read")]
WorkScheduleRead,

#[strum(serialize = "map:read")]
MapRead,
Expand Down Expand Up @@ -55,7 +57,8 @@ impl BuiltinRoleSet for BuiltinRole {
InfraWrite => vec![InfraRead],
RollingStockCollectionRead => vec![],
RollingStockCollectionWrite => vec![RollingStockCollectionRead],
WorkScheduleWrite => vec![],
WorkScheduleWrite => vec![WorkScheduleRead],
WorkScheduleRead => vec![],
MapRead => vec![],
Stdcm => vec![MapRead],
TimetableRead => vec![],
Expand Down
28 changes: 27 additions & 1 deletion editoast/src/models/fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ use editoast_schemas::primitives::OSRDObject;
use editoast_schemas::rolling_stock::RollingStock;
use editoast_schemas::train_schedule::TrainScheduleBase;
use postgis_diesel::types::LineString;
use serde_json::Value;

use crate::infra_cache::operation::create::apply_create_operation;
use crate::models::prelude::*;
use crate::models::rolling_stock_livery::RollingStockLiveryModel;
use crate::models::timetable::Timetable;
use crate::models::train_schedule::TrainSchedule;
use crate::models::work_schedules::WorkScheduleGroup;
use crate::models::work_schedules::{WorkSchedule, WorkScheduleGroup};
use crate::models::Document;
use crate::models::Infra;
use crate::models::Project;
Expand All @@ -29,6 +30,7 @@ use crate::models::Study;
use crate::models::Tags;
use crate::views::rolling_stock::form::RollingStockForm;
use crate::views::train_schedule::TrainScheduleForm;
use crate::views::work_schedules::WorkScheduleItemForm;
use crate::ElectricalProfileSet;

pub fn project_changeset(name: &str) -> Changeset<Project> {
Expand Down Expand Up @@ -308,3 +310,27 @@ pub async fn create_work_schedule_group(conn: &mut DbConnection) -> WorkSchedule
.await
.expect("Failed to create empty work schedule group")
}

pub struct WorkSchedulesFixtureSet {
pub work_schedule_group: WorkScheduleGroup,
pub work_schedules: Vec<WorkSchedule>,
}

pub async fn create_work_schedules_fixture_set(
conn: &mut DbConnection,
work_schedules: Vec<WorkScheduleItemForm>,
) -> WorkSchedulesFixtureSet {
let work_schedule_group = create_work_schedule_group(conn).await;
let work_schedules_changesets = work_schedules
.into_iter()
.map(|work_schedule| work_schedule.into_work_schedule_changeset(work_schedule_group.id))
.collect::<Vec<_>>();
let work_schedules = WorkSchedule::create_batch(conn, work_schedules_changesets)
.await
.expect("Failed to create work test schedules");

WorkSchedulesFixtureSet {
work_schedule_group,
work_schedules,
}
}
206 changes: 202 additions & 4 deletions editoast/src/views/work_schedules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,24 @@ use std::result::Result as StdResult;
use thiserror::Error;
use utoipa::ToSchema;

use crate::core::pathfinding::TrackRange as CoreTrackRange;
use crate::error::InternalError;
use crate::error::Result;
use crate::models::prelude::*;
use crate::models::work_schedules::WorkSchedule;
use crate::models::work_schedules::WorkScheduleGroup;
use crate::models::work_schedules::WorkScheduleType;
use crate::views::path::projection::PathProjection;
use crate::views::AuthorizationError;
use crate::views::AuthorizerExt;
use crate::AppState;
use editoast_schemas::infra::TrackRange;
use editoast_schemas::infra::{Direction, TrackRange};

crate::routes! {
"/work_schedules" => create,
"/work_schedules" => {
create,
"/project" => project,
},
}

editoast_common::schemas! {
Expand Down Expand Up @@ -56,7 +61,7 @@ pub fn map_diesel_error(e: InternalError, name: impl AsRef<str>) -> InternalErro
}

#[derive(Serialize, Derivative, ToSchema)]
struct WorkScheduleItemForm {
pub struct WorkScheduleItemForm {
pub start_date_time: NaiveDateTime,
pub end_date_time: NaiveDateTime,
pub track_ranges: Vec<TrackRange>,
Expand Down Expand Up @@ -175,15 +180,98 @@ async fn create(
}))
}

#[derive(Serialize, Deserialize, ToSchema)]
struct WorkScheduleProjectForm {
work_schedule_group_id: i64,
path_track_ranges: Vec<CoreTrackRange>,
}

#[derive(Serialize, Deserialize, ToSchema, PartialEq, Debug)]
struct WorkScheduleProjectResponse {
projections: Vec<WorkScheduleProjection>,
}

#[derive(Serialize, Deserialize, ToSchema, PartialEq, Debug)]
struct WorkScheduleProjection {
#[serde(rename = "type")]
pub work_schedule_type: WorkScheduleType,
pub start_date_time: NaiveDateTime,
pub end_date_time: NaiveDateTime,
pub path_position_ranges: Vec<(u64, u64)>,
}

#[utoipa::path(
post, path = "/project",
tag = "work_schedules",
request_body = ProjectWorkScheduleForm,
responses(
(status = 201, body = String, description = ""),
)
)]
async fn project(
State(app_state): State<AppState>,
Extension(authorizer): AuthorizerExt,
Json(WorkScheduleProjectForm {
work_schedule_group_id,
path_track_ranges,
}): Json<WorkScheduleProjectForm>,
) -> Result<Json<WorkScheduleProjectResponse>> {
let authorized = authorizer
.check_roles([BuiltinRole::WorkScheduleRead].into())
.await
.map_err(AuthorizationError::AuthError)?;
if !authorized {
return Err(AuthorizationError::Unauthorized.into());
}

// get all work_schedule of the group
let db_pool = app_state.db_pool_v2.clone();
let conn = &mut db_pool.get().await?;
let settings: SelectionSettings<WorkSchedule> = SelectionSettings::new()
.filter(move || WorkSchedule::WORK_SCHEDULE_GROUP_ID.eq(work_schedule_group_id));
let work_schedules = WorkSchedule::list(conn, settings).await?;

let projections = work_schedules
.into_iter()
.map(|ws| {
let ws_track_ranges: Vec<CoreTrackRange> = ws
.track_ranges
.into_iter()
.map(|tr| CoreTrackRange {
track_section: tr.track,
begin: (tr.begin * 1000.0) as u64,
end: (tr.end * 1000.0) as u64,
direction: Direction::StartToStop,
})
.collect();

let path_projection = PathProjection::new(&ws_track_ranges);
// project this work_schedule on the path
let path_position_ranges = path_projection.get_intersections(&path_track_ranges);
WorkScheduleProjection {
work_schedule_type: ws.work_schedule_type,
start_date_time: ws.start_date_time,
end_date_time: ws.end_date_time,
path_position_ranges,
}
})
.collect();
Ok(Json(WorkScheduleProjectResponse { projections }))
}

#[cfg(test)]
pub mod test {
use axum::http::StatusCode;
use chrono::NaiveDate;
use pretty_assertions::assert_eq;
use rstest::rstest;
use serde_json::json;

use super::*;
use crate::views::test_app::TestAppBuilder;
use crate::{
models::fixtures::{create_work_schedules_fixture_set, WorkSchedulesFixtureSet},
views::test_app::TestAppBuilder,
};

#[rstest]
async fn work_schedule_create() {
Expand Down Expand Up @@ -271,4 +359,114 @@ pub mod test {
"editoast:work_schedule:NameAlreadyUsed"
);
}

#[rstest]
#[case::one_work_schedule(
vec![
vec![
TrackRange::new("a", 0.0, 100.0),
TrackRange::new("b", 0.0, 50.0),
]
],
vec![
vec![(0, 150000)],
]
)]
#[case::two_work_schedules(
vec![
vec![
TrackRange::new("a", 0.0, 100.0),
TrackRange::new("c", 50.0, 100.0),
],
vec![TrackRange::new("d", 50.0, 100.0)],
],
vec![
vec![(0, 100000), (250000, 300000)],
vec![(350000, 400000)]
],
)]
async fn work_schedule_project_path_on_ws_group(
#[case] work_schedule_track_ranges: Vec<Vec<TrackRange>>,
#[case] expected_path_position_ranges: Vec<Vec<(u64, u64)>>,
) {
// GIVEN
let app = TestAppBuilder::default_app();
let pool = app.db_pool();
let conn = &mut pool.get_ok();

// create work schedules
let working_schedules_form = work_schedule_track_ranges
.into_iter()
.enumerate()
.map(|(index, track_ranges)| WorkScheduleItemForm {
start_date_time: NaiveDate::from_ymd_opt(2024, 1, (index + 1).try_into().unwrap())
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap(),
end_date_time: NaiveDate::from_ymd_opt(2024, 1, (index + 2).try_into().unwrap())
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap(),
track_ranges,
obj_id: format!("work_schedule_{}", index),
work_schedule_type: WorkScheduleType::Track,
})
.collect();

let WorkSchedulesFixtureSet {
work_schedule_group,
work_schedules,
} = create_work_schedules_fixture_set(conn, working_schedules_form).await;

// let’s have bunch of track ranges that represent a path
let request = app.post("/work_schedules/project").json(&json!({
"work_schedule_group_id": work_schedule_group.id,
"path_track_ranges": [
{
"track_section": "a",
"begin": 0,
"end": 100000,
"direction": "START_TO_STOP"
},
{
"track_section": "b",
"begin": 0,
"end": 100000,
"direction": "START_TO_STOP"
},
{
"track_section": "c",
"begin": 0,
"end": 100000,
"direction": "START_TO_STOP"
},
{
"track_section": "d",
"begin": 0,
"end": 100000,
"direction": "START_TO_STOP"
}
]
}));

// WHEN
let work_schedule_project_response = app
.fetch(request)
.assert_status(StatusCode::OK)
.json_into::<WorkScheduleProjectResponse>();

// THEN
let expected = WorkScheduleProjectResponse {
projections: expected_path_position_ranges
.into_iter()
.enumerate()
.map(|(index, position_ranges)| WorkScheduleProjection {
work_schedule_type: WorkScheduleType::Track,
start_date_time: work_schedules[index].start_date_time,
end_date_time: work_schedules[index].end_date_time,
path_position_ranges: position_ranges,
}).collect()
};
assert_eq!(work_schedule_project_response, expected);
}
}

0 comments on commit 0382418

Please sign in to comment.