Skip to content

Commit

Permalink
refactor(api): [torrust#179] Axum API, category context, delete category
Browse files Browse the repository at this point in the history
  • Loading branch information
josecelano committed Jun 15, 2023
1 parent f63bf05 commit b4a7ea6
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 10 deletions.
4 changes: 3 additions & 1 deletion src/web/api/v1/contexts/category/forms.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
pub struct CategoryForm {
pub struct AddCategoryForm {
pub name: String,
pub icon: Option<String>,
}

pub type DeleteCategoryForm = AddCategoryForm;
32 changes: 29 additions & 3 deletions src/web/api/v1/contexts/category/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use std::sync::Arc;
use axum::extract::{self, State};
use axum::response::Json;

use super::forms::CategoryForm;
use super::responses::added_category;
use super::forms::{AddCategoryForm, DeleteCategoryForm};
use super::responses::{added_category, deleted_category};
use crate::common::AppData;
use crate::databases::database::{self, Category};
use crate::errors::ServiceError;
Expand Down Expand Up @@ -48,7 +48,7 @@ pub async fn get_all_handler(
pub async fn add_handler(
State(app_data): State<Arc<AppData>>,
Extract(maybe_bearer_token): Extract,
extract::Json(category_form): extract::Json<CategoryForm>,
extract::Json(category_form): extract::Json<AddCategoryForm>,
) -> Result<Json<OkResponse<String>>, ServiceError> {
let user_id = app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await?;

Expand All @@ -57,3 +57,29 @@ pub async fn add_handler(
Err(error) => Err(error),
}
}

/// It deletes a category.
///
/// # Errors
///
/// It returns an error if:
///
/// - The user does not have permissions to delete category.
/// - There is a database error.
#[allow(clippy::unused_async)]
pub async fn delete_handler(
State(app_data): State<Arc<AppData>>,
Extract(maybe_bearer_token): Extract,
extract::Json(category_form): extract::Json<DeleteCategoryForm>,
) -> Result<Json<OkResponse<String>>, ServiceError> {
// code-review: why do we need to send the whole category object to delete it?
// And we should use the ID instead of the name, because the name could change
// or we could add support for multiple languages.

let user_id = app_data.auth.get_user_id_from_bearer_token(&maybe_bearer_token).await?;

match app_data.category_service.delete_category(&category_form.name, &user_id).await {
Ok(_) => Ok(deleted_category(&category_form.name)),
Err(error) => Err(error),
}
}
7 changes: 7 additions & 0 deletions src/web/api/v1/contexts/category/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,10 @@ pub fn added_category(category_name: &str) -> Json<OkResponse<String>> {
data: category_name.to_string(),
})
}

/// Response after successfully deleting a new category.
pub fn deleted_category(category_name: &str) -> Json<OkResponse<String>> {
Json(OkResponse {
data: category_name.to_string(),
})
}
7 changes: 4 additions & 3 deletions src/web/api/v1/contexts/category/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
//! Refer to the [API endpoint documentation](crate::web::api::v1::contexts::category).
use std::sync::Arc;

use axum::routing::{get, post};
use axum::routing::{delete, get, post};
use axum::Router;

use super::handlers::{add_handler, get_all_handler};
use super::handlers::{add_handler, delete_handler, get_all_handler};
use crate::common::AppData;

/// Routes for the [`category`](crate::web::api::v1::contexts::category) API context.
pub fn router(app_data: Arc<AppData>) -> Router {
Router::new()
.route("/", get(get_all_handler).with_state(app_data.clone()))
.route("/", post(add_handler).with_state(app_data))
.route("/", post(add_handler).with_state(app_data.clone()))
.route("/", delete(delete_handler).with_state(app_data))
}
11 changes: 10 additions & 1 deletion tests/common/contexts/category/asserts.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::common::asserts::assert_json_ok;
use crate::common::contexts::category::responses::AddedCategoryResponse;
use crate::common::contexts::category::responses::{AddedCategoryResponse, DeletedCategoryResponse};
use crate::common::responses::TextResponse;

pub fn assert_added_category_response(response: &TextResponse, category_name: &str) {
Expand All @@ -10,3 +10,12 @@ pub fn assert_added_category_response(response: &TextResponse, category_name: &s

assert_json_ok(response);
}

pub fn assert_deleted_category_response(response: &TextResponse, category_name: &str) {
let deleted_category_response: DeletedCategoryResponse = serde_json::from_str(&response.body)
.unwrap_or_else(|_| panic!("response {:#?} should be a DeletedCategoryResponse", response.body));

assert_eq!(deleted_category_response.data, category_name);

assert_json_ok(response);
}
5 changes: 5 additions & 0 deletions tests/common/contexts/category/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ pub struct AddedCategoryResponse {
pub data: String,
}

#[derive(Deserialize)]
pub struct DeletedCategoryResponse {
pub data: String,
}

#[derive(Deserialize, Debug)]
pub struct ListResponse {
pub data: Vec<ListItem>,
Expand Down
78 changes: 76 additions & 2 deletions tests/e2e/contexts/category/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,9 @@ mod with_axum_implementation {

use crate::common::asserts::assert_json_ok;
use crate::common::client::Client;
use crate::common::contexts::category::asserts::assert_added_category_response;
use crate::common::contexts::category::asserts::{assert_added_category_response, assert_deleted_category_response};
use crate::common::contexts::category::fixtures::random_category_name;
use crate::common::contexts::category::forms::AddCategoryForm;
use crate::common::contexts::category::forms::{AddCategoryForm, DeleteCategoryForm};
use crate::common::contexts::category::responses::ListResponse;
use crate::e2e::config::ENV_VAR_E2E_EXCLUDE_AXUM_IMPL;
use crate::e2e::contexts::category::steps::{add_category, add_random_category};
Expand Down Expand Up @@ -379,4 +379,78 @@ mod with_axum_implementation {

assert_eq!(response.status, 400);
}

#[tokio::test]
async fn it_should_allow_admins_to_delete_categories() {
let mut env = TestEnv::new();
env.start(api::Implementation::Axum).await;

if env::var(ENV_VAR_E2E_EXCLUDE_AXUM_IMPL).is_ok() {
println!("Skipped");
return;
}

let logged_in_admin = new_logged_in_admin(&env).await;
let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_in_admin.token);

let added_category_name = add_random_category(&env).await;

let response = client
.delete_category(DeleteCategoryForm {
name: added_category_name.to_string(),
icon: None,
})
.await;

assert_deleted_category_response(&response, &added_category_name);
}

#[tokio::test]
async fn it_should_not_allow_non_admins_to_delete_categories() {
let mut env = TestEnv::new();
env.start(api::Implementation::Axum).await;

if env::var(ENV_VAR_E2E_EXCLUDE_AXUM_IMPL).is_ok() {
println!("Skipped");
return;
}

let added_category_name = add_random_category(&env).await;

let logged_in_non_admin = new_logged_in_user(&env).await;
let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_in_non_admin.token);

let response = client
.delete_category(DeleteCategoryForm {
name: added_category_name.to_string(),
icon: None,
})
.await;

assert_eq!(response.status, 403);
}

#[tokio::test]
async fn it_should_not_allow_guests_to_delete_categories() {
let mut env = TestEnv::new();
env.start(api::Implementation::Axum).await;

if env::var(ENV_VAR_E2E_EXCLUDE_AXUM_IMPL).is_ok() {
println!("Skipped");
return;
}

let client = Client::unauthenticated(&env.server_socket_addr().unwrap());

let added_category_name = add_random_category(&env).await;

let response = client
.delete_category(DeleteCategoryForm {
name: added_category_name.to_string(),
icon: None,
})
.await;

assert_eq!(response.status, 401);
}
}

0 comments on commit b4a7ea6

Please sign in to comment.