diff --git a/src/handler.rs b/src/handler.rs index 2062e6fa..b6b5909c 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -15,6 +15,7 @@ pub(crate) mod session_store; pub(crate) mod proxy; use crate::{clients::Auth0Client, routes, routes::axum_routes, Config, Crypter, Db, FeatureFlags}; +use trillium_client::Client; use axum::extract::{DefaultBodyLimit, FromRef}; use axum::http::{header, HeaderValue}; @@ -66,6 +67,7 @@ pub struct AxumAppState { pub(crate) oauth_client: OauthClient, pub(crate) crypter: Crypter, pub(crate) feature_flags: FeatureFlags, + pub(crate) client: Client, } impl FromRef for Db { @@ -104,6 +106,12 @@ impl FromRef for FeatureFlags { } } +impl FromRef for Client { + fn from_ref(state: &AxumAppState) -> Self { + state.client.clone() + } +} + #[derive(Handler, Debug)] pub struct DivviupApi { #[handler(except = init)] @@ -137,6 +145,7 @@ impl DivviupApi { oauth_client: OauthClient::new(&config.oauth_config()), crypter: config.crypter.clone(), feature_flags: config.feature_flags(), + client: config.client.clone(), }; // Middleware stack in logical order (outermost first), matching the // Trillium api() handler chain. diff --git a/src/routes.rs b/src/routes.rs index f8be1aec..2995220c 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -10,7 +10,7 @@ mod users; use crate::{ clients::Auth0Client, - handler::{actor_required, admin_required, ReplaceMimeTypes}, + handler::{actor_required, ReplaceMimeTypes}, }; pub use health_check::health_check; use trillium::{ @@ -38,37 +38,15 @@ fn api_routes() -> impl Handler { ( ReplaceMimeTypes, api(actor_required), - router() - .get("/tasks/:task_id", api(tasks::show)) - .patch("/tasks/:task_id", api(tasks::update)) - .delete("/tasks/:task_id", api(tasks::delete)) - .patch("/aggregators/:aggregator_id", api(aggregators::update)) - .get("/aggregators/:aggregator_id", api(aggregators::show)) - .delete("/aggregators/:aggregator_id", api(aggregators::delete)) - .get("/aggregators", api(aggregators::index)) - .post( - "/aggregators", - (api(admin_required), api(aggregators::admin_create)), - ) - .any( - &[Patch, Get, Post], - "/accounts/:account_id/*", - accounts_routes(), - ) - .all("/admin/*", admin::routes()), + router().all("/admin/*", admin::routes()), ) } -fn accounts_routes() -> impl Handler { - router() - .get("/tasks", api(tasks::index)) - .post("/tasks", api(tasks::create)) - .post("/aggregators", api(aggregators::create)) - .get("/aggregators", api(aggregators::index)) -} - pub(crate) mod axum_routes { - use super::{accounts, api_tokens, collector_credentials, memberships, users}; + use super::{ + accounts, aggregators::axum_handler as aggregators, api_tokens, collector_credentials, + memberships, tasks::axum_handler as tasks, users, + }; use crate::handler::AxumAppState; use axum::routing::{delete, get}; @@ -96,6 +74,20 @@ pub(crate) mod axum_routes { .get(collector_credentials::show) .patch(collector_credentials::update), ) + .route( + "/aggregators", + get(aggregators::index_shared).post(aggregators::admin_create), + ) + .route( + "/aggregators/{aggregator_id}", + get(aggregators::show) + .patch(aggregators::update) + .delete(aggregators::delete), + ) + .route( + "/tasks/{task_id}", + get(tasks::show).patch(tasks::update).delete(tasks::delete), + ) .nest( "/accounts/{account_id}", axum::Router::new() @@ -111,6 +103,11 @@ pub(crate) mod axum_routes { .route( "/collector_credentials", get(collector_credentials::index).post(collector_credentials::create), + ) + .route("/tasks", get(tasks::index).post(tasks::create)) + .route( + "/aggregators", + get(aggregators::index_for_account).post(aggregators::create), ), ) } diff --git a/src/routes/accounts.rs b/src/routes/accounts.rs index de793b1d..a1eb675d 100644 --- a/src/routes/accounts.rs +++ b/src/routes/accounts.rs @@ -8,6 +8,7 @@ use axum::{ http::{header, request::Parts, StatusCode}, response::IntoResponse, }; +use httpdate::fmt_http_date; use sea_orm::{ActiveModelTrait, EntityTrait, TransactionTrait}; use trillium::Conn; use trillium_api::FromConn; @@ -49,7 +50,7 @@ where } pub async fn show(account: Account) -> impl IntoResponse { - let last_modified = httpdate::fmt_http_date(account.updated_at.into()); + let last_modified = fmt_http_date(account.updated_at.into()); ([(header::LAST_MODIFIED, last_modified)], Json(account)) } diff --git a/src/routes/aggregators.rs b/src/routes/aggregators.rs index c1d58cf2..899d7160 100644 --- a/src/routes/aggregators.rs +++ b/src/routes/aggregators.rs @@ -1,18 +1,19 @@ use crate::{ config::FeatureFlags, entity::{Account, Aggregator, AggregatorColumn, Aggregators, NewAggregator, UpdateAggregator}, - handler::extract::extract_entity, - Db, Error, Permissions, PermissionsActor, + handler::extract::{extract_entity, Json}, + Crypter, Db, Error, Permissions, PermissionsActor, }; -use axum::extract::{FromRef, FromRequestParts}; -use axum::http::request::Parts; +use axum::extract::{FromRef, FromRequestParts, State}; +use axum::http::{request::Parts, StatusCode}; +use axum::response::IntoResponse; use sea_orm::{ sea_query::{all, any}, ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, }; -use trillium::{Conn, Handler, Status}; -use trillium_api::{FromConn, Json, State}; - +use trillium::Conn; +use trillium_api::FromConn; +use trillium_client::Client; use trillium_router::RouterConnExt; use uuid::Uuid; @@ -63,96 +64,110 @@ impl Permissions for Aggregator { } } -pub async fn show(_: &mut Conn, aggregator: Aggregator) -> Json { - Json(aggregator) -} +pub mod axum_handler { + use super::*; -pub async fn index( - conn: &mut Conn, - (db, account): (Db, Option), -) -> Result>, Error> { - if conn.param("account_id").is_some() && account.is_none() { - return Err(Error::NotFound); + pub async fn show(aggregator: Aggregator) -> Json { + Json(aggregator) } - Aggregators::find() - .filter(all![ - match account { - Some(account) => any![ - AggregatorColumn::AccountId.eq(account.id), - AggregatorColumn::AccountId.is_null() - ], - None => any![AggregatorColumn::AccountId.is_null()], - }, - AggregatorColumn::DeletedAt.is_null() - ]) - .all(&db) - .await - .map(Json) - .map_err(Error::from) -} + pub async fn index_shared( + _actor: PermissionsActor, + State(db): State, + ) -> Result>, Error> { + Ok(Json( + Aggregators::find() + .filter(all![ + AggregatorColumn::AccountId.is_null(), + AggregatorColumn::DeletedAt.is_null() + ]) + .all(&db) + .await?, + )) + } -pub async fn create( - conn: &mut Conn, - (db, account, Json(new_aggregator), State(feature_flags)): ( - Db, - Account, - Json, - State, - ), -) -> Result { - let client = conn.take_state().unwrap(); - let crypter = conn.take_state().unwrap(); + pub async fn index_for_account( + account: Account, + State(db): State, + ) -> Result>, Error> { + Ok(Json( + Aggregators::find() + .filter(all![ + any![ + AggregatorColumn::AccountId.eq(account.id), + AggregatorColumn::AccountId.is_null() + ], + AggregatorColumn::DeletedAt.is_null() + ]) + .all(&db) + .await?, + )) + } - new_aggregator - .build( - Some(&account), - client, - &crypter, - feature_flags.ssrf_validation_enabled, - ) - .await? - .insert(&db) - .await - .map_err(Error::from) - .map(|agg| (Json(agg), Status::Created)) -} + pub async fn create( + account: Account, + State(db): State, + State(client): State, + State(crypter): State, + State(feature_flags): State, + Json(new_aggregator): Json, + ) -> Result { + let aggregator = new_aggregator + .build( + Some(&account), + client, + &crypter, + feature_flags.ssrf_validation_enabled, + ) + .await? + .insert(&db) + .await?; + Ok((StatusCode::CREATED, Json(aggregator))) + } -pub async fn delete(_: &mut Conn, (db, aggregator): (Db, Aggregator)) -> Result { - aggregator.tombstone().update(&db).await?; - Ok(Status::NoContent) -} + pub async fn update( + aggregator: Aggregator, + State(db): State, + State(client): State, + State(crypter): State, + Json(update_aggregator): Json, + ) -> Result, Error> { + Ok(Json( + update_aggregator + .build(aggregator, client, &crypter) + .await? + .update(&db) + .await?, + )) + } -pub async fn update( - conn: &mut Conn, - (db, aggregator, Json(update_aggregator)): (Db, Aggregator, Json), -) -> Result, Error> { - let client = conn.take_state().unwrap(); - let crypter = conn.state().unwrap(); - update_aggregator - .build(aggregator, client, crypter) - .await? - .update(&db) - .await - .map_err(Error::from) - .map(Json) -} + pub async fn delete(aggregator: Aggregator, State(db): State) -> Result { + aggregator.tombstone().update(&db).await?; + Ok(StatusCode::NO_CONTENT) + } -pub async fn admin_create( - conn: &mut Conn, - (db, Json(new_aggregator), State(feature_flags)): ( - Db, - Json, - State, - ), -) -> Result { - let client = conn.take_state().unwrap(); - let crypter = conn.state().unwrap(); - new_aggregator - .build(None, client, crypter, feature_flags.ssrf_validation_enabled) - .await? - .insert(&db) - .await - .map_err(Error::from) - .map(|agg| (Json(agg), Status::Created)) + pub async fn admin_create( + actor: PermissionsActor, + State(db): State, + State(client): State, + State(crypter): State, + State(feature_flags): State, + Json(new_aggregator): Json, + ) -> Result { + if !actor.is_admin() { + return Err(Error::NotFound); + } + + let aggregator = new_aggregator + .build( + None, + client, + &crypter, + feature_flags.ssrf_validation_enabled, + ) + .await? + .insert(&db) + .await?; + Ok((StatusCode::CREATED, Json(aggregator))) + } } diff --git a/src/routes/collector_credentials.rs b/src/routes/collector_credentials.rs index ebf15a78..d4c24fcb 100644 --- a/src/routes/collector_credentials.rs +++ b/src/routes/collector_credentials.rs @@ -11,6 +11,7 @@ use axum::{ http::{header, request::Parts, StatusCode}, response::IntoResponse, }; +use httpdate::fmt_http_date; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, ModelTrait, QueryFilter}; use trillium::Conn; use trillium_api::FromConn; @@ -68,7 +69,7 @@ pub async fn index( } pub async fn show(collector_credential: CollectorCredential) -> impl IntoResponse { - let last_modified = httpdate::fmt_http_date(collector_credential.updated_at.into()); + let last_modified = fmt_http_date(collector_credential.updated_at.into()); ( [(header::LAST_MODIFIED, last_modified)], Json(collector_credential), diff --git a/src/routes/tasks.rs b/src/routes/tasks.rs index 0bc7d1ae..b8262036 100644 --- a/src/routes/tasks.rs +++ b/src/routes/tasks.rs @@ -2,36 +2,28 @@ use crate::{ clients::aggregator_client::{api_types::TaskAggregationJobMetrics, TaskUploadMetrics}, config::FeatureFlags, entity::{Account, NewTask, Task, TaskColumn, Tasks, UpdateTask}, - handler::extract::extract_entity, + handler::extract::Json, Crypter, Db, Error, Permissions, PermissionsActor, }; -use axum::extract::{FromRef, FromRequestParts}; -use axum::http::request::Parts; -use querystrong::QueryStrong; +use axum::extract::{FromRef, FromRequestParts, Path, Query, State}; +use axum::http::{header, request::Parts, StatusCode}; +use axum::response::IntoResponse; +use httpdate::fmt_http_date; use sea_orm::{ ActiveModelTrait, ActiveValue, ColumnTrait, EntityTrait, IntoActiveModel, ModelTrait, QueryFilter, }; +use serde::Deserialize; +use std::collections::HashMap; use std::time::Duration; use time::OffsetDateTime; use tokio::join; use tracing::warn; -use trillium::{Conn, Handler, Status}; -use trillium_api::{FromConn, Json, State}; -use trillium_caching_headers::CachingHeadersExt; +use trillium::Conn; +use trillium_api::FromConn; use trillium_client::Client; use trillium_router::RouterConnExt; -pub async fn index(_: &mut Conn, (account, db): (Account, Db)) -> Result { - account - .find_related(Tasks) - .filter(TaskColumn::DeletedAt.is_null()) - .all(&db) - .await - .map(Json) - .map_err(Error::from) -} - impl Permissions for Task { fn allow_write(&self, actor: &PermissionsActor) -> bool { actor.is_admin() || actor.account_ids().contains(&self.account_id) @@ -63,24 +55,24 @@ where { type Rejection = Error; async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { - extract_entity::(parts, state, "task_id").await - } -} + let Path(params) = Path::>::from_request_parts(parts, state) + .await + .map_err(|_| Error::NotFound)?; -type CreateArgs = (Account, Json, State, Db); -pub async fn create( - conn: &mut Conn, - (account, mut task, State(client), db): CreateArgs, -) -> Result { - let crypter = conn.state().unwrap(); - task.normalize_and_validate(account, &db) - .await? - .provision(client, crypter) - .await? - .insert(&db) - .await - .map_err(Into::into) - .map(|task| (Status::Created, Json(task))) + let id = params.get("task_id").ok_or(Error::NotFound)?; + + let actor = PermissionsActor::from_request_parts(parts, state).await?; + let db = Db::from_ref(state); + + let task = Tasks::find_by_id(id) + .one(&db) + .await? + .ok_or(Error::AccessDenied)?; + + actor + .if_allowed_http(&parts.method, task) + .ok_or(Error::AccessDenied) + } } async fn refresh_metrics_if_needed( @@ -116,88 +108,122 @@ async fn refresh_metrics_if_needed( .map_err(Into::into) } -pub async fn show( - conn: &mut Conn, - (task, db, State(client), State(feature_flags)): (Task, Db, State, State), -) -> Result, Error> { - let task = if feature_flags.metrics_refresh_enabled { - let crypter = conn.state().unwrap(); - refresh_metrics_if_needed(task, db, client, crypter).await? - } else { - task - }; - conn.set_last_modified(task.updated_at.into()); - Ok(Json(task)) -} - -type UpdateArgs = (Task, Json, State, Db); -pub async fn update( - conn: &mut Conn, - (task, Json(update), client, db): UpdateArgs, -) -> Result { - let crypter = conn.state().unwrap(); - update - .update(&client, &db, crypter, task) - .await? - .update(&db) - .await - .map(Json) - .map_err(Error::from) -} +pub mod axum_handler { + use super::*; + + pub async fn index(account: Account, State(db): State) -> Result>, Error> { + Ok(Json( + account + .find_related(Tasks) + .filter(TaskColumn::DeletedAt.is_null()) + .all(&db) + .await?, + )) + } -pub async fn delete( - conn: &mut Conn, - (task, client, db): (Task, State, Db), -) -> Result { - let params = QueryStrong::parse(conn.querystring()); - let force = params - .get_str("force") - .and_then(|param| param.parse().ok()) - .unwrap_or(false); - - if task.deleted_at.is_some() { - return Ok(Status::NoContent); + pub async fn create( + account: Account, + State(db): State, + State(client): State, + State(crypter): State, + Json(mut new_task): Json, + ) -> Result { + let task = new_task + .normalize_and_validate(account, &db) + .await? + .provision(client, &crypter) + .await? + .insert(&db) + .await?; + Ok((StatusCode::CREATED, Json(task))) } - let crypter = conn.state().unwrap(); - let now = OffsetDateTime::now_utc(); - - let mut am = task.clone().into_active_model(); - // If the task has not already expired, mark the aggregator-side tasks as expired. This will - // allow the aggregators to cease processing and eventually GC the task at their leisure. - if task.expiration.is_none() || task.expiration > Some(now) { - let update = UpdateTask::expiration(Some(now)); - - let (leader_result, helper_result) = join!( - update.update_aggregator_expiration( - task.leader_aggregator(&db).await?, - &task.id, - &client, - crypter, - ), - update.update_aggregator_expiration( - task.helper_aggregator(&db).await?, - &task.id, - &client, - crypter, - ) - ); - - if force { - let _ = leader_result - .map_err(|err| warn!(?err, "failed to expire leader-side task, ignoring")); - let _ = helper_result - .map_err(|err| warn!(?err, "failed to expire helper-side task, ignoring")); + pub async fn show( + task: Task, + State(db): State, + State(client): State, + State(crypter): State, + State(feature_flags): State, + ) -> Result { + let task = if feature_flags.metrics_refresh_enabled { + refresh_metrics_if_needed(task, db, client, &crypter).await? } else { - leader_result?; - helper_result?; - } + task + }; + let last_modified = fmt_http_date(task.updated_at.into()); + Ok(([(header::LAST_MODIFIED, last_modified)], Json(task))) + } + + pub async fn update( + task: Task, + State(db): State, + State(client): State, + State(crypter): State, + Json(update): Json, + ) -> Result, Error> { + Ok(Json( + update + .update(&client, &db, &crypter, task) + .await? + .update(&db) + .await?, + )) + } - am.expiration = ActiveValue::Set(Some(now)); + #[derive(Deserialize)] + pub struct DeleteParams { + #[serde(default)] + force: bool, } - am.updated_at = ActiveValue::Set(now); - am.deleted_at = ActiveValue::Set(Some(now)); - am.update(&db).await?; - Ok(Status::NoContent) + pub async fn delete( + task: Task, + State(db): State, + State(client): State, + State(crypter): State, + Query(params): Query, + ) -> Result { + if task.deleted_at.is_some() { + return Ok(StatusCode::NO_CONTENT); + } + + let now = OffsetDateTime::now_utc(); + + let mut am = task.clone().into_active_model(); + if task.expiration.is_none() || task.expiration > Some(now) { + let update = UpdateTask::expiration(Some(now)); + + let (leader_result, helper_result) = join!( + update.update_aggregator_expiration( + task.leader_aggregator(&db).await?, + &task.id, + &client, + &crypter, + ), + update.update_aggregator_expiration( + task.helper_aggregator(&db).await?, + &task.id, + &client, + &crypter, + ) + ); + + if params.force { + let _ = leader_result + .map_err(|err| warn!(?err, "failed to expire leader-side task, ignoring")); + let _ = helper_result + .map_err(|err| warn!(?err, "failed to expire helper-side task, ignoring")); + } else { + leader_result?; + helper_result?; + } + + am.expiration = ActiveValue::Set(Some(now)); + } + + am.updated_at = ActiveValue::Set(now); + am.deleted_at = ActiveValue::Set(Some(now)); + am.update(&db).await?; + Ok(StatusCode::NO_CONTENT) + } } diff --git a/tests/integration/aggregators.rs b/tests/integration/aggregators.rs index 739c0c6d..7a5a1fdb 100644 --- a/tests/integration/aggregators.rs +++ b/tests/integration/aggregators.rs @@ -83,13 +83,13 @@ mod index { fixtures::aggregator(&app, Some(&account)).await; fixtures::aggregator(&app, Some(&account)).await; - let mut conn = get(format!("/api/accounts/{}/aggregators", account.id)) + let conn = get(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); Ok(()) } @@ -204,13 +204,13 @@ mod index { fixtures::aggregator(&app, Some(&account)).await; fixtures::aggregator(&app, Some(&account)).await; - let mut conn = get(format!("/api/accounts/{}/aggregators", account.id)) + let conn = get(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() .with_request_header(KnownHeaderName::Authorization, token) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); Ok(()) } @@ -370,14 +370,14 @@ mod create { let account = fixtures::account(&app).await; // no membership let aggregator_count_before = Aggregators::find().count(app.db()).await?; - let mut conn = post(format!("/api/accounts/{}/aggregators", account.id)) + let conn = post(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() .with_state(user) .with_request_json(fixtures::new_aggregator()) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); let aggregator_count_after = Aggregators::find().count(app.db()).await?; assert_eq!(aggregator_count_before, aggregator_count_after); @@ -472,14 +472,14 @@ mod create { let account = fixtures::account(&app).await; let aggregator_count_before = Aggregators::find().count(app.db()).await?; - let mut conn = post(format!("/api/accounts/{}/aggregators", account.id)) + let conn = post(format!("/api/accounts/{}/aggregators", account.id)) .with_api_headers() .with_auth_header(token) .with_request_json(fixtures::new_aggregator()) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); let aggregator_count_after = Aggregators::find().count(app.db()).await?; assert_eq!(aggregator_count_before, aggregator_count_after); @@ -549,12 +549,12 @@ mod show { let user = fixtures::user(); let account = fixtures::account(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let mut conn = get(format!("/api/aggregators/{}", aggregator.id)) + let conn = get(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); Ok(()) } @@ -703,12 +703,12 @@ mod show { let (_, token) = fixtures::api_token(&app, &other_account).await; let account = fixtures::account(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let mut conn = get(format!("/api/aggregators/{}", aggregator.id)) + let conn = get(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); Ok(()) } } @@ -822,14 +822,14 @@ mod update { let account = fixtures::account(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let mut conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let conn = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_request_json(json!({ "name": "irrelevant" })) .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); assert_eq!( aggregator.reload(app.db()).await?.unwrap().name, aggregator.name // unchanged @@ -842,14 +842,14 @@ mod update { let user = fixtures::user(); let aggregator = fixtures::aggregator(&app, None).await; let old_name = aggregator.name.clone(); - let mut conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let conn = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_request_json(json!({ "name": "irrelevant" })) .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); assert_eq!(aggregator.reload(app.db()).await?.unwrap().name, old_name); Ok(()) } @@ -939,13 +939,13 @@ mod update { .update(app.db()) .await?; let name = fixtures::random_name(); - let mut conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let conn = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_request_json(json!({ "name": name })) .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); Ok(()) } @@ -1041,13 +1041,13 @@ mod update { let account = fixtures::account(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; let name_before = aggregator.name.clone(); - let mut conn = patch(format!("/api/aggregators/{}", aggregator.id)) + let conn = patch(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_auth_header(token) .with_request_json(json!({ "name": fixtures::random_name() })) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); assert_eq!( aggregator.reload(app.db()).await?.unwrap().name, name_before @@ -1109,12 +1109,12 @@ mod delete { #[ignore] async fn nonexistant_aggregator(app: DivviupApi) -> TestResult { let (user, ..) = fixtures::member(&app).await; - let mut conn = delete(format!("/api/aggregators/{}", Uuid::new_v4())) + let conn = delete(format!("/api/aggregators/{}", Uuid::new_v4())) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); Ok(()) } @@ -1137,12 +1137,12 @@ mod delete { async fn shared(app: DivviupApi) -> TestResult { let (user, ..) = fixtures::member(&app).await; let aggregator = fixtures::aggregator(&app, None).await; - let mut conn = delete(format!("/api/aggregators/{}", aggregator.id)) + let conn = delete(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); assert!(!aggregator.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) @@ -1168,12 +1168,12 @@ mod delete { let account = fixtures::account(&app).await; let (user, ..) = fixtures::member(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let mut conn = delete(format!("/api/aggregators/{}", aggregator.id)) + let conn = delete(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); assert!(!aggregator.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) @@ -1231,12 +1231,12 @@ mod delete { let (_, token) = fixtures::api_token(&app, &other_account).await; let account = fixtures::account(&app).await; let aggregator = fixtures::aggregator(&app, Some(&account)).await; - let mut conn = delete(format!("/api/aggregators/{}", aggregator.id)) + let conn = delete(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); assert!(!aggregator.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) } @@ -1246,12 +1246,12 @@ mod delete { let account = fixtures::account(&app).await; let (_, token) = fixtures::api_token(&app, &account).await; let aggregator = fixtures::aggregator(&app, None).await; - let mut conn = delete(format!("/api/aggregators/{}", aggregator.id)) + let conn = delete(format!("/api/aggregators/{}", aggregator.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); assert!(!aggregator.reload(app.db()).await?.unwrap().is_tombstoned()); Ok(()) } diff --git a/tests/integration/tasks.rs b/tests/integration/tasks.rs index 299e0fca..2d76ff09 100644 --- a/tests/integration/tasks.rs +++ b/tests/integration/tasks.rs @@ -52,13 +52,13 @@ mod index { fixtures::task(&app, &account).await; fixtures::task(&app, &account).await; - let mut conn = get(format!("/api/accounts/{}/tasks", account.id)) + let conn = get(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); Ok(()) } @@ -148,13 +148,13 @@ mod index { fixtures::task(&app, &account).await; fixtures::task(&app, &account).await; - let mut conn = get(format!("/api/accounts/{}/tasks", account.id)) + let conn = get(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); Ok(()) } } @@ -348,14 +348,14 @@ mod create { let (leader, helper) = fixtures::aggregator_pair(&app, &account).await; let collector_credential = fixtures::collector_credential(&app, &account).await; - let mut conn = post(format!("/api/accounts/{}/tasks", account.id)) + let conn = post(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_state(user) .with_request_json(valid_task_json(&collector_credential, &leader, &helper)) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); Ok(()) } @@ -450,7 +450,7 @@ mod create { let collector_credential = fixtures::collector_credential(&app, &account).await; let count_before = Tasks::find().count(app.db()).await?; - let mut conn = post(format!("/api/accounts/{}/tasks", account.id)) + let conn = post(format!("/api/accounts/{}/tasks", account.id)) .with_api_headers() .with_auth_header(token) .with_request_json(valid_task_json(&collector_credential, &leader, &helper)) @@ -459,7 +459,7 @@ mod create { let count_after = Tasks::find().count(app.db()).await?; assert_eq!(count_before, count_after); - assert_not_found!(conn); + assert_response!(conn, 403); Ok(()) } } @@ -564,12 +564,12 @@ mod show { let user = fixtures::user(); let account = fixtures::account(&app).await; let task = fixtures::task(&app, &account).await; - let mut conn = get(format!("/api/tasks/{}", task.id)) + let conn = get(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); Ok(()) } @@ -592,12 +592,12 @@ mod show { #[test(harness = set_up)] async fn nonexistant_task(app: DivviupApi) -> TestResult { let user = fixtures::user(); - let mut conn = get("/api/tasks/some-made-up-id") + let conn = get("/api/tasks/some-made-up-id") .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); Ok(()) } @@ -639,12 +639,12 @@ mod show { let (_, token) = fixtures::api_token(&app, &other_account).await; let account = fixtures::account(&app).await; let task = fixtures::task(&app, &account).await; - let mut conn = get(format!("/api/tasks/{}", task.id)) + let conn = get(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); Ok(()) } } @@ -799,14 +799,14 @@ mod update { let account = fixtures::account(&app).await; let task = fixtures::task(&app, &account).await; - let mut conn = patch(format!("/api/tasks/{}", task.id)) + let conn = patch(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_request_json(json!({ "name": "irrelevant" })) .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); assert_eq!( task.reload(app.db()).await?.unwrap().name, task.name // unchanged @@ -837,13 +837,13 @@ mod update { #[test(harness = set_up)] async fn nonexistant_task(app: DivviupApi) -> TestResult { let user = fixtures::user(); - let mut conn = patch("/api/tasks/not-a-task-id") + let conn = patch("/api/tasks/not-a-task-id") .with_api_headers() .with_request_json(json!({ "name": "irrelevant" })) .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); Ok(()) } @@ -895,7 +895,7 @@ mod update { let task = fixtures::task(&app, &account).await; let name_before = task.name.clone(); let new_name = format!("new name {}", fixtures::random_name()); - let mut conn = patch(format!("/api/tasks/{}", task.id)) + let conn = patch(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_request_json(json!({ "name": &new_name })) .with_auth_header(token) @@ -903,7 +903,7 @@ mod update { .await; assert_eq!(task.reload(app.db()).await?.unwrap().name, name_before); - assert_not_found!(conn); + assert_response!(conn, 403); Ok(()) } } @@ -1060,12 +1060,12 @@ mod delete { let account = fixtures::account(&app).await; let task = fixtures::task(&app, &account).await; - let mut conn = delete(format!("/api/tasks/{}", task.id)) + let conn = delete(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); let task_reload = task.reload(app.db()).await?.unwrap(); assert_eq!(task_reload.expiration, task.expiration); assert_eq!(task_reload.deleted_at, None); @@ -1095,12 +1095,12 @@ mod delete { #[test(harness = set_up)] async fn nonexistant_task(app: DivviupApi) -> TestResult { let user = fixtures::user(); - let mut conn = delete("/api/tasks/not-a-task-id") + let conn = delete("/api/tasks/not-a-task-id") .with_api_headers() .with_state(user) .run_async(&app) .await; - assert_not_found!(conn); + assert_response!(conn, 403); Ok(()) } @@ -1150,7 +1150,7 @@ mod delete { let (_, token) = fixtures::api_token(&app, &other_account).await; let account = fixtures::account(&app).await; let task = fixtures::task(&app, &account).await; - let mut conn = delete(format!("/api/tasks/{}", task.id)) + let conn = delete(format!("/api/tasks/{}", task.id)) .with_api_headers() .with_auth_header(token) .run_async(&app) @@ -1159,7 +1159,7 @@ mod delete { let task_reload = task.reload(app.db()).await?.unwrap(); assert_eq!(task_reload.expiration, task.expiration); assert_eq!(task_reload.deleted_at, None); - assert_not_found!(conn); + assert_response!(conn, 403); Ok(()) } }