From c9749a3541039f60ad80c9132ed85bc48dedeec3 Mon Sep 17 00:00:00 2001 From: Marlon Baeten Date: Tue, 24 Sep 2024 13:56:06 +0200 Subject: [PATCH] WIP ven tests --- openadr-vtn/src/api/ven.rs | 84 +++++++++++++++++-- openadr-vtn/src/data_source/mod.rs | 32 ++++++- .../src/data_source/postgres/resource.rs | 1 + openadr-vtn/src/data_source/postgres/ven.rs | 69 +++++++++------ openadr-vtn/src/jwt.rs | 1 + 5 files changed, 153 insertions(+), 34 deletions(-) diff --git a/openadr-vtn/src/api/ven.rs b/openadr-vtn/src/api/ven.rs index cf9d001..5b25b77 100644 --- a/openadr-vtn/src/api/ven.rs +++ b/openadr-vtn/src/api/ven.rs @@ -24,11 +24,11 @@ use crate::{ pub async fn get_all( State(ven_source): State>, ValidatedQuery(query_params): ValidatedQuery, - VenManagerUser(user): VenManagerUser, + User(user): User, ) -> AppResponse> { trace!(?query_params); - let vens = ven_source.retrieve_all(&query_params, &user).await?; + let vens = ven_source.retrieve_all(&query_params, &user.try_into()?).await?; Ok(Json(vens)) } @@ -46,7 +46,7 @@ pub async fn get( return Err(AppError::Forbidden("User is not a VEN or VEN Manager")); } - let ven = ven_source.retrieve(&id, &user).await?; + let ven = ven_source.retrieve(&id, &user.try_into()?).await?; Ok(Json(ven)) } @@ -56,7 +56,7 @@ pub async fn add( VenManagerUser(user): VenManagerUser, ValidatedJson(new_ven): ValidatedJson, ) -> Result<(StatusCode, Json), AppError> { - let ven = ven_source.create(new_ven, &user).await?; + let ven = ven_source.create(new_ven, &user.try_into()?).await?; Ok((StatusCode::CREATED, Json(ven))) } @@ -67,7 +67,7 @@ pub async fn edit( VenManagerUser(user): VenManagerUser, ValidatedJson(content): ValidatedJson, ) -> AppResponse { - let ven = ven_source.update(&id, content, &user).await?; + let ven = ven_source.update(&id, content, &user.try_into()?).await?; info!(%ven.id, ven.ven_name=ven.content.ven_name, "ven updated"); @@ -79,7 +79,7 @@ pub async fn delete( Path(id): Path, VenManagerUser(user): VenManagerUser, ) -> AppResponse { - let ven = ven_source.delete(&id, &user).await?; + let ven = ven_source.delete(&id, &user.try_into()?).await?; info!(%id, "deleted ven"); Ok(Json(ven)) } @@ -109,3 +109,75 @@ fn validate_target_type_value_pair(query: &QueryParams) -> Result<(), Validation fn get_50() -> i64 { 50 } + +#[cfg(test)] +mod tests { + use axum::{body::Body, http::{self, Request, Response}, Router}; + use http_body_util::BodyExt; + use openadr_wire::Ven; + use serde::de::DeserializeOwned; + use sqlx::PgPool; + use tower::ServiceExt; + + use crate::{api::test::jwt_test_token, data_source::PostgresStorage, jwt::{AuthRole, JwtManager}, state::AppState}; + + async fn request_all( + app: Router, + token: &str, + ) -> Response { + app.oneshot( + Request::builder() + .method(http::Method::GET) + .uri("/vens") + .header(http::header::AUTHORIZATION, format!("Bearer {}", token)) + .header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) + .body(Body::empty()) + .unwrap() + ).await.unwrap() + } + + async fn get_response_json(response: Response) -> T { + let body = response.into_body().collect().await.unwrap().to_bytes(); + + serde_json::from_slice(&body).unwrap() + } + + fn test_state(db: PgPool) -> AppState { + let store = PostgresStorage::new(db).unwrap(); + let jwt_manager = JwtManager::from_base64_secret("test").unwrap(); + + AppState::new(store, jwt_manager) + } + + #[sqlx::test(fixtures("users", "vens"))] + async fn get_all_unfiletred(db: PgPool) { + let state = test_state(db); + let token = jwt_test_token(&state, vec![AuthRole::VenManager]); + let app = state.into_router(); + + let resp = request_all(app, &token).await; + + assert_eq!(resp.status(), http::StatusCode::OK); + let vens: Vec = get_response_json(resp).await; + + assert_eq!(vens.len(), 2); + assert_eq!(vens[0].id.as_str(), "ven-2"); + assert_eq!(vens[1].id.as_str(), "ven-1"); + } + + + #[sqlx::test(fixtures("users", "vens"))] + async fn get_all_ven_user(db: PgPool) { + let state = test_state(db); + let token = jwt_test_token(&state, vec![AuthRole::VEN("ven-1".parse().unwrap())]); + let app = state.into_router(); + + let resp = request_all(app, &token).await; + + assert_eq!(resp.status(), http::StatusCode::OK); + let vens: Vec = get_response_json(resp).await; + + assert_eq!(vens.len(), 1); + assert_eq!(vens[0].id.as_str(), "ven-1"); + } +} \ No newline at end of file diff --git a/openadr-vtn/src/data_source/mod.rs b/openadr-vtn/src/data_source/mod.rs index 1c2a971..7c6ec04 100644 --- a/openadr-vtn/src/data_source/mod.rs +++ b/openadr-vtn/src/data_source/mod.rs @@ -133,6 +133,36 @@ pub trait EventCrud: { } + +pub enum VenPermissions { + AllAllowed, + Specific(Vec), +} + +impl VenPermissions { + pub fn as_value(&self) -> Option> { + match self { + VenPermissions::AllAllowed => None, + VenPermissions::Specific(ids) => Some(ids.iter().map(|id| id.to_string()).collect::>()), + } + } +} + + +impl TryFrom for VenPermissions { + type Error = AppError; + + fn try_from(claims: Claims) -> Result { + if claims.is_ven_manager() { + Ok(VenPermissions::AllAllowed) + } else if claims.is_ven() { + Ok(VenPermissions::Specific(claims.ven_ids())) + } else { + Err(AppError::Forbidden("User not authorized to access this vens")) + } + } +} + pub trait VenCrud: Crud< Type = Ven, @@ -140,7 +170,7 @@ pub trait VenCrud: NewType = VenContent, Error = AppError, Filter = crate::api::ven::QueryParams, - PermissionFilter = Claims, + PermissionFilter = VenPermissions, > { } diff --git a/openadr-vtn/src/data_source/postgres/resource.rs b/openadr-vtn/src/data_source/postgres/resource.rs index 2c08900..015382e 100644 --- a/openadr-vtn/src/data_source/postgres/resource.rs +++ b/openadr-vtn/src/data_source/postgres/resource.rs @@ -341,3 +341,4 @@ impl PgResourceStorage { .collect::>() } } + diff --git a/openadr-vtn/src/data_source/postgres/ven.rs b/openadr-vtn/src/data_source/postgres/ven.rs index 77eddc2..824fd00 100644 --- a/openadr-vtn/src/data_source/postgres/ven.rs +++ b/openadr-vtn/src/data_source/postgres/ven.rs @@ -2,10 +2,9 @@ use crate::{ api::ven::QueryParams, data_source::{ postgres::{to_json_value, PgTargetsFilter}, - Crud, VenCrud, + Crud, VenCrud, VenPermissions, }, error::AppError, - jwt::Claims, }; use axum::async_trait; use chrono::{DateTime, Utc}; @@ -127,7 +126,7 @@ impl Crud for PgVenStorage { type NewType = VenContent; type Error = AppError; type Filter = QueryParams; - type PermissionFilter = Claims; + type PermissionFilter = VenPermissions; async fn create( &self, @@ -162,16 +161,20 @@ impl Crud for PgVenStorage { async fn retrieve( &self, id: &Self::Id, - _user: &Self::PermissionFilter, + permissions: &Self::PermissionFilter, ) -> Result { + let ids = permissions.as_value(); + let mut ven: Ven = sqlx::query_as!( PostgresVen, r#" SELECT * FROM ven WHERE id = $1 + AND ($2::text[] IS NULL OR id = ANY($2)) "#, id.as_str(), + ids.as_deref(), ) .fetch_one(&self.db) .await? @@ -185,11 +188,13 @@ impl Crud for PgVenStorage { async fn retrieve_all( &self, filter: &Self::Filter, - _user: &Self::PermissionFilter, + permissions: &Self::PermissionFilter, ) -> Result, Self::Error> { let pg_filter: PostgresFilter = filter.into(); trace!(?pg_filter); + let ids = permissions.as_value(); + let mut vens: Vec = sqlx::query_as!( PostgresVen, r#" @@ -205,12 +210,15 @@ impl Crud for PgVenStorage { WHERE ($1::text[] IS NULL OR v.ven_name = ANY($1)) AND ($2::text[] IS NULL OR r.resource_name = ANY($2)) AND ($3::jsonb = '[]'::jsonb OR $3::jsonb <@ v.targets) - OFFSET $4 LIMIT $5 + AND ($4::text[] IS NULL OR v.id = ANY($4)) + ORDER BY v.created_date_time DESC + OFFSET $5 LIMIT $6 "#, pg_filter.ven_names, pg_filter.resource_names, serde_json::to_value(pg_filter.targets) .map_err(AppError::SerdeJsonInternalServerError)?, + ids.as_deref(), pg_filter.skip, pg_filter.limit, ) @@ -310,7 +318,6 @@ mod tests { api::ven::QueryParams, data_source::{postgres::ven::PgVenStorage, Crud}, error::AppError, - jwt::Claims, }; use openadr_wire::{ values_map::{Value, ValueType, ValuesMap}, @@ -369,7 +376,7 @@ mod tests { } mod get_all { - use crate::data_source::postgres::ven::PgVenStorage; + use crate::data_source::postgres::ven::{PgVenStorage, VenPermissions}; use super::*; use openadr_wire::target::TargetLabel; @@ -378,7 +385,7 @@ mod tests { async fn default_get_all(db: PgPool) { let repo: PgVenStorage = db.into(); let mut vens = repo - .retrieve_all(&Default::default(), &Claims::ven_manager()) + .retrieve_all(&Default::default(), &VenPermissions::AllAllowed) .await .unwrap(); assert_eq!(vens.len(), 2); @@ -395,7 +402,7 @@ mod tests { limit: 1, ..Default::default() }, - &Claims::ven_manager(), + &VenPermissions::AllAllowed, ) .await .unwrap(); @@ -411,7 +418,7 @@ mod tests { skip: 1, ..Default::default() }, - &Claims::ven_manager(), + &VenPermissions::AllAllowed, ) .await .unwrap(); @@ -423,7 +430,7 @@ mod tests { skip: 2, ..Default::default() }, - &Claims::ven_manager(), + &VenPermissions::AllAllowed, ) .await .unwrap(); @@ -441,7 +448,7 @@ mod tests { target_values: Some(vec!["group-1".to_string()]), ..Default::default() }, - &Claims::ven_manager(), + &VenPermissions::AllAllowed, ) .await .unwrap(); @@ -454,7 +461,7 @@ mod tests { target_values: Some(vec!["not-existent".to_string()]), ..Default::default() }, - &Claims::ven_manager(), + &VenPermissions::AllAllowed, ) .await .unwrap(); @@ -467,7 +474,7 @@ mod tests { target_values: Some(vec!["ven-2-name".to_string()]), ..Default::default() }, - &Claims::ven_manager(), + &VenPermissions::AllAllowed, ) .await .unwrap(); @@ -481,7 +488,7 @@ mod tests { target_values: Some(vec!["ven-not-existent".to_string()]), ..Default::default() }, - &Claims::ven_manager(), + &VenPermissions::AllAllowed, ) .await .unwrap(); @@ -490,6 +497,8 @@ mod tests { } mod get { + use crate::data_source::postgres::ven::VenPermissions; + use super::*; #[sqlx::test(fixtures("users", "vens"))] @@ -497,7 +506,7 @@ mod tests { let repo: PgVenStorage = db.into(); let ven = repo - .retrieve(&"ven-1".parse().unwrap(), &Claims::ven_manager()) + .retrieve(&"ven-1".parse().unwrap(), &VenPermissions::AllAllowed) .await .unwrap(); assert_eq!(ven, ven_1()); @@ -507,7 +516,7 @@ mod tests { async fn get_not_existent(db: PgPool) { let repo: PgVenStorage = db.into(); let ven = repo - .retrieve(&"ven-not-existent".parse().unwrap(), &Claims::ven_manager()) + .retrieve(&"ven-not-existent".parse().unwrap(), &VenPermissions::AllAllowed) .await; assert!(matches!(ven, Err(AppError::NotFound))); @@ -515,6 +524,8 @@ mod tests { } mod add { + use crate::data_source::postgres::ven::VenPermissions; + use super::*; use chrono::{Duration, Utc}; @@ -523,7 +534,7 @@ mod tests { let repo: PgVenStorage = db.into(); let ven = repo - .create(ven_1().content, &Claims::ven_manager()) + .create(ven_1().content, &VenPermissions::AllAllowed) .await .unwrap(); assert!(ven.created_date_time < Utc::now() + Duration::minutes(10)); @@ -536,12 +547,14 @@ mod tests { async fn add_existing_name(db: PgPool) { let repo: PgVenStorage = db.into(); - let ven = repo.create(ven_1().content, &Claims::ven_manager()).await; + let ven = repo.create(ven_1().content, &VenPermissions::AllAllowed).await; assert!(matches!(ven, Err(AppError::Conflict(_, _)))); } } mod modify { + use crate::data_source::postgres::ven::VenPermissions; + use super::*; use chrono::{DateTime, Duration, Utc}; @@ -552,7 +565,7 @@ mod tests { .update( &"ven-1".parse().unwrap(), ven_1().content, - &Claims::ven_manager(), + &VenPermissions::AllAllowed, ) .await .unwrap(); @@ -578,14 +591,14 @@ mod tests { .update( &"ven-1".parse().unwrap(), updated.clone(), - &Claims::ven_manager(), + &VenPermissions::AllAllowed, ) .await .unwrap(); assert_eq!(ven.content, updated); let ven = repo - .retrieve(&"ven-1".parse().unwrap(), &Claims::ven_manager()) + .retrieve(&"ven-1".parse().unwrap(), &VenPermissions::AllAllowed) .await .unwrap(); assert_eq!(ven.content, updated); @@ -593,24 +606,26 @@ mod tests { } mod delete { + use crate::data_source::postgres::ven::VenPermissions; + use super::*; #[sqlx::test(fixtures("users", "vens"))] async fn delete_existing(db: PgPool) { let repo: PgVenStorage = db.into(); let ven = repo - .delete(&"ven-1".parse().unwrap(), &Claims::ven_manager()) + .delete(&"ven-1".parse().unwrap(), &VenPermissions::AllAllowed) .await .unwrap(); assert_eq!(ven, ven_1()); let ven = repo - .retrieve(&"ven-1".parse().unwrap(), &Claims::ven_manager()) + .retrieve(&"ven-1".parse().unwrap(), &VenPermissions::AllAllowed) .await; assert!(matches!(ven, Err(AppError::NotFound))); let ven = repo - .retrieve(&"ven-2".parse().unwrap(), &Claims::ven_manager()) + .retrieve(&"ven-2".parse().unwrap(), &VenPermissions::AllAllowed) .await .unwrap(); assert_eq!(ven, ven_2()); @@ -620,7 +635,7 @@ mod tests { async fn delete_not_existing(db: PgPool) { let repo: PgVenStorage = db.into(); let ven = repo - .delete(&"ven-not-existing".parse().unwrap(), &Claims::ven_manager()) + .delete(&"ven-not-existing".parse().unwrap(), &VenPermissions::AllAllowed) .await; assert!(matches!(ven, Err(AppError::NotFound))); } diff --git a/openadr-vtn/src/jwt.rs b/openadr-vtn/src/jwt.rs index 5ca2ec3..b42a3a4 100644 --- a/openadr-vtn/src/jwt.rs +++ b/openadr-vtn/src/jwt.rs @@ -209,6 +209,7 @@ pub struct UserManagerUser(pub Claims); /// User claims extracted from the request, with the requirement that the user is a VEN manager pub struct VenManagerUser(pub Claims); + #[async_trait] impl FromRequestParts for User where