From 44c799e29edb66f1d505d74a685ecc05a6518b1b Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 20 Jun 2023 12:48:46 +0100 Subject: [PATCH] refator(api): [#208] remove ActixWeb implementation --- .github/workflows/develop.yml | 2 - src/app.rs | 2 - src/bin/import_tracker_statistics.rs | 2 +- src/bin/main.rs | 1 - src/bin/upgrade.rs | 2 +- src/common.rs | 2 - src/errors.rs | 26 +- src/lib.rs | 3 +- src/models/torrent.rs | 2 +- src/routes/about.rs | 38 -- src/routes/category.rs | 71 --- src/routes/mod.rs | 21 - src/routes/proxy.rs | 60 -- src/routes/root.rs | 9 - src/routes/settings.rs | 76 --- src/routes/tag.rs | 76 --- src/routes/torrent.rs | 237 -------- src/routes/user.rs | 145 ----- src/web/api/actix.rs | 75 --- src/web/api/axum.rs | 1 - src/web/api/mod.rs | 10 - src/web/api/v1/auth.rs | 44 -- src/web/api/v1/contexts/tag/mod.rs | 2 +- src/web/api/v1/contexts/torrent/mod.rs | 2 +- src/web/api/v1/contexts/user/mod.rs | 14 +- tests/e2e/config.rs | 3 - tests/e2e/contexts/about/contract.rs | 45 -- tests/e2e/contexts/category/contract.rs | 273 --------- tests/e2e/contexts/root/contract.rs | 27 - tests/e2e/contexts/settings/contract.rs | 123 +--- tests/e2e/contexts/tag/contract.rs | 240 -------- tests/e2e/contexts/torrent/contract.rs | 713 ------------------------ tests/e2e/contexts/user/contract.rs | 211 ------- tests/environments/app_starter.rs | 1 - 34 files changed, 15 insertions(+), 2544 deletions(-) delete mode 100644 src/routes/about.rs delete mode 100644 src/routes/category.rs delete mode 100644 src/routes/mod.rs delete mode 100644 src/routes/proxy.rs delete mode 100644 src/routes/root.rs delete mode 100644 src/routes/settings.rs delete mode 100644 src/routes/tag.rs delete mode 100644 src/routes/torrent.rs delete mode 100644 src/routes/user.rs delete mode 100644 src/web/api/actix.rs diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index c613b039..2ee931b2 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -31,5 +31,3 @@ jobs: run: cargo llvm-cov nextest - name: E2E Tests run: ./docker/bin/run-e2e-tests.sh - env: - TORRUST_IDX_BACK_E2E_EXCLUDE_ACTIX_WEB_IMPL: "true" diff --git a/src/app.rs b/src/app.rs index 1c270bc4..c1d89e3c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -24,7 +24,6 @@ use crate::{mailer, tracker}; pub struct Running { pub api_socket_addr: SocketAddr, - pub actix_web_api_server: Option>>, pub axum_api_server: Option>>, pub tracker_data_importer_handle: tokio::task::JoinHandle<()>, } @@ -171,7 +170,6 @@ pub async fn run(configuration: Configuration, api_implementation: &Implementati Running { api_socket_addr: running_api.socket_addr, - actix_web_api_server: running_api.actix_web_api_server, axum_api_server: running_api.axum_api_server, tracker_data_importer_handle: tracker_statistics_importer_handle, } diff --git a/src/bin/import_tracker_statistics.rs b/src/bin/import_tracker_statistics.rs index 0b7f7288..894863fa 100644 --- a/src/bin/import_tracker_statistics.rs +++ b/src/bin/import_tracker_statistics.rs @@ -5,7 +5,7 @@ //! You can execute it with: `cargo run --bin import_tracker_statistics` use torrust_index_backend::console::commands::import_tracker_statistics::run_importer; -#[actix_web::main] +#[tokio::main] async fn main() { run_importer().await; } diff --git a/src/bin/main.rs b/src/bin/main.rs index 46922a13..3772d321 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -11,7 +11,6 @@ async fn main() -> Result<(), std::io::Error> { let app = app::run(configuration, &api_implementation).await; match api_implementation { - Implementation::ActixWeb => app.actix_web_api_server.unwrap().await.expect("the API server was dropped"), Implementation::Axum => app.axum_api_server.unwrap().await.expect("the Axum API server was dropped"), } } diff --git a/src/bin/upgrade.rs b/src/bin/upgrade.rs index 8fb1ee0c..486bde93 100644 --- a/src/bin/upgrade.rs +++ b/src/bin/upgrade.rs @@ -4,7 +4,7 @@ use torrust_index_backend::upgrades::from_v1_0_0_to_v2_0_0::upgrader::run; -#[actix_web::main] +#[tokio::main] async fn main() { run().await; } diff --git a/src/common.rs b/src/common.rs index 90815ca8..0af991a2 100644 --- a/src/common.rs +++ b/src/common.rs @@ -17,8 +17,6 @@ use crate::web::api::v1::auth::Authentication; use crate::{mailer, tracker}; pub type Username = String; -pub type WebAppData = actix_web::web::Data>; - pub struct AppData { pub cfg: Arc, pub database: Arc>, diff --git a/src/errors.rs b/src/errors.rs index 02404896..16c89353 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,10 +1,8 @@ use std::borrow::Cow; use std::error; -use actix_web::http::{header, StatusCode}; -use actix_web::{HttpResponse, HttpResponseBuilder, ResponseError}; use derive_more::{Display, Error}; -use serde::{Deserialize, Serialize}; +use hyper::StatusCode; use crate::databases::database; @@ -139,28 +137,6 @@ pub enum ServiceError { DatabaseError, } -// Begin ActixWeb error handling -// todo: remove after migration to Axum - -#[derive(Serialize, Deserialize)] -pub struct ErrorToResponse { - pub error: String, -} - -impl ResponseError for ServiceError { - fn status_code(&self) -> StatusCode { - http_status_code_for_service_error(self) - } - - fn error_response(&self) -> HttpResponse { - HttpResponseBuilder::new(self.status_code()) - .append_header((header::CONTENT_TYPE, "application/json; charset=UTF-8")) - .body(serde_json::to_string(&ErrorToResponse { error: self.to_string() }).unwrap()) - } -} - -// End ActixWeb error handling - impl From for ServiceError { fn from(e: sqlx::Error) -> Self { eprintln!("{e:?}"); diff --git a/src/lib.rs b/src/lib.rs index 36a7b879..ae01a9a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ //! //! This is the backend API for [Torrust Tracker Index](https://github.com/torrust/torrust-index). //! -//! It is written in Rust and uses the [actix-web](https://actix.rs/) framework. It is designed to be +//! It is written in Rust and uses the [Axum](https://github.com/tokio-rs/axum) framework. It is designed to be //! used with by the [Torrust Tracker Index Frontend](https://github.com/torrust/torrust-index-frontend). //! //! If you are looking for information on how to use the API, please see the @@ -266,7 +266,6 @@ pub mod databases; pub mod errors; pub mod mailer; pub mod models; -pub mod routes; pub mod services; pub mod tracker; pub mod ui; diff --git a/src/models/torrent.rs b/src/models/torrent.rs index f9d4dfa5..c06800ca 100644 --- a/src/models/torrent.rs +++ b/src/models/torrent.rs @@ -39,7 +39,7 @@ pub struct Metadata { } impl Metadata { - /// Returns the verify of this [`Create`]. + /// Returns the verify of this [`Metadata`]. /// /// # Errors /// diff --git a/src/routes/about.rs b/src/routes/about.rs deleted file mode 100644 index a88e3865..00000000 --- a/src/routes/about.rs +++ /dev/null @@ -1,38 +0,0 @@ -use actix_web::http::StatusCode; -use actix_web::{web, HttpResponse, Responder}; - -use crate::errors::ServiceResult; -use crate::services::about::{index_page, license_page}; -use crate::web::api::API_VERSION; - -pub fn init(cfg: &mut web::ServiceConfig) { - cfg.service( - web::scope(&format!("/{API_VERSION}/about")) - .service(web::resource("").route(web::get().to(get))) - .service(web::resource("/license").route(web::get().to(license))), - ); -} - -/// Get About Section HTML -/// -/// # Errors -/// -/// This function will not return an error. -#[allow(clippy::unused_async)] -pub async fn get() -> ServiceResult { - Ok(HttpResponse::build(StatusCode::OK) - .content_type("text/html; charset=utf-8") - .body(index_page())) -} - -/// Get the License in HTML -/// -/// # Errors -/// -/// This function will not return an error. -#[allow(clippy::unused_async)] -pub async fn license() -> ServiceResult { - Ok(HttpResponse::build(StatusCode::OK) - .content_type("text/html; charset=utf-8") - .body(license_page())) -} diff --git a/src/routes/category.rs b/src/routes/category.rs deleted file mode 100644 index bd285867..00000000 --- a/src/routes/category.rs +++ /dev/null @@ -1,71 +0,0 @@ -use actix_web::{web, HttpRequest, HttpResponse, Responder}; -use serde::{Deserialize, Serialize}; - -use crate::common::WebAppData; -use crate::errors::ServiceResult; -use crate::models::response::OkResponse; -use crate::web::api::API_VERSION; - -pub fn init(cfg: &mut web::ServiceConfig) { - cfg.service( - web::scope(&format!("/{API_VERSION}/category")).service( - web::resource("") - .route(web::get().to(get)) - .route(web::post().to(add)) - .route(web::delete().to(delete)), - ), - ); -} - -/// Gets the Categories -/// -/// # Errors -/// -/// This function will return an error if there is a database error. -pub async fn get(app_data: WebAppData) -> ServiceResult { - let categories = app_data.category_repository.get_all().await?; - - Ok(HttpResponse::Ok().json(OkResponse { data: categories })) -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Category { - pub name: String, - pub icon: Option, -} - -/// Adds a New Category -/// -/// # Errors -/// -/// This function will return an error if unable to get user. -/// This function will return an error if unable to insert into the database the new category. -pub async fn add(req: HttpRequest, payload: web::Json, app_data: WebAppData) -> ServiceResult { - let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?; - - let _category_id = app_data.category_service.add_category(&payload.name, &user_id).await?; - - Ok(HttpResponse::Ok().json(OkResponse { - data: payload.name.clone(), - })) -} - -/// Deletes a Category -/// -/// # Errors -/// -/// This function will return an error if unable to get user. -/// This function will return an error if unable to delete the category from the database. -pub async fn delete(req: HttpRequest, payload: web::Json, app_data: WebAppData) -> ServiceResult { - // 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_actix_web_request(&req).await?; - - app_data.category_service.delete_category(&payload.name, &user_id).await?; - - Ok(HttpResponse::Ok().json(OkResponse { - data: payload.name.clone(), - })) -} diff --git a/src/routes/mod.rs b/src/routes/mod.rs deleted file mode 100644 index 25ac1551..00000000 --- a/src/routes/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -use actix_web::web; - -pub mod about; -pub mod category; -pub mod proxy; -pub mod root; -pub mod settings; -pub mod tag; -pub mod torrent; -pub mod user; - -pub fn init(cfg: &mut web::ServiceConfig) { - user::init(cfg); - torrent::init(cfg); - category::init(cfg); - settings::init(cfg); - about::init(cfg); - proxy::init(cfg); - tag::init(cfg); - root::init(cfg); -} diff --git a/src/routes/proxy.rs b/src/routes/proxy.rs deleted file mode 100644 index ac6e2967..00000000 --- a/src/routes/proxy.rs +++ /dev/null @@ -1,60 +0,0 @@ -use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse, Responder}; - -use crate::cache::image::manager::Error; -use crate::common::WebAppData; -use crate::errors::ServiceResult; -use crate::ui::proxy::{load_error_images, map_error_to_image}; -use crate::web::api::API_VERSION; - -pub fn init(cfg: &mut web::ServiceConfig) { - cfg.service( - web::scope(&format!("/{API_VERSION}/proxy")).service(web::resource("/image/{url}").route(web::get().to(get_proxy_image))), - ); - - load_error_images(); -} - -/// Get the proxy image. -/// -/// # Errors -/// -/// This function will return `Ok` only for now. -pub async fn get_proxy_image(req: HttpRequest, app_data: WebAppData, path: web::Path) -> ServiceResult { - let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await.ok(); - - match user_id { - Some(user_id) => { - // Get image URL from URL path - let encoded_image_url = path.into_inner(); - let image_url = urlencoding::decode(&encoded_image_url).unwrap_or_default(); - - match app_data.proxy_service.get_image_by_url(&image_url, &user_id).await { - Ok(image_bytes) => { - // Returns the cached image. - Ok(HttpResponse::build(StatusCode::OK) - .content_type("image/png") - .append_header(("Cache-Control", "max-age=15552000")) - .body(image_bytes)) - } - Err(e) => - // Returns an error image. - // Handling status codes in the frontend other tan OK is quite a pain. - // Return OK for now. - { - Ok(HttpResponse::build(StatusCode::OK) - .content_type("image/png") - .append_header(("Cache-Control", "no-cache")) - .body(map_error_to_image(&e))) - } - } - } - None => { - // Unauthenticated users can't see images. - Ok(HttpResponse::build(StatusCode::OK) - .content_type("image/png") - .append_header(("Cache-Control", "no-cache")) - .body(map_error_to_image(&Error::Unauthenticated))) - } - } -} diff --git a/src/routes/root.rs b/src/routes/root.rs deleted file mode 100644 index 7c82ecbd..00000000 --- a/src/routes/root.rs +++ /dev/null @@ -1,9 +0,0 @@ -use actix_web::web; - -use crate::routes::about; -use crate::web::api::API_VERSION; - -pub fn init(cfg: &mut web::ServiceConfig) { - cfg.service(web::scope("/").service(web::resource("").route(web::get().to(about::get)))); - cfg.service(web::scope(&format!("/{API_VERSION}")).service(web::resource("").route(web::get().to(about::get)))); -} diff --git a/src/routes/settings.rs b/src/routes/settings.rs deleted file mode 100644 index 53d336c1..00000000 --- a/src/routes/settings.rs +++ /dev/null @@ -1,76 +0,0 @@ -use actix_web::{web, HttpRequest, HttpResponse, Responder}; - -use crate::common::WebAppData; -use crate::config; -use crate::errors::ServiceResult; -use crate::models::response::OkResponse; -use crate::web::api::API_VERSION; - -pub fn init(cfg: &mut web::ServiceConfig) { - cfg.service( - web::scope(&format!("/{API_VERSION}/settings")) - .service( - web::resource("") - .route(web::get().to(get_all_handler)) - .route(web::post().to(update_handler)), - ) - .service(web::resource("/name").route(web::get().to(get_site_name_handler))) - .service(web::resource("/public").route(web::get().to(get_public_handler))), - ); -} - -/// Get Settings -/// -/// # Errors -/// -/// This function will return an error if unable to get user from database. -pub async fn get_all_handler(req: HttpRequest, app_data: WebAppData) -> ServiceResult { - let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?; - - let all_settings = app_data.settings_service.get_all(&user_id).await?; - - Ok(HttpResponse::Ok().json(OkResponse { data: all_settings })) -} - -/// Update the settings -/// -/// # Errors -/// -/// Will return an error if: -/// -/// - There is no logged-in user. -/// - The user is not an administrator. -/// - The settings could not be updated because they were loaded from env vars. -pub async fn update_handler( - req: HttpRequest, - payload: web::Json, - app_data: WebAppData, -) -> ServiceResult { - let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?; - - let new_settings = app_data.settings_service.update_all(payload.into_inner(), &user_id).await?; - - Ok(HttpResponse::Ok().json(OkResponse { data: new_settings })) -} - -/// Get Public Settings -/// -/// # Errors -/// -/// This function should not return an error. -pub async fn get_public_handler(app_data: WebAppData) -> ServiceResult { - let public_settings = app_data.settings_service.get_public().await; - - Ok(HttpResponse::Ok().json(OkResponse { data: public_settings })) -} - -/// Get Name of Website -/// -/// # Errors -/// -/// This function should not return an error. -pub async fn get_site_name_handler(app_data: WebAppData) -> ServiceResult { - let site_name = app_data.settings_service.get_site_name().await; - - Ok(HttpResponse::Ok().json(OkResponse { data: site_name })) -} diff --git a/src/routes/tag.rs b/src/routes/tag.rs deleted file mode 100644 index 025031c6..00000000 --- a/src/routes/tag.rs +++ /dev/null @@ -1,76 +0,0 @@ -use actix_web::{web, HttpRequest, HttpResponse, Responder}; -use serde::{Deserialize, Serialize}; - -use crate::common::WebAppData; -use crate::errors::ServiceResult; -use crate::models::response::OkResponse; -use crate::models::torrent_tag::TagId; -use crate::web::api::API_VERSION; - -pub fn init(cfg: &mut web::ServiceConfig) { - cfg.service( - web::scope(&format!("/{API_VERSION}/tag")).service( - web::resource("") - .route(web::post().to(create)) - .route(web::delete().to(delete)), - ), - ); - cfg.service(web::scope(&format!("/{API_VERSION}/tags")).service(web::resource("").route(web::get().to(get_all)))); -} - -/// Get Tags -/// -/// # Errors -/// -/// This function will return an error if unable to get tags from database. -pub async fn get_all(app_data: WebAppData) -> ServiceResult { - let tags = app_data.tag_repository.get_all().await?; - - Ok(HttpResponse::Ok().json(OkResponse { data: tags })) -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Create { - pub name: String, -} - -/// Create Tag -/// -/// # Errors -/// -/// This function will return an error if unable to: -/// -/// * Get the requesting user id from the request. -/// * Get the compact user from the user id. -/// * Add the new tag to the database. -pub async fn create(req: HttpRequest, payload: web::Json, app_data: WebAppData) -> ServiceResult { - let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?; - - app_data.tag_service.add_tag(&payload.name, &user_id).await?; - - Ok(HttpResponse::Ok().json(OkResponse { - data: payload.name.to_string(), - })) -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Delete { - pub tag_id: TagId, -} - -/// Delete Tag -/// -/// # Errors -/// -/// This function will return an error if unable to: -/// -/// * Get the requesting user id from the request. -/// * Get the compact user from the user id. -/// * Delete the tag from the database. -pub async fn delete(req: HttpRequest, payload: web::Json, app_data: WebAppData) -> ServiceResult { - let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?; - - app_data.tag_service.delete_tag(&payload.tag_id, &user_id).await?; - - Ok(HttpResponse::Ok().json(OkResponse { data: payload.tag_id })) -} diff --git a/src/routes/torrent.rs b/src/routes/torrent.rs deleted file mode 100644 index d422e3b7..00000000 --- a/src/routes/torrent.rs +++ /dev/null @@ -1,237 +0,0 @@ -use std::io::{Cursor, Write}; -use std::str::FromStr; - -use actix_multipart::Multipart; -use actix_web::web::Query; -use actix_web::{web, HttpRequest, HttpResponse, Responder}; -use futures::{StreamExt, TryStreamExt}; -use serde::Deserialize; -use sqlx::FromRow; - -use crate::common::WebAppData; -use crate::errors::{ServiceError, ServiceResult}; -use crate::models::info_hash::InfoHash; -use crate::models::response::{NewTorrentResponse, OkResponse}; -use crate::models::torrent::{AddTorrentRequest, Metadata}; -use crate::models::torrent_tag::TagId; -use crate::services::torrent::ListingRequest; -use crate::utils::parse_torrent; -use crate::web::api::API_VERSION; - -pub fn init(cfg: &mut web::ServiceConfig) { - cfg.service( - web::scope(&format!("/{API_VERSION}/torrent")) - .service(web::resource("/upload").route(web::post().to(upload_torrent_handler))) - .service(web::resource("/download/{info_hash}").route(web::get().to(download_torrent_handler))) - .service( - web::resource("/{info_hash}") - .route(web::get().to(get_torrent_info_handler)) - .route(web::put().to(update_torrent_info_handler)) - .route(web::delete().to(delete_torrent_handler)), - ), - ); - cfg.service( - web::scope(&format!("/{API_VERSION}/torrents")).service(web::resource("").route(web::get().to(get_torrents_handler))), - ); -} - -#[derive(FromRow)] -pub struct Count { - pub count: i32, -} - -#[derive(Debug, Deserialize)] -pub struct Update { - title: Option, - description: Option, - tags: Option>, -} - -/// Upload a Torrent to the Index -/// -/// # Errors -/// -/// This function will return an error if there was a problem uploading the -/// torrent. -pub async fn upload_torrent_handler(req: HttpRequest, payload: Multipart, app_data: WebAppData) -> ServiceResult { - let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?; - - let torrent_request = get_torrent_request_from_payload(payload).await?; - - let info_hash = torrent_request.torrent.info_hash().clone(); - - let torrent_service = app_data.torrent_service.clone(); - - let torrent_id = torrent_service.add_torrent(torrent_request, user_id).await?; - - Ok(HttpResponse::Ok().json(OkResponse { - data: NewTorrentResponse { torrent_id, info_hash }, - })) -} - -/// Returns the torrent as a byte stream `application/x-bittorrent`. -/// -/// # Errors -/// -/// Returns `ServiceError::BadRequest` if the torrent info-hash is invalid. -pub async fn download_torrent_handler(req: HttpRequest, app_data: WebAppData) -> ServiceResult { - let info_hash = get_torrent_info_hash_from_request(&req)?; - let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await.ok(); - - let torrent = app_data.torrent_service.get_torrent(&info_hash, user_id).await?; - - let buffer = parse_torrent::encode_torrent(&torrent).map_err(|_| ServiceError::InternalServerError)?; - - Ok(HttpResponse::Ok().content_type("application/x-bittorrent").body(buffer)) -} - -/// Get Torrent from the Index -/// -/// # Errors -/// -/// This function will return an error if unable to get torrent info. -pub async fn get_torrent_info_handler(req: HttpRequest, app_data: WebAppData) -> ServiceResult { - let info_hash = get_torrent_info_hash_from_request(&req)?; - let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await.ok(); - - let torrent_response = app_data.torrent_service.get_torrent_info(&info_hash, user_id).await?; - - Ok(HttpResponse::Ok().json(OkResponse { data: torrent_response })) -} - -/// Update a Torrent in the Index -/// -/// # Errors -/// -/// This function will return an error if unable to: -/// -/// * Get the user id from the request. -/// * Get the torrent info-hash from the request. -/// * Update the torrent info. -pub async fn update_torrent_info_handler( - req: HttpRequest, - payload: web::Json, - app_data: WebAppData, -) -> ServiceResult { - let info_hash = get_torrent_info_hash_from_request(&req)?; - let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?; - - let torrent_response = app_data - .torrent_service - .update_torrent_info(&info_hash, &payload.title, &payload.description, &payload.tags, &user_id) - .await?; - - Ok(HttpResponse::Ok().json(OkResponse { data: torrent_response })) -} - -/// Delete a Torrent from the Index -/// -/// # Errors -/// -/// This function will return an error if unable to: -/// -/// * Get the user id from the request. -/// * Get the torrent info-hash from the request. -/// * Delete the torrent. -pub async fn delete_torrent_handler(req: HttpRequest, app_data: WebAppData) -> ServiceResult { - let info_hash = get_torrent_info_hash_from_request(&req)?; - let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?; - - let deleted_torrent_response = app_data.torrent_service.delete_torrent(&info_hash, &user_id).await?; - - Ok(HttpResponse::Ok().json(OkResponse { - data: deleted_torrent_response, - })) -} - -/// It returns a list of torrents matching the search criteria. -/// Eg: `/torrents?categories=music,other,movie&search=bunny&sort=size_DESC` -/// -/// # Errors -/// -/// Returns a `ServiceError::DatabaseError` if the database query fails. -pub async fn get_torrents_handler(criteria: Query, app_data: WebAppData) -> ServiceResult { - let torrents_response = app_data.torrent_service.generate_torrent_info_listing(&criteria).await?; - - Ok(HttpResponse::Ok().json(OkResponse { data: torrents_response })) -} - -fn get_torrent_info_hash_from_request(req: &HttpRequest) -> Result { - match req.match_info().get("info_hash") { - None => Err(ServiceError::BadRequest), - Some(info_hash) => match InfoHash::from_str(info_hash) { - Err(_) => Err(ServiceError::BadRequest), - Ok(v) => Ok(v), - }, - } -} - -async fn get_torrent_request_from_payload(mut payload: Multipart) -> Result { - let torrent_buffer = vec![0u8]; - let mut torrent_cursor = Cursor::new(torrent_buffer); - - let mut title = String::new(); - let mut description = String::new(); - let mut category = String::new(); - let mut tags: Vec = vec![]; - - while let Ok(Some(mut field)) = payload.try_next().await { - match field.content_disposition().get_name().unwrap() { - "title" | "description" | "category" | "tags" => { - let data = field.next().await; - - if data.is_none() { - continue; - } - - let wrapped_data = &data.unwrap().map_err(|_| ServiceError::BadRequest)?; - let parsed_data = std::str::from_utf8(wrapped_data).map_err(|_| ServiceError::BadRequest)?; - - match field.content_disposition().get_name().unwrap() { - "title" => title = parsed_data.to_string(), - "description" => description = parsed_data.to_string(), - "category" => category = parsed_data.to_string(), - "tags" => tags = serde_json::from_str(parsed_data).map_err(|_| ServiceError::BadRequest)?, - _ => {} - } - } - "torrent" => { - if *field.content_type().unwrap() != "application/x-bittorrent" { - return Err(ServiceError::InvalidFileType); - } - - while let Some(chunk) = field.next().await { - let data = chunk.unwrap(); - torrent_cursor.write_all(&data)?; - } - } - _ => {} - } - } - - let fields = Metadata { - title, - description, - category, - tags, - }; - - fields.verify()?; - - let position = usize::try_from(torrent_cursor.position()).map_err(|_| ServiceError::InvalidTorrentFile)?; - let inner = torrent_cursor.get_ref(); - - let torrent = parse_torrent::decode_torrent(&inner[..position]).map_err(|_| ServiceError::InvalidTorrentFile)?; - - // make sure that the pieces key has a length that is a multiple of 20 - if let Some(pieces) = torrent.info.pieces.as_ref() { - if pieces.as_ref().len() % 20 != 0 { - return Err(ServiceError::InvalidTorrentPiecesLength); - } - } - - Ok(AddTorrentRequest { - metadata: fields, - torrent, - }) -} diff --git a/src/routes/user.rs b/src/routes/user.rs deleted file mode 100644 index 6ff78170..00000000 --- a/src/routes/user.rs +++ /dev/null @@ -1,145 +0,0 @@ -use actix_web::{web, HttpRequest, HttpResponse, Responder}; -use serde::{Deserialize, Serialize}; - -use crate::common::WebAppData; -use crate::errors::{ServiceError, ServiceResult}; -use crate::models::response::{OkResponse, TokenResponse}; -use crate::web::api::v1::contexts::user::forms::RegistrationForm; -use crate::web::api::API_VERSION; - -pub fn init(cfg: &mut web::ServiceConfig) { - cfg.service( - web::scope(&format!("/{API_VERSION}/user")) - // Registration - .service(web::resource("/register").route(web::post().to(registration_handler))) - // code-review: should this be part of the REST API? - // - This endpoint should only verify the email. - // - There should be an independent service (web app) serving the email verification page. - // The wep app can user this endpoint to verify the email and render the page accordingly. - .service(web::resource("/email/verify/{token}").route(web::get().to(email_verification_handler))) - // Authentication - .service(web::resource("/login").route(web::post().to(login_handler))) - .service(web::resource("/token/verify").route(web::post().to(verify_token_handler))) - .service(web::resource("/token/renew").route(web::post().to(renew_token_handler))) - // User Access Ban - // code-review: should not this be a POST method? We add the user to the blacklist. We do not delete the user. - .service(web::resource("/ban/{user}").route(web::delete().to(ban_handler))), - ); -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Login { - pub login: String, - pub password: String, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Token { - pub token: String, -} - -/// Register a User in the Index -/// -/// # Errors -/// -/// This function will return an error if the user could not be registered. -pub async fn registration_handler( - req: HttpRequest, - registration_form: web::Json, - app_data: WebAppData, -) -> ServiceResult { - let conn_info = req.connection_info().clone(); - // todo: check if `base_url` option was define in settings `net->base_url`. - // It should have priority over request he - let api_base_url = format!("{}://{}", conn_info.scheme(), conn_info.host()); - - let _user_id = app_data - .registration_service - .register_user(®istration_form, &api_base_url) - .await?; - - Ok(HttpResponse::Ok()) -} - -/// Login user to Index -/// -/// # Errors -/// -/// This function will return an error if the user could not be logged in. -pub async fn login_handler(payload: web::Json, app_data: WebAppData) -> ServiceResult { - let (token, user_compact) = app_data - .authentication_service - .login(&payload.login, &payload.password) - .await?; - - Ok(HttpResponse::Ok().json(OkResponse { - data: TokenResponse { - token, - username: user_compact.username, - admin: user_compact.administrator, - }, - })) -} - -/// Verify a supplied JWT. -/// -/// # Errors -/// -/// This function will return an error if unable to verify the supplied payload as a valid jwt. -pub async fn verify_token_handler(payload: web::Json, app_data: WebAppData) -> ServiceResult { - // Verify if JWT is valid - let _claims = app_data.json_web_token.verify(&payload.token).await?; - - Ok(HttpResponse::Ok().json(OkResponse { - data: "Token is valid.".to_string(), - })) -} - -/// Renew a supplied JWT. -/// -/// # Errors -/// -/// This function will return an error if unable to verify the supplied -/// payload as a valid JWT. -pub async fn renew_token_handler(payload: web::Json, app_data: WebAppData) -> ServiceResult { - let (token, user_compact) = app_data.authentication_service.renew_token(&payload.token).await?; - - Ok(HttpResponse::Ok().json(OkResponse { - data: TokenResponse { - token, - username: user_compact.username, - admin: user_compact.administrator, - }, - })) -} - -pub async fn email_verification_handler(req: HttpRequest, app_data: WebAppData) -> String { - // Get token from URL path - let token = match req.match_info().get("token").ok_or(ServiceError::InternalServerError) { - Ok(token) => token, - Err(err) => return err.to_string(), - }; - - match app_data.registration_service.verify_email(token).await { - Ok(_) => String::from("Email verified, you can close this page."), - Err(error) => error.to_string(), - } -} - -/// Ban a user from the Index -/// -/// TODO: add reason and `date_expiry` parameters to request -/// -/// # Errors -/// -/// This function will return if the user could not be banned. -pub async fn ban_handler(req: HttpRequest, app_data: WebAppData) -> ServiceResult { - let user_id = app_data.auth.get_user_id_from_actix_web_request(&req).await?; - let to_be_banned_username = req.match_info().get("user").ok_or(ServiceError::InternalServerError)?; - - app_data.ban_service.ban_user(to_be_banned_username, &user_id).await?; - - Ok(HttpResponse::Ok().json(OkResponse { - data: format!("Banned user: {to_be_banned_username}"), - })) -} diff --git a/src/web/api/actix.rs b/src/web/api/actix.rs deleted file mode 100644 index 47b5f3d6..00000000 --- a/src/web/api/actix.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::net::SocketAddr; -use std::sync::Arc; - -use actix_cors::Cors; -use actix_web::{middleware, web, App, HttpServer}; -use log::info; -use tokio::sync::oneshot::{self, Sender}; - -use super::Running; -use crate::common::AppData; -use crate::routes; -use crate::web::api::ServerStartedMessage; - -/// Starts the API server with `ActixWeb`. -/// -/// # Panics -/// -/// Panics if the API server can't be started. -pub async fn start(app_data: Arc, net_ip: &str, net_port: u16) -> Running { - let config_socket_addr: SocketAddr = format!("{net_ip}:{net_port}") - .parse() - .expect("API server socket address to be valid."); - - let (tx, rx) = oneshot::channel::(); - - // Run the API server - let join_handle = tokio::spawn(async move { - info!("Starting API server with net config: {} ...", config_socket_addr); - - let server_future = start_server(config_socket_addr, app_data.clone(), tx); - - let _ = server_future.await; - - Ok(()) - }); - - // Wait until the API server is running - let bound_addr = match rx.await { - Ok(msg) => msg.socket_addr, - Err(e) => panic!("API server start. The API server was dropped: {e}"), - }; - - info!("API server started"); - - Running { - socket_addr: bound_addr, - actix_web_api_server: Some(join_handle), - axum_api_server: None, - } -} - -fn start_server( - config_socket_addr: SocketAddr, - app_data: Arc, - tx: Sender, -) -> actix_web::dev::Server { - let server = HttpServer::new(move || { - App::new() - .wrap(Cors::permissive()) - .app_data(web::Data::new(app_data.clone())) - .wrap(middleware::Logger::default()) - .configure(routes::init) - }) - .bind(config_socket_addr) - .expect("can't bind server to socket address"); - - let bound_addr = server.addrs()[0]; - - info!("API server listening on http://{}", bound_addr); - - tx.send(ServerStartedMessage { socket_addr: bound_addr }) - .expect("the API server should not be dropped"); - - server.run() -} diff --git a/src/web/api/axum.rs b/src/web/api/axum.rs index 5371dbc9..c281381c 100644 --- a/src/web/api/axum.rs +++ b/src/web/api/axum.rs @@ -42,7 +42,6 @@ pub async fn start(app_data: Arc, net_ip: &str, net_port: u16) -> Runni Running { socket_addr: bound_addr, - actix_web_api_server: None, axum_api_server: Some(join_handle), } } diff --git a/src/web/api/mod.rs b/src/web/api/mod.rs index 8159eae3..31c38487 100644 --- a/src/web/api/mod.rs +++ b/src/web/api/mod.rs @@ -3,7 +3,6 @@ //! Currently, the API has only one version: `v1`. //! //! Refer to the [`v1`](crate::web::api::v1) module for more information. -pub mod actix; pub mod axum; pub mod v1; @@ -19,8 +18,6 @@ pub const API_VERSION: &str = "v1"; /// API implementations. pub enum Implementation { - /// API implementation with Actix Web. - ActixWeb, /// API implementation with Axum. Axum, } @@ -29,8 +26,6 @@ pub enum Implementation { pub struct Running { /// The socket address the API server is listening on. pub socket_addr: SocketAddr, - /// The API server when using Actix Web. - pub actix_web_api_server: Option>>, /// The handle for the running API server task when using Axum. pub axum_api_server: Option>>, } @@ -42,14 +37,9 @@ pub struct ServerStartedMessage { } /// Starts the API server. -/// -/// We are migrating the API server from Actix Web to Axum. While the migration -/// is in progress, we will keep both implementations, running the Axum one only -/// for testing purposes. #[must_use] pub async fn start(app_data: Arc, net_ip: &str, net_port: u16, implementation: &Implementation) -> api::Running { match implementation { - Implementation::ActixWeb => actix::start(app_data, net_ip, net_port).await, Implementation::Axum => axum::start(app_data, net_ip, net_port).await, } } diff --git a/src/web/api/v1/auth.rs b/src/web/api/v1/auth.rs index 268efc14..3967aa28 100644 --- a/src/web/api/v1/auth.rs +++ b/src/web/api/v1/auth.rs @@ -80,7 +80,6 @@ //! ``` use std::sync::Arc; -use actix_web::HttpRequest; use hyper::http::HeaderValue; use crate::common::AppData; @@ -113,47 +112,6 @@ impl Authentication { self.json_web_token.verify(token).await } - // Begin ActixWeb - - /// Get User id from `ActixWeb` Request - /// - /// # Errors - /// - /// This function will return an error if it can get claims from the request - pub async fn get_user_id_from_actix_web_request(&self, req: &HttpRequest) -> Result { - let claims = self.get_claims_from_actix_web_request(req).await?; - Ok(claims.user.user_id) - } - - /// Get Claims from `ActixWeb` Request - /// - /// # Errors - /// - /// - Return an `ServiceError::TokenNotFound` if `HeaderValue` is `None`. - /// - Pass through the `ServiceError::TokenInvalid` if unable to verify the JWT. - async fn get_claims_from_actix_web_request(&self, req: &HttpRequest) -> Result { - match req.headers().get("Authorization") { - Some(auth) => { - let split: Vec<&str> = auth - .to_str() - .expect("variable `auth` contains data that is not visible ASCII chars.") - .split("Bearer") - .collect(); - let token = split[1].trim(); - - match self.verify_jwt(token).await { - Ok(claims) => Ok(claims), - Err(e) => Err(e), - } - } - None => Err(ServiceError::TokenNotFound), - } - } - - // End ActixWeb - - // Begin Axum - /// Get logged-in user ID from bearer token /// /// # Errors @@ -181,8 +139,6 @@ impl Authentication { None => Err(ServiceError::TokenNotFound), } } - - // End Axum } /// Parses the token from the `Authorization` header. diff --git a/src/web/api/v1/contexts/tag/mod.rs b/src/web/api/v1/contexts/tag/mod.rs index 3e969590..1d4d77de 100644 --- a/src/web/api/v1/contexts/tag/mod.rs +++ b/src/web/api/v1/contexts/tag/mod.rs @@ -41,7 +41,7 @@ //! ``` //! **Resource** //! -//! Refer to the [`Tag`](crate::databases::database::Tag) +//! Refer to the [`Tag`](crate::models::torrent_tag::TorrentTag) //! struct for more information about the response attributes. //! //! # Add a tag diff --git a/src/web/api/v1/contexts/torrent/mod.rs b/src/web/api/v1/contexts/torrent/mod.rs index 78351f78..81c1651d 100644 --- a/src/web/api/v1/contexts/torrent/mod.rs +++ b/src/web/api/v1/contexts/torrent/mod.rs @@ -243,7 +243,7 @@ //! `description` | `Option` | The torrent description | No | `MandelbrotSet image` //! //! -//! Refer to the [`Update`](crate::routes::torrent::Update) +//! Refer to the [`UpdateTorrentInfoForm`](crate::web::api::v1::contexts::torrent::forms::UpdateTorrentInfoForm) //! struct for more information about the request attributes. //! //! **Example request** diff --git a/src/web/api/v1/contexts/user/mod.rs b/src/web/api/v1/contexts/user/mod.rs index 3a4267c0..0b0b0eb5 100644 --- a/src/web/api/v1/contexts/user/mod.rs +++ b/src/web/api/v1/contexts/user/mod.rs @@ -50,7 +50,7 @@ //! max_password_length = 64 //! ``` //! -//! Refer to the [`RegistrationForm`](crate::routes::user::RegistrationForm) +//! Refer to the [`RegistrationForm`](crate::web::api::v1::contexts::user::forms::RegistrationForm) //! struct for more information about the registration form. //! //! **Example request** @@ -97,8 +97,8 @@ //! `login` | `String` | The password | Yes | `indexadmin` //! `password` | `String` | The password | Yes | `BenoitMandelbrot1924` //! -//! Refer to the [`RegistrationForm`](crate::routes::user::Login) -//! struct for more information about the registration form. +//! Refer to the [`LoginForm`](crate::web::api::v1::contexts::user::forms::LoginForm) +//! struct for more information about the login form. //! //! **Example request** //! @@ -125,8 +125,8 @@ //! ---|---|---|---|--- //! `token` | `String` | The token you want to verify | Yes | `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI` //! -//! Refer to the [`Token`](crate::routes::user::Token) -//! struct for more information about the registration form. +//! Refer to the [`JsonWebToken`](crate::web::api::v1::contexts::user::forms::JsonWebToken) +//! struct for more information about the token. //! //! **Example request** //! @@ -171,8 +171,8 @@ //! ---|---|---|---|--- //! `token` | `String` | The current valid token | Yes | `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI` //! -//! Refer to the [`Token`](crate::routes::user::Token) -//! struct for more information about the registration form. +//! Refer to the [`JsonWebToken`](crate::web::api::v1::contexts::user::forms::JsonWebToken) +//! struct for more information about the token. //! //! **Example request** //! diff --git a/tests/e2e/config.rs b/tests/e2e/config.rs index ce168333..f3179f43 100644 --- a/tests/e2e/config.rs +++ b/tests/e2e/config.rs @@ -14,9 +14,6 @@ pub const ENV_VAR_E2E_SHARED: &str = "TORRUST_IDX_BACK_E2E_SHARED"; /// The whole `config.toml` file content. It has priority over the config file. pub const ENV_VAR_E2E_CONFIG: &str = "TORRUST_IDX_BACK_E2E_CONFIG"; -/// If present, E2E tests for new `ActixWeb` implementation will not be executed -pub const ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL: &str = "TORRUST_IDX_BACK_E2E_EXCLUDE_ACTIX_WEB_IMPL"; - // Default values pub const ENV_VAR_E2E_DEFAULT_CONFIG_PATH: &str = "./config-idx-back.local.toml"; diff --git a/tests/e2e/contexts/about/contract.rs b/tests/e2e/contexts/about/contract.rs index 7c3e84b2..52d7efed 100644 --- a/tests/e2e/contexts/about/contract.rs +++ b/tests/e2e/contexts/about/contract.rs @@ -1,49 +1,4 @@ //! API contract for `about` context. -use std::env; - -use torrust_index_backend::web::api; - -use crate::common::asserts::{assert_response_title, assert_text_ok}; -use crate::common::client::Client; -use crate::e2e::config::ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL; -use crate::e2e::environment::TestEnv; - -#[tokio::test] -async fn it_should_load_the_about_page_with_information_about_the_api() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let response = client.about().await; - - assert_text_ok(&response); - assert_response_title(&response, "About"); -} - -#[tokio::test] -async fn it_should_load_the_license_page_at_the_api_entrypoint() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let response = client.license().await; - - assert_text_ok(&response); - assert_response_title(&response, "Licensing"); -} - mod with_axum_implementation { use torrust_index_backend::web::api; diff --git a/tests/e2e/contexts/category/contract.rs b/tests/e2e/contexts/category/contract.rs index 3187d89d..eb5f2d94 100644 --- a/tests/e2e/contexts/category/contract.rs +++ b/tests/e2e/contexts/category/contract.rs @@ -1,277 +1,4 @@ //! API contract for `category` context. -use std::env; - -use torrust_index_backend::web::api; - -use crate::common::asserts::assert_json_ok_response; -use crate::common::client::Client; -use crate::common::contexts::category::fixtures::random_category_name; -use crate::common::contexts::category::forms::{AddCategoryForm, DeleteCategoryForm}; -use crate::common::contexts::category::responses::{AddedCategoryResponse, ListResponse}; -use crate::e2e::config::ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL; -use crate::e2e::contexts::category::steps::{add_category, add_random_category}; -use crate::e2e::contexts::user::steps::{new_logged_in_admin, new_logged_in_user}; -use crate::e2e::environment::TestEnv; - -#[tokio::test] -async fn it_should_return_an_empty_category_list_when_there_are_no_categories() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let response = client.get_categories().await; - - assert_json_ok_response(&response); -} - -#[tokio::test] -async fn it_should_return_a_category_list() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - add_random_category(&env).await; - - let response = client.get_categories().await; - - let res: ListResponse = serde_json::from_str(&response.body).unwrap(); - - // There should be at least the category we added. - // Since this is an E2E test and it could be run in a shared test env, - // there might be more categories. - assert!(res.data.len() > 1); - if let Some(content_type) = &response.content_type { - assert_eq!(content_type, "application/json"); - } - assert_eq!(response.status, 200); -} - -#[tokio::test] -async fn it_should_not_allow_adding_a_new_category_to_unauthenticated_users() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let response = client - .add_category(AddCategoryForm { - name: "CATEGORY NAME".to_string(), - icon: None, - }) - .await; - - assert_eq!(response.status, 401); -} - -#[tokio::test] -async fn it_should_not_allow_adding_a_new_category_to_non_admins() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let logged_non_admin = new_logged_in_user(&env).await; - - let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_non_admin.token); - - let response = client - .add_category(AddCategoryForm { - name: "CATEGORY NAME".to_string(), - icon: None, - }) - .await; - - assert_eq!(response.status, 403); -} - -#[tokio::test] -async fn it_should_allow_admins_to_add_new_categories() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_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 category_name = random_category_name(); - - let response = client - .add_category(AddCategoryForm { - name: category_name.to_string(), - icon: None, - }) - .await; - - let res: AddedCategoryResponse = serde_json::from_str(&response.body).unwrap(); - - assert_eq!(res.data, category_name); - if let Some(content_type) = &response.content_type { - assert_eq!(content_type, "application/json"); - } - assert_eq!(response.status, 200); -} - -#[tokio::test] -async fn it_should_allow_adding_empty_categories() { - // code-review: this is a bit weird, is it a intended behavior? - - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if env.is_shared() { - // This test cannot be run in a shared test env because it will fail - // when the empty category already exits - 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 category_name = String::new(); - - let response = client - .add_category(AddCategoryForm { - name: category_name.to_string(), - icon: None, - }) - .await; - - let res: AddedCategoryResponse = serde_json::from_str(&response.body).unwrap(); - - assert_eq!(res.data, category_name); - if let Some(content_type) = &response.content_type { - assert_eq!(content_type, "application/json"); - } - assert_eq!(response.status, 200); -} - -#[tokio::test] -async fn it_should_not_allow_adding_duplicated_categories() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let added_category_name = add_random_category(&env).await; - - // Try to add the same category again - let response = add_category(&added_category_name, &env).await; - 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::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_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; - - let res: AddedCategoryResponse = serde_json::from_str(&response.body).unwrap(); - - assert_eq!(res.data, added_category_name); - if let Some(content_type) = &response.content_type { - assert_eq!(content_type, "application/json"); - } - assert_eq!(response.status, 200); -} - -#[tokio::test] -async fn it_should_not_allow_non_admins_to_delete_categories() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_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::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_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); -} - mod with_axum_implementation { use torrust_index_backend::web::api; diff --git a/tests/e2e/contexts/root/contract.rs b/tests/e2e/contexts/root/contract.rs index bf7bd754..e66661d2 100644 --- a/tests/e2e/contexts/root/contract.rs +++ b/tests/e2e/contexts/root/contract.rs @@ -1,31 +1,4 @@ //! API contract for `root` context. -use std::env; - -use torrust_index_backend::web::api; - -use crate::common::asserts::{assert_response_title, assert_text_ok}; -use crate::common::client::Client; -use crate::e2e::config::ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL; -use crate::e2e::environment::TestEnv; - -#[tokio::test] -async fn it_should_load_the_about_page_at_the_api_entrypoint() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let response = client.root().await; - - assert_text_ok(&response); - assert_response_title(&response, "About"); -} - mod with_axum_implementation { use torrust_index_backend::web::api; diff --git a/tests/e2e/contexts/settings/contract.rs b/tests/e2e/contexts/settings/contract.rs index d82d45a3..ac4cbd49 100644 --- a/tests/e2e/contexts/settings/contract.rs +++ b/tests/e2e/contexts/settings/contract.rs @@ -1,125 +1,4 @@ -use std::env; - -use torrust_index_backend::web::api; - -use crate::common::client::Client; -use crate::common::contexts::settings::responses::{AllSettingsResponse, Public, PublicSettingsResponse, SiteNameResponse}; -use crate::e2e::config::ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL; -use crate::e2e::contexts::user::steps::new_logged_in_admin; -use crate::e2e::environment::TestEnv; - -#[tokio::test] -async fn it_should_allow_guests_to_get_the_public_settings() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let response = client.get_public_settings().await; - - let res: PublicSettingsResponse = serde_json::from_str(&response.body).unwrap(); - - assert_eq!( - res.data, - Public { - website_name: env.server_settings().unwrap().website.name, - tracker_url: env.server_settings().unwrap().tracker.url, - tracker_mode: env.server_settings().unwrap().tracker.mode, - email_on_signup: env.server_settings().unwrap().auth.email_on_signup, - } - ); - if let Some(content_type) = &response.content_type { - assert_eq!(content_type, "application/json"); - } - assert_eq!(response.status, 200); -} - -#[tokio::test] -async fn it_should_allow_guests_to_get_the_site_name() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let response = client.get_site_name().await; - - let res: SiteNameResponse = serde_json::from_str(&response.body).unwrap(); - - assert_eq!(res.data, "Torrust"); - if let Some(content_type) = &response.content_type { - assert_eq!(content_type, "application/json"); - } - assert_eq!(response.status, 200); -} - -#[tokio::test] -async fn it_should_allow_admins_to_get_all_the_settings() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_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 response = client.get_settings().await; - - let res: AllSettingsResponse = serde_json::from_str(&response.body).unwrap(); - - assert_eq!(res.data, env.server_settings().unwrap()); - if let Some(content_type) = &response.content_type { - assert_eq!(content_type, "application/json"); - } - assert_eq!(response.status, 200); -} - -#[tokio::test] -async fn it_should_allow_admins_to_update_all_the_settings() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if !env.is_isolated() { - // This test can't be executed in a non-isolated environment because - // it will change the settings for all the other tests. - 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 mut new_settings = env.server_settings().unwrap(); - - new_settings.website.name = "UPDATED NAME".to_string(); - - let response = client.update_settings(&new_settings).await; - - let res: AllSettingsResponse = serde_json::from_str(&response.body).unwrap(); - - assert_eq!(res.data, new_settings); - if let Some(content_type) = &response.content_type { - assert_eq!(content_type, "application/json"); - } - assert_eq!(response.status, 200); -} - +//! API contract for `settings` context. mod with_axum_implementation { use torrust_index_backend::web::api; diff --git a/tests/e2e/contexts/tag/contract.rs b/tests/e2e/contexts/tag/contract.rs index 711b1c0a..775b53dc 100644 --- a/tests/e2e/contexts/tag/contract.rs +++ b/tests/e2e/contexts/tag/contract.rs @@ -1,244 +1,4 @@ //! API contract for `tag` context. -use std::env; - -use torrust_index_backend::web::api; - -use crate::common::asserts::assert_json_ok_response; -use crate::common::client::Client; -use crate::common::contexts::tag::fixtures::random_tag_name; -use crate::common::contexts::tag::forms::{AddTagForm, DeleteTagForm}; -use crate::common::contexts::tag::responses::{AddedTagResponse, DeletedTagResponse, ListResponse}; -use crate::e2e::config::ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL; -use crate::e2e::contexts::tag::steps::{add_random_tag, add_tag}; -use crate::e2e::contexts::user::steps::{new_logged_in_admin, new_logged_in_user}; -use crate::e2e::environment::TestEnv; - -#[tokio::test] -async fn it_should_return_an_empty_tag_list_when_there_are_no_tags() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let response = client.get_tags().await; - - assert_json_ok_response(&response); -} - -#[tokio::test] -async fn it_should_return_a_tag_list() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - // Add a tag - let tag_name = random_tag_name(); - let response = add_tag(&tag_name, &env).await; - assert_eq!(response.status, 200); - - let response = client.get_tags().await; - - let res: ListResponse = serde_json::from_str(&response.body).unwrap(); - - // There should be at least the tag we added. - // Since this is an E2E test that could be executed in a shred env, - // there might be more tags. - assert!(!res.data.is_empty()); - if let Some(content_type) = &response.content_type { - assert_eq!(content_type, "application/json"); - } - assert_eq!(response.status, 200); -} - -#[tokio::test] -async fn it_should_not_allow_adding_a_new_tag_to_unauthenticated_users() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let response = client - .add_tag(AddTagForm { - name: "TAG NAME".to_string(), - }) - .await; - - assert_eq!(response.status, 401); -} - -#[tokio::test] -async fn it_should_not_allow_adding_a_new_tag_to_non_admins() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let logged_non_admin = new_logged_in_user(&env).await; - - let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_non_admin.token); - - let response = client - .add_tag(AddTagForm { - name: "TAG NAME".to_string(), - }) - .await; - - assert_eq!(response.status, 403); -} - -#[tokio::test] -async fn it_should_allow_admins_to_add_new_tags() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_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 tag_name = random_tag_name(); - - let response = client - .add_tag(AddTagForm { - name: tag_name.to_string(), - }) - .await; - - let res: AddedTagResponse = serde_json::from_str(&response.body).unwrap(); - - assert_eq!(res.data, tag_name); - if let Some(content_type) = &response.content_type { - assert_eq!(content_type, "application/json"); - } - assert_eq!(response.status, 200); -} - -#[tokio::test] -async fn it_should_allow_adding_duplicated_tags() { - // code-review: is this an intended behavior? - - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - // Add a tag - let random_tag_name = random_tag_name(); - let response = add_tag(&random_tag_name, &env).await; - assert_eq!(response.status, 200); - - // Try to add the same tag again - let response = add_tag(&random_tag_name, &env).await; - assert_eq!(response.status, 200); -} - -#[tokio::test] -async fn it_should_allow_adding_a_tag_with_an_empty_name() { - // code-review: is this an intended behavior? - - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let empty_tag_name = String::new(); - let response = add_tag(&empty_tag_name, &env).await; - assert_eq!(response.status, 200); -} - -#[tokio::test] -async fn it_should_allow_admins_to_delete_tags() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_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 (tag_id, _tag_name) = add_random_tag(&env).await; - - let response = client.delete_tag(DeleteTagForm { tag_id }).await; - - let res: DeletedTagResponse = serde_json::from_str(&response.body).unwrap(); - - assert_eq!(res.data, tag_id); - if let Some(content_type) = &response.content_type { - assert_eq!(content_type, "application/json"); - } - assert_eq!(response.status, 200); -} - -#[tokio::test] -async fn it_should_not_allow_non_admins_to_delete_tags() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - 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 (tag_id, _tag_name) = add_random_tag(&env).await; - - let response = client.delete_tag(DeleteTagForm { tag_id }).await; - - assert_eq!(response.status, 403); -} - -#[tokio::test] -async fn it_should_not_allow_guests_to_delete_tags() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let (tag_id, _tag_name) = add_random_tag(&env).await; - - let response = client.delete_tag(DeleteTagForm { tag_id }).await; - - assert_eq!(response.status, 401); -} - mod with_axum_implementation { use torrust_index_backend::web::api; diff --git a/tests/e2e/contexts/torrent/contract.rs b/tests/e2e/contexts/torrent/contract.rs index 52c084fe..a01cf5d3 100644 --- a/tests/e2e/contexts/torrent/contract.rs +++ b/tests/e2e/contexts/torrent/contract.rs @@ -14,719 +14,6 @@ Get torrent info: - should contain realtime seeders and leechers from the tracker */ -mod for_guests { - use std::env; - - use torrust_index_backend::utils::parse_torrent::decode_torrent; - use torrust_index_backend::web::api; - - use crate::common::client::Client; - use crate::common::contexts::category::fixtures::software_predefined_category_id; - use crate::common::contexts::torrent::asserts::assert_expected_torrent_details; - use crate::common::contexts::torrent::requests::InfoHash; - use crate::common::contexts::torrent::responses::{ - Category, File, TorrentDetails, TorrentDetailsResponse, TorrentListResponse, - }; - use crate::common::http::{Query, QueryParam}; - use crate::e2e::config::ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL; - use crate::e2e::contexts::torrent::asserts::expected_torrent; - use crate::e2e::contexts::torrent::steps::upload_random_torrent_to_index; - use crate::e2e::contexts::user::steps::new_logged_in_user; - use crate::e2e::environment::TestEnv; - - #[tokio::test] - async fn it_should_allow_guests_to_get_torrents() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if !env.provides_a_tracker() { - println!("test skipped. It requires a tracker to be running."); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let uploader = new_logged_in_user(&env).await; - let (_test_torrent, _indexed_torrent) = upload_random_torrent_to_index(&uploader, &env).await; - - let response = client.get_torrents(Query::empty()).await; - - let torrent_list_response: TorrentListResponse = serde_json::from_str(&response.body).unwrap(); - - assert!(torrent_list_response.data.total > 0); - assert!(response.is_json_and_ok()); - } - - #[tokio::test] - async fn it_should_allow_to_get_torrents_with_pagination() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if !env.provides_a_tracker() { - println!("test skipped. It requires a tracker to be running."); - return; - } - - let uploader = new_logged_in_user(&env).await; - - // Given we insert two torrents - let (_test_torrent, _indexed_torrent) = upload_random_torrent_to_index(&uploader, &env).await; - let (_test_torrent, _indexed_torrent) = upload_random_torrent_to_index(&uploader, &env).await; - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - // When we request only one torrent per page - let response = client - .get_torrents(Query::with_params([QueryParam::new("page_size", "1")].to_vec())) - .await; - - let torrent_list_response: TorrentListResponse = serde_json::from_str(&response.body).unwrap(); - - // Then we should have only one torrent per page - assert_eq!(torrent_list_response.data.results.len(), 1); - assert!(response.is_json_and_ok()); - } - - #[tokio::test] - async fn it_should_allow_to_limit_the_number_of_torrents_per_request() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if !env.provides_a_tracker() { - println!("test skipped. It requires a tracker to be running."); - return; - } - - let uploader = new_logged_in_user(&env).await; - - let max_torrent_page_size = 30; - - // Given we insert one torrent more than the page size limit - for _ in 0..max_torrent_page_size { - let (_test_torrent, _indexed_torrent) = upload_random_torrent_to_index(&uploader, &env).await; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - // When we request more torrents than the page size limit - let response = client - .get_torrents(Query::with_params( - [QueryParam::new("page_size", &format!("{}", (max_torrent_page_size + 1)))].to_vec(), - )) - .await; - - let torrent_list_response: TorrentListResponse = serde_json::from_str(&response.body).unwrap(); - - // Then we should get only the page size limit - assert_eq!(torrent_list_response.data.results.len(), max_torrent_page_size); - assert!(response.is_json_and_ok()); - } - - #[tokio::test] - async fn it_should_return_a_default_amount_of_torrents_per_request_if_no_page_size_is_provided() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if !env.provides_a_tracker() { - println!("test skipped. It requires a tracker to be running."); - return; - } - - let uploader = new_logged_in_user(&env).await; - - let default_torrent_page_size = 10; - - // Given we insert one torrent more than the default page size - for _ in 0..default_torrent_page_size { - let (_test_torrent, _indexed_torrent) = upload_random_torrent_to_index(&uploader, &env).await; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - // When we request more torrents than the default page size limit - let response = client.get_torrents(Query::empty()).await; - - let torrent_list_response: TorrentListResponse = serde_json::from_str(&response.body).unwrap(); - - // Then we should get only the default number of torrents per page - assert_eq!(torrent_list_response.data.results.len(), default_torrent_page_size); - assert!(response.is_json_and_ok()); - } - - #[tokio::test] - async fn it_should_allow_guests_to_get_torrent_details_searching_by_info_hash() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if !env.provides_a_tracker() { - println!("test skipped. It requires a tracker to be running."); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let uploader = new_logged_in_user(&env).await; - let (test_torrent, uploaded_torrent) = upload_random_torrent_to_index(&uploader, &env).await; - - let response = client.get_torrent(&test_torrent.info_hash()).await; - - let torrent_details_response: TorrentDetailsResponse = serde_json::from_str(&response.body).unwrap(); - - let tracker_url = env.server_settings().unwrap().tracker.url; - let encoded_tracker_url = urlencoding::encode(&tracker_url); - - let expected_torrent = TorrentDetails { - torrent_id: uploaded_torrent.torrent_id, - uploader: uploader.username, - info_hash: test_torrent.file_info.info_hash.to_uppercase(), - title: test_torrent.index_info.title.clone(), - description: test_torrent.index_info.description, - category: Category { - category_id: software_predefined_category_id(), - name: test_torrent.index_info.category, - num_torrents: 19, // Ignored in assertion - }, - upload_date: "2023-04-27 07:56:08".to_string(), // Ignored in assertion - file_size: test_torrent.file_info.content_size, - seeders: 0, - leechers: 0, - files: vec![File { - path: vec![test_torrent.file_info.files[0].clone()], - // Using one file torrent for testing: content_size = first file size - length: test_torrent.file_info.content_size, - md5sum: None, - }], - // code-review: why is this duplicated? It seems that is adding the - // same tracker twice because first ti adds all trackers and then - // it adds the tracker with the personal announce url, if the user - // is logged in. If the user is not logged in, it adds the default - // tracker again, and it ends up with two trackers. - trackers: vec![tracker_url.clone(), tracker_url.clone()], - magnet_link: format!( - // cspell:disable-next-line - "magnet:?xt=urn:btih:{}&dn={}&tr={}&tr={}", - test_torrent.file_info.info_hash.to_uppercase(), - urlencoding::encode(&test_torrent.index_info.title), - encoded_tracker_url, - encoded_tracker_url - ), - }; - - assert_expected_torrent_details(&torrent_details_response.data, &expected_torrent); - assert!(response.is_json_and_ok()); - } - - #[tokio::test] - async fn it_should_allow_guests_to_download_a_torrent_file_searching_by_info_hash() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if !env.provides_a_tracker() { - println!("test skipped. It requires a tracker to be running."); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let uploader = new_logged_in_user(&env).await; - let (test_torrent, _torrent_listed_in_index) = upload_random_torrent_to_index(&uploader, &env).await; - - let response = client.download_torrent(&test_torrent.info_hash()).await; - - let torrent = decode_torrent(&response.bytes).expect("could not decode downloaded torrent"); - let uploaded_torrent = - decode_torrent(&test_torrent.index_info.torrent_file.contents).expect("could not decode uploaded torrent"); - let expected_torrent = expected_torrent(uploaded_torrent, &env, &None).await; - assert_eq!(torrent, expected_torrent); - assert!(response.is_bittorrent_and_ok()); - } - - #[tokio::test] - async fn it_should_return_a_not_found_trying_to_download_a_non_existing_torrent() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if !env.provides_a_tracker() { - println!("test skipped. It requires a tracker to be running."); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let non_existing_info_hash: InfoHash = "443c7602b4fde83d1154d6d9da48808418b181b6".to_string(); - - let response = client.download_torrent(&non_existing_info_hash).await; - - // code-review: should this be 404? - assert_eq!(response.status, 400); - } - - #[tokio::test] - async fn it_should_not_allow_guests_to_delete_torrents() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if !env.provides_a_tracker() { - println!("test skipped. It requires a tracker to be running."); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let uploader = new_logged_in_user(&env).await; - let (test_torrent, _uploaded_torrent) = upload_random_torrent_to_index(&uploader, &env).await; - - let response = client.delete_torrent(&test_torrent.info_hash()).await; - - assert_eq!(response.status, 401); - } -} - -mod for_authenticated_users { - - use std::env; - - use torrust_index_backend::utils::parse_torrent::decode_torrent; - use torrust_index_backend::web::api; - - use crate::common::client::Client; - use crate::common::contexts::torrent::fixtures::random_torrent; - use crate::common::contexts::torrent::forms::UploadTorrentMultipartForm; - use crate::common::contexts::torrent::responses::UploadedTorrentResponse; - use crate::e2e::config::ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL; - use crate::e2e::contexts::torrent::asserts::{build_announce_url, get_user_tracker_key}; - use crate::e2e::contexts::torrent::steps::upload_random_torrent_to_index; - use crate::e2e::contexts::user::steps::new_logged_in_user; - use crate::e2e::environment::TestEnv; - - #[tokio::test] - async fn it_should_allow_authenticated_users_to_upload_new_torrents() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if !env.provides_a_tracker() { - println!("test skipped. It requires a tracker to be running."); - return; - } - - let uploader = new_logged_in_user(&env).await; - let client = Client::authenticated(&env.server_socket_addr().unwrap(), &uploader.token); - - let test_torrent = random_torrent(); - let info_hash = test_torrent.info_hash().clone(); - - let form: UploadTorrentMultipartForm = test_torrent.index_info.into(); - - let response = client.upload_torrent(form.into()).await; - - let uploaded_torrent_response: UploadedTorrentResponse = serde_json::from_str(&response.body).unwrap(); - - assert_eq!( - uploaded_torrent_response.data.info_hash.to_lowercase(), - info_hash.to_lowercase() - ); - assert!(response.is_json_and_ok()); - } - - #[tokio::test] - async fn it_should_not_allow_uploading_a_torrent_with_a_non_existing_category() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let uploader = new_logged_in_user(&env).await; - let client = Client::authenticated(&env.server_socket_addr().unwrap(), &uploader.token); - - let mut test_torrent = random_torrent(); - - test_torrent.index_info.category = "non-existing-category".to_string(); - - let form: UploadTorrentMultipartForm = test_torrent.index_info.into(); - - let response = client.upload_torrent(form.into()).await; - - assert_eq!(response.status, 400); - } - - #[tokio::test] - async fn it_should_not_allow_uploading_a_torrent_with_a_title_that_already_exists() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if !env.provides_a_tracker() { - println!("test skipped. It requires a tracker to be running."); - return; - } - - let uploader = new_logged_in_user(&env).await; - let client = Client::authenticated(&env.server_socket_addr().unwrap(), &uploader.token); - - // Upload the first torrent - let first_torrent = random_torrent(); - let first_torrent_title = first_torrent.index_info.title.clone(); - let form: UploadTorrentMultipartForm = first_torrent.index_info.into(); - let _response = client.upload_torrent(form.into()).await; - - // Upload the second torrent with the same title as the first one - let mut second_torrent = random_torrent(); - second_torrent.index_info.title = first_torrent_title; - let form: UploadTorrentMultipartForm = second_torrent.index_info.into(); - let response = client.upload_torrent(form.into()).await; - - assert_eq!(response.body, "{\"error\":\"This torrent title has already been used.\"}"); - assert_eq!(response.status, 400); - } - - #[tokio::test] - async fn it_should_not_allow_uploading_a_torrent_with_a_info_hash_that_already_exists() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if !env.provides_a_tracker() { - println!("test skipped. It requires a tracker to be running."); - return; - } - - let uploader = new_logged_in_user(&env).await; - let client = Client::authenticated(&env.server_socket_addr().unwrap(), &uploader.token); - - // Upload the first torrent - let first_torrent = random_torrent(); - let mut first_torrent_clone = first_torrent.clone(); - let first_torrent_title = first_torrent.index_info.title.clone(); - let form: UploadTorrentMultipartForm = first_torrent.index_info.into(); - let _response = client.upload_torrent(form.into()).await; - - // Upload the second torrent with the same info-hash as the first one. - // We need to change the title otherwise the torrent will be rejected - // because of the duplicate title. - first_torrent_clone.index_info.title = format!("{first_torrent_title}-clone"); - let form: UploadTorrentMultipartForm = first_torrent_clone.index_info.into(); - let response = client.upload_torrent(form.into()).await; - - assert_eq!(response.status, 400); - } - - #[tokio::test] - async fn it_should_allow_authenticated_users_to_download_a_torrent_with_a_personal_announce_url() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if !env.provides_a_tracker() { - println!("test skipped. It requires a tracker to be running."); - return; - } - - // Given a previously uploaded torrent - let uploader = new_logged_in_user(&env).await; - let (test_torrent, _torrent_listed_in_index) = upload_random_torrent_to_index(&uploader, &env).await; - - // And a logged in user who is going to download the torrent - let downloader = new_logged_in_user(&env).await; - let client = Client::authenticated(&env.server_socket_addr().unwrap(), &downloader.token); - - // When the user downloads the torrent - let response = client.download_torrent(&test_torrent.info_hash()).await; - - let torrent = decode_torrent(&response.bytes).expect("could not decode downloaded torrent"); - - // Then the torrent should have the personal announce URL - let tracker_key = get_user_tracker_key(&downloader, &env) - .await - .expect("uploader should have a valid tracker key"); - - let tracker_url = env.server_settings().unwrap().tracker.url; - - assert_eq!( - torrent.announce.unwrap(), - build_announce_url(&tracker_url, &Some(tracker_key)) - ); - } - - mod and_non_admins { - use std::env; - - use torrust_index_backend::web::api; - - use crate::common::client::Client; - use crate::common::contexts::torrent::forms::UpdateTorrentFrom; - use crate::e2e::config::ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL; - use crate::e2e::contexts::torrent::steps::upload_random_torrent_to_index; - use crate::e2e::contexts::user::steps::new_logged_in_user; - use crate::e2e::environment::TestEnv; - - #[tokio::test] - async fn it_should_not_allow_non_admins_to_delete_torrents() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if !env.provides_a_tracker() { - println!("test skipped. It requires a tracker to be running."); - return; - } - - let uploader = new_logged_in_user(&env).await; - let (test_torrent, _uploaded_torrent) = upload_random_torrent_to_index(&uploader, &env).await; - - let client = Client::authenticated(&env.server_socket_addr().unwrap(), &uploader.token); - - let response = client.delete_torrent(&test_torrent.info_hash()).await; - - assert_eq!(response.status, 403); - } - - #[tokio::test] - async fn it_should_allow_non_admin_users_to_update_someone_elses_torrents() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if !env.provides_a_tracker() { - println!("test skipped. It requires a tracker to be running."); - return; - } - - // Given a users uploads a torrent - let uploader = new_logged_in_user(&env).await; - let (test_torrent, _uploaded_torrent) = upload_random_torrent_to_index(&uploader, &env).await; - - // Then another non admin user should not be able to update the torrent - let not_the_uploader = new_logged_in_user(&env).await; - let client = Client::authenticated(&env.server_socket_addr().unwrap(), ¬_the_uploader.token); - - let new_title = format!("{}-new-title", test_torrent.index_info.title); - let new_description = format!("{}-new-description", test_torrent.index_info.description); - - let response = client - .update_torrent( - &test_torrent.info_hash(), - UpdateTorrentFrom { - title: Some(new_title.clone()), - description: Some(new_description.clone()), - }, - ) - .await; - - assert_eq!(response.status, 403); - } - } - - mod and_torrent_owners { - use std::env; - - use torrust_index_backend::web::api; - - use crate::common::client::Client; - use crate::common::contexts::torrent::forms::UpdateTorrentFrom; - use crate::common::contexts::torrent::responses::UpdatedTorrentResponse; - use crate::e2e::config::ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL; - use crate::e2e::contexts::torrent::steps::upload_random_torrent_to_index; - use crate::e2e::contexts::user::steps::new_logged_in_user; - use crate::e2e::environment::TestEnv; - - #[tokio::test] - async fn it_should_allow_torrent_owners_to_update_their_torrents() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if !env.provides_a_tracker() { - println!("test skipped. It requires a tracker to be running."); - return; - } - - let uploader = new_logged_in_user(&env).await; - let (test_torrent, _uploaded_torrent) = upload_random_torrent_to_index(&uploader, &env).await; - - let client = Client::authenticated(&env.server_socket_addr().unwrap(), &uploader.token); - - let new_title = format!("{}-new-title", test_torrent.index_info.title); - let new_description = format!("{}-new-description", test_torrent.index_info.description); - - let response = client - .update_torrent( - &test_torrent.info_hash(), - UpdateTorrentFrom { - title: Some(new_title.clone()), - description: Some(new_description.clone()), - }, - ) - .await; - - let updated_torrent_response: UpdatedTorrentResponse = serde_json::from_str(&response.body).unwrap(); - - let torrent = updated_torrent_response.data; - - assert_eq!(torrent.title, new_title); - assert_eq!(torrent.description, new_description); - assert!(response.is_json_and_ok()); - } - } - - mod and_admins { - use std::env; - - use torrust_index_backend::web::api; - - use crate::common::client::Client; - use crate::common::contexts::torrent::forms::UpdateTorrentFrom; - use crate::common::contexts::torrent::responses::{DeletedTorrentResponse, UpdatedTorrentResponse}; - use crate::e2e::config::ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL; - use crate::e2e::contexts::torrent::steps::upload_random_torrent_to_index; - use crate::e2e::contexts::user::steps::{new_logged_in_admin, new_logged_in_user}; - use crate::e2e::environment::TestEnv; - - #[tokio::test] - async fn it_should_allow_admins_to_delete_torrents_searching_by_info_hash() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if !env.provides_a_tracker() { - println!("test skipped. It requires a tracker to be running."); - return; - } - - let uploader = new_logged_in_user(&env).await; - let (test_torrent, uploaded_torrent) = upload_random_torrent_to_index(&uploader, &env).await; - - let admin = new_logged_in_admin(&env).await; - let client = Client::authenticated(&env.server_socket_addr().unwrap(), &admin.token); - - let response = client.delete_torrent(&test_torrent.info_hash()).await; - - let deleted_torrent_response: DeletedTorrentResponse = serde_json::from_str(&response.body).unwrap(); - - assert_eq!(deleted_torrent_response.data.torrent_id, uploaded_torrent.torrent_id); - assert!(response.is_json_and_ok()); - } - - #[tokio::test] - async fn it_should_allow_admins_to_update_someone_elses_torrents() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - if !env.provides_a_tracker() { - println!("test skipped. It requires a tracker to be running."); - return; - } - - let uploader = new_logged_in_user(&env).await; - let (test_torrent, _uploaded_torrent) = upload_random_torrent_to_index(&uploader, &env).await; - - let logged_in_admin = new_logged_in_admin(&env).await; - let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_in_admin.token); - - let new_title = format!("{}-new-title", test_torrent.index_info.title); - let new_description = format!("{}-new-description", test_torrent.index_info.description); - - let response = client - .update_torrent( - &test_torrent.info_hash(), - UpdateTorrentFrom { - title: Some(new_title.clone()), - description: Some(new_description.clone()), - }, - ) - .await; - - let updated_torrent_response: UpdatedTorrentResponse = serde_json::from_str(&response.body).unwrap(); - - let torrent = updated_torrent_response.data; - - assert_eq!(torrent.title, new_title); - assert_eq!(torrent.description, new_description); - assert!(response.is_json_and_ok()); - } - } -} - mod with_axum_implementation { mod for_guests { diff --git a/tests/e2e/contexts/user/contract.rs b/tests/e2e/contexts/user/contract.rs index 17732da9..e1e66d56 100644 --- a/tests/e2e/contexts/user/contract.rs +++ b/tests/e2e/contexts/user/contract.rs @@ -1,17 +1,4 @@ //! API contract for `user` context. -use std::env; - -use torrust_index_backend::web::api; - -use crate::common::client::Client; -use crate::common::contexts::user::fixtures::random_user_registration_form; -use crate::common::contexts::user::forms::{LoginForm, TokenRenewalForm, TokenVerificationForm}; -use crate::common::contexts::user::responses::{ - SuccessfulLoginResponse, TokenRenewalData, TokenRenewalResponse, TokenVerifiedResponse, -}; -use crate::e2e::config::ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL; -use crate::e2e::contexts::user::steps::{new_logged_in_user, new_registered_user}; -use crate::e2e::environment::TestEnv; /* @@ -39,204 +26,6 @@ the mailcatcher API. */ -// Responses data - -#[tokio::test] -async fn it_should_allow_a_guest_user_to_register() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let form = random_user_registration_form(); - - let response = client.register_user(form).await; - - assert_eq!(response.body, "", "wrong response body, it should be an empty string"); - if let Some(content_type) = &response.content_type { - assert_eq!(content_type, "text/plain; charset=utf-8"); - } - assert_eq!(response.status, 200); -} - -#[tokio::test] -async fn it_should_allow_a_registered_user_to_login() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let registered_user = new_registered_user(&env).await; - - let response = client - .login_user(LoginForm { - login: registered_user.username.clone(), - password: registered_user.password.clone(), - }) - .await; - - let res: SuccessfulLoginResponse = serde_json::from_str(&response.body).unwrap(); - let logged_in_user = res.data; - - assert_eq!(logged_in_user.username, registered_user.username); - if let Some(content_type) = &response.content_type { - assert_eq!(content_type, "application/json"); - } - assert_eq!(response.status, 200); -} - -#[tokio::test] -async fn it_should_allow_a_logged_in_user_to_verify_an_authentication_token() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let logged_in_user = new_logged_in_user(&env).await; - - let response = client - .verify_token(TokenVerificationForm { - token: logged_in_user.token.clone(), - }) - .await; - - let res: TokenVerifiedResponse = serde_json::from_str(&response.body).unwrap(); - - assert_eq!(res.data, "Token is valid."); - if let Some(content_type) = &response.content_type { - assert_eq!(content_type, "application/json"); - } - assert_eq!(response.status, 200); -} - -#[tokio::test] -async fn it_should_not_allow_a_logged_in_user_to_renew_an_authentication_token_which_is_still_valid_for_more_than_one_week() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let logged_in_user = new_logged_in_user(&env).await; - let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_in_user.token); - - let response = client - .renew_token(TokenRenewalForm { - token: logged_in_user.token.clone(), - }) - .await; - - let res: TokenRenewalResponse = serde_json::from_str(&response.body).unwrap(); - - assert_eq!( - res.data, - TokenRenewalData { - token: logged_in_user.token.clone(), // The same token is returned - username: logged_in_user.username.clone(), - admin: logged_in_user.admin, - } - ); - if let Some(content_type) = &response.content_type { - assert_eq!(content_type, "application/json"); - } - assert_eq!(response.status, 200); -} - -mod banned_user_list { - use std::env; - - use torrust_index_backend::web::api; - - use crate::common::client::Client; - use crate::common::contexts::user::forms::Username; - use crate::common::contexts::user::responses::BannedUserResponse; - use crate::e2e::config::ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL; - use crate::e2e::contexts::user::steps::{new_logged_in_admin, new_logged_in_user, new_registered_user}; - use crate::e2e::environment::TestEnv; - - #[tokio::test] - async fn it_should_allow_an_admin_to_ban_a_user() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_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 registered_user = new_registered_user(&env).await; - - let response = client.ban_user(Username::new(registered_user.username.clone())).await; - - let res: BannedUserResponse = serde_json::from_str(&response.body).unwrap(); - - assert_eq!(res.data, format!("Banned user: {}", registered_user.username)); - if let Some(content_type) = &response.content_type { - assert_eq!(content_type, "application/json"); - } - assert_eq!(response.status, 200); - } - - #[tokio::test] - async fn it_should_not_allow_a_non_admin_to_ban_a_user() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let logged_non_admin = new_logged_in_user(&env).await; - let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_non_admin.token); - - let registered_user = new_registered_user(&env).await; - - let response = client.ban_user(Username::new(registered_user.username.clone())).await; - - assert_eq!(response.status, 403); - } - - #[tokio::test] - async fn it_should_not_allow_a_guest_to_ban_a_user() { - let mut env = TestEnv::new(); - env.start(api::Implementation::ActixWeb).await; - - if env::var(ENV_VAR_E2E_EXCLUDE_ACTIX_WEB_IMPL).is_ok() { - println!("Skipped"); - return; - } - - let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - - let registered_user = new_registered_user(&env).await; - - let response = client.ban_user(Username::new(registered_user.username.clone())).await; - - assert_eq!(response.status, 401); - } -} - mod with_axum_implementation { mod registration { diff --git a/tests/environments/app_starter.rs b/tests/environments/app_starter.rs index a08f3592..47aef6e2 100644 --- a/tests/environments/app_starter.rs +++ b/tests/environments/app_starter.rs @@ -54,7 +54,6 @@ impl AppStarter { .expect("the app starter should not be dropped"); match api_implementation { - Implementation::ActixWeb => app.actix_web_api_server.unwrap().await, Implementation::Axum => app.axum_api_server.unwrap().await, } });