From 79682a52085e2cc20dffc738a9afae03229106b5 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 13 Jun 2023 16:51:12 +0100 Subject: [PATCH] refactor(api): [#183] Axum API, user context, registration --- .github/workflows/develop.yml | 2 + src/config.rs | 6 + src/errors.rs | 128 ++++++++++++---------- src/routes/user.rs | 13 +-- src/services/user.rs | 2 +- src/web/api/v1/contexts/about/routes.rs | 16 +-- src/web/api/v1/contexts/user/forms.rs | 9 ++ src/web/api/v1/contexts/user/handlers.rs | 46 ++++++++ src/web/api/v1/contexts/user/mod.rs | 4 + src/web/api/v1/contexts/user/responses.rs | 17 +++ src/web/api/v1/contexts/user/routes.rs | 15 +++ src/web/api/v1/mod.rs | 1 + src/web/api/v1/responses.rs | 25 +++++ src/web/api/v1/routes.rs | 14 +-- tests/common/contexts/user/asserts.rs | 9 ++ tests/common/contexts/user/fixtures.rs | 2 +- tests/common/contexts/user/mod.rs | 1 + tests/common/contexts/user/responses.rs | 10 ++ tests/e2e/config.rs | 3 + tests/e2e/contexts/user/contract.rs | 34 +++++- tests/e2e/contexts/user/steps.rs | 4 +- 21 files changed, 266 insertions(+), 95 deletions(-) create mode 100644 src/web/api/v1/contexts/user/forms.rs create mode 100644 src/web/api/v1/contexts/user/handlers.rs create mode 100644 src/web/api/v1/contexts/user/responses.rs create mode 100644 src/web/api/v1/contexts/user/routes.rs create mode 100644 src/web/api/v1/responses.rs create mode 100644 tests/common/contexts/user/asserts.rs diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 2ee931b2..b0a958d3 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -31,3 +31,5 @@ jobs: run: cargo llvm-cov nextest - name: E2E Tests run: ./docker/bin/run-e2e-tests.sh + env: + TORRUST_IDX_BACK_E2E_EXCLUDE_AXUM_IMPL: "true" diff --git a/src/config.rs b/src/config.rs index 0db50ea9..3901d987 100644 --- a/src/config.rs +++ b/src/config.rs @@ -422,6 +422,12 @@ impl Configuration { settings_lock.website.name.clone() } + + pub async fn get_api_base_url(&self) -> Option { + let settings_lock = self.settings.read().await; + + settings_lock.net.base_url.clone() + } } /// The public backend configuration. diff --git a/src/errors.rs b/src/errors.rs index 668bf3ab..6f880162 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -146,49 +146,7 @@ pub struct ErrorToResponse { impl ResponseError for ServiceError { fn status_code(&self) -> StatusCode { - #[allow(clippy::match_same_arms)] - match self { - ServiceError::ClosedForRegistration => StatusCode::FORBIDDEN, - ServiceError::EmailInvalid => StatusCode::BAD_REQUEST, - ServiceError::NotAUrl => StatusCode::BAD_REQUEST, - ServiceError::WrongPasswordOrUsername => StatusCode::FORBIDDEN, - ServiceError::UsernameNotFound => StatusCode::NOT_FOUND, - ServiceError::UserNotFound => StatusCode::NOT_FOUND, - ServiceError::AccountNotFound => StatusCode::NOT_FOUND, - ServiceError::ProfanityError => StatusCode::BAD_REQUEST, - ServiceError::BlacklistError => StatusCode::BAD_REQUEST, - ServiceError::UsernameCaseMappedError => StatusCode::BAD_REQUEST, - ServiceError::PasswordTooShort => StatusCode::BAD_REQUEST, - ServiceError::PasswordTooLong => StatusCode::BAD_REQUEST, - ServiceError::PasswordsDontMatch => StatusCode::BAD_REQUEST, - ServiceError::UsernameTaken => StatusCode::BAD_REQUEST, - ServiceError::UsernameInvalid => StatusCode::BAD_REQUEST, - ServiceError::EmailTaken => StatusCode::BAD_REQUEST, - ServiceError::EmailNotVerified => StatusCode::FORBIDDEN, - ServiceError::TokenNotFound => StatusCode::UNAUTHORIZED, - ServiceError::TokenExpired => StatusCode::UNAUTHORIZED, - ServiceError::TokenInvalid => StatusCode::UNAUTHORIZED, - ServiceError::TorrentNotFound => StatusCode::BAD_REQUEST, - ServiceError::InvalidTorrentFile => StatusCode::BAD_REQUEST, - ServiceError::InvalidTorrentPiecesLength => StatusCode::BAD_REQUEST, - ServiceError::InvalidFileType => StatusCode::BAD_REQUEST, - ServiceError::BadRequest => StatusCode::BAD_REQUEST, - ServiceError::InvalidCategory => StatusCode::BAD_REQUEST, - ServiceError::InvalidTag => StatusCode::BAD_REQUEST, - ServiceError::Unauthorized => StatusCode::FORBIDDEN, - ServiceError::InfoHashAlreadyExists => StatusCode::BAD_REQUEST, - ServiceError::TorrentTitleAlreadyExists => StatusCode::BAD_REQUEST, - ServiceError::TrackerOffline => StatusCode::INTERNAL_SERVER_ERROR, - ServiceError::CategoryAlreadyExists => StatusCode::BAD_REQUEST, - ServiceError::TagAlreadyExists => StatusCode::BAD_REQUEST, - ServiceError::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR, - ServiceError::EmailMissing => StatusCode::NOT_FOUND, - ServiceError::FailedToSendVerificationEmail => StatusCode::INTERNAL_SERVER_ERROR, - ServiceError::WhitelistingError => StatusCode::INTERNAL_SERVER_ERROR, - ServiceError::DatabaseError => StatusCode::INTERNAL_SERVER_ERROR, - ServiceError::CategoryNotFound => StatusCode::NOT_FOUND, - ServiceError::TagNotFound => StatusCode::NOT_FOUND, - } + http_status_code_for_service_error(self) } fn error_response(&self) -> HttpResponse { @@ -220,22 +178,7 @@ impl From for ServiceError { impl From for ServiceError { fn from(e: database::Error) -> Self { - #[allow(clippy::match_same_arms)] - match e { - database::Error::Error => ServiceError::InternalServerError, - database::Error::ErrorWithText(_) => ServiceError::InternalServerError, - database::Error::UsernameTaken => ServiceError::UsernameTaken, - database::Error::EmailTaken => ServiceError::EmailTaken, - database::Error::UserNotFound => ServiceError::UserNotFound, - database::Error::CategoryAlreadyExists => ServiceError::CategoryAlreadyExists, - database::Error::CategoryNotFound => ServiceError::InvalidCategory, - database::Error::TagAlreadyExists => ServiceError::TagAlreadyExists, - database::Error::TagNotFound => ServiceError::InvalidTag, - database::Error::TorrentNotFound => ServiceError::TorrentNotFound, - database::Error::TorrentAlreadyExists => ServiceError::InfoHashAlreadyExists, - database::Error::TorrentTitleAlreadyExists => ServiceError::TorrentTitleAlreadyExists, - database::Error::UnrecognizedDatabaseDriver => ServiceError::InternalServerError, - } + map_database_error_to_service_error(&e) } } @@ -266,3 +209,70 @@ impl From for ServiceError { ServiceError::InternalServerError } } + +#[must_use] +pub fn http_status_code_for_service_error(error: &ServiceError) -> StatusCode { + #[allow(clippy::match_same_arms)] + match error { + ServiceError::ClosedForRegistration => StatusCode::FORBIDDEN, + ServiceError::EmailInvalid => StatusCode::BAD_REQUEST, + ServiceError::NotAUrl => StatusCode::BAD_REQUEST, + ServiceError::WrongPasswordOrUsername => StatusCode::FORBIDDEN, + ServiceError::UsernameNotFound => StatusCode::NOT_FOUND, + ServiceError::UserNotFound => StatusCode::NOT_FOUND, + ServiceError::AccountNotFound => StatusCode::NOT_FOUND, + ServiceError::ProfanityError => StatusCode::BAD_REQUEST, + ServiceError::BlacklistError => StatusCode::BAD_REQUEST, + ServiceError::UsernameCaseMappedError => StatusCode::BAD_REQUEST, + ServiceError::PasswordTooShort => StatusCode::BAD_REQUEST, + ServiceError::PasswordTooLong => StatusCode::BAD_REQUEST, + ServiceError::PasswordsDontMatch => StatusCode::BAD_REQUEST, + ServiceError::UsernameTaken => StatusCode::BAD_REQUEST, + ServiceError::UsernameInvalid => StatusCode::BAD_REQUEST, + ServiceError::EmailTaken => StatusCode::BAD_REQUEST, + ServiceError::EmailNotVerified => StatusCode::FORBIDDEN, + ServiceError::TokenNotFound => StatusCode::UNAUTHORIZED, + ServiceError::TokenExpired => StatusCode::UNAUTHORIZED, + ServiceError::TokenInvalid => StatusCode::UNAUTHORIZED, + ServiceError::TorrentNotFound => StatusCode::BAD_REQUEST, + ServiceError::InvalidTorrentFile => StatusCode::BAD_REQUEST, + ServiceError::InvalidTorrentPiecesLength => StatusCode::BAD_REQUEST, + ServiceError::InvalidFileType => StatusCode::BAD_REQUEST, + ServiceError::BadRequest => StatusCode::BAD_REQUEST, + ServiceError::InvalidCategory => StatusCode::BAD_REQUEST, + ServiceError::InvalidTag => StatusCode::BAD_REQUEST, + ServiceError::Unauthorized => StatusCode::FORBIDDEN, + ServiceError::InfoHashAlreadyExists => StatusCode::BAD_REQUEST, + ServiceError::TorrentTitleAlreadyExists => StatusCode::BAD_REQUEST, + ServiceError::TrackerOffline => StatusCode::INTERNAL_SERVER_ERROR, + ServiceError::CategoryAlreadyExists => StatusCode::BAD_REQUEST, + ServiceError::TagAlreadyExists => StatusCode::BAD_REQUEST, + ServiceError::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR, + ServiceError::EmailMissing => StatusCode::NOT_FOUND, + ServiceError::FailedToSendVerificationEmail => StatusCode::INTERNAL_SERVER_ERROR, + ServiceError::WhitelistingError => StatusCode::INTERNAL_SERVER_ERROR, + ServiceError::DatabaseError => StatusCode::INTERNAL_SERVER_ERROR, + ServiceError::CategoryNotFound => StatusCode::NOT_FOUND, + ServiceError::TagNotFound => StatusCode::NOT_FOUND, + } +} + +#[must_use] +pub fn map_database_error_to_service_error(error: &database::Error) -> ServiceError { + #[allow(clippy::match_same_arms)] + match error { + database::Error::Error => ServiceError::InternalServerError, + database::Error::ErrorWithText(_) => ServiceError::InternalServerError, + database::Error::UsernameTaken => ServiceError::UsernameTaken, + database::Error::EmailTaken => ServiceError::EmailTaken, + database::Error::UserNotFound => ServiceError::UserNotFound, + database::Error::CategoryAlreadyExists => ServiceError::CategoryAlreadyExists, + database::Error::CategoryNotFound => ServiceError::InvalidCategory, + database::Error::TagAlreadyExists => ServiceError::TagAlreadyExists, + database::Error::TagNotFound => ServiceError::InvalidTag, + database::Error::TorrentNotFound => ServiceError::TorrentNotFound, + database::Error::TorrentAlreadyExists => ServiceError::InfoHashAlreadyExists, + database::Error::TorrentTitleAlreadyExists => ServiceError::TorrentTitleAlreadyExists, + database::Error::UnrecognizedDatabaseDriver => ServiceError::InternalServerError, + } +} diff --git a/src/routes/user.rs b/src/routes/user.rs index 5912334a..40030754 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -5,6 +5,7 @@ use crate::common::WebAppData; use crate::errors::{ServiceError, ServiceResult}; use crate::models::response::{OkResponse, TokenResponse}; use crate::routes::API_VERSION; +use crate::web::api::v1::contexts::user::forms::RegistrationForm; pub fn init(cfg: &mut web::ServiceConfig) { cfg.service( @@ -26,14 +27,6 @@ pub fn init(cfg: &mut web::ServiceConfig) { ); } -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct RegistrationForm { - pub username: String, - pub email: Option, - pub password: String, - pub confirm_password: String, -} - #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Login { pub login: String, @@ -56,8 +49,8 @@ pub async fn registration_handler( app_data: WebAppData, ) -> ServiceResult { let conn_info = req.connection_info().clone(); - // todo: we should add this in the configuration. It does not work is the - // server is behind a reverse proxy. + // todo: check if `base_url` option was define in settings `net->base_url`. + // It should have priority over request headers. let api_base_url = format!("{}://{}", conn_info.scheme(), conn_info.host()); let _user_id = app_data diff --git a/src/services/user.rs b/src/services/user.rs index 10a42b60..a0211546 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -13,8 +13,8 @@ use crate::errors::ServiceError; use crate::mailer; use crate::mailer::VerifyClaims; use crate::models::user::{UserCompact, UserId, UserProfile}; -use crate::routes::user::RegistrationForm; use crate::utils::regex::validate_email_address; +use crate::web::api::v1::contexts::user::forms::RegistrationForm; /// Since user email could be optional, we need a way to represent "no email" /// in the database. This function returns the string that should be used for diff --git a/src/web/api/v1/contexts/about/routes.rs b/src/web/api/v1/contexts/about/routes.rs index da53052c..d3877a3b 100644 --- a/src/web/api/v1/contexts/about/routes.rs +++ b/src/web/api/v1/contexts/about/routes.rs @@ -9,15 +9,9 @@ use axum::Router; use super::handlers::{about_page_handler, license_page_handler}; use crate::common::AppData; -/// It adds the routes to the router for the [`about`](crate::web::api::v1::contexts::about) API context. -pub fn add(prefix: &str, router: Router, app_data: Arc) -> Router { - router - .route( - &format!("{prefix}/about"), - get(about_page_handler).with_state(app_data.clone()), - ) - .route( - &format!("{prefix}/about/license"), - get(license_page_handler).with_state(app_data), - ) +/// Routes for the [`about`](crate::web::api::v1::contexts::about) API context. +pub fn router(app_data: Arc) -> Router { + Router::new() + .route("/", get(about_page_handler).with_state(app_data.clone())) + .route("/license", get(license_page_handler).with_state(app_data)) } diff --git a/src/web/api/v1/contexts/user/forms.rs b/src/web/api/v1/contexts/user/forms.rs new file mode 100644 index 00000000..78a92f06 --- /dev/null +++ b/src/web/api/v1/contexts/user/forms.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct RegistrationForm { + pub username: String, + pub email: Option, + pub password: String, + pub confirm_password: String, +} diff --git a/src/web/api/v1/contexts/user/handlers.rs b/src/web/api/v1/contexts/user/handlers.rs new file mode 100644 index 00000000..9fccff14 --- /dev/null +++ b/src/web/api/v1/contexts/user/handlers.rs @@ -0,0 +1,46 @@ +//! API handlers for the the [`user`](crate::web::api::v1::contexts::user) API +//! context. +use std::sync::Arc; + +use axum::extract::{self, Host, State}; +use axum::Json; + +use super::forms::RegistrationForm; +use super::responses::{self, NewUser}; +use crate::common::AppData; +use crate::errors::ServiceError; +use crate::web::api::v1::responses::OkResponse; + +/// It handles the registration of a new user. +/// +/// # Errors +/// +/// It returns an error if the user could not be registered. +#[allow(clippy::unused_async)] +pub async fn registration_handler( + State(app_data): State>, + Host(host_from_header): Host, + extract::Json(registration_form): extract::Json, +) -> Result>, ServiceError> { + let api_base_url = app_data + .cfg + .get_api_base_url() + .await + .unwrap_or(api_base_url(&host_from_header)); + + match app_data + .registration_service + .register_user(®istration_form, &api_base_url) + .await + { + Ok(user_id) => Ok(responses::added_user(user_id)), + Err(error) => Err(error), + } +} + +/// It returns the base API URL without the port. For example: `http://localhost`. +fn api_base_url(host: &str) -> String { + // HTTPS is not supported yet. + // See https://github.com/torrust/torrust-index-backend/issues/131 + format!("http://{host}") +} diff --git a/src/web/api/v1/contexts/user/mod.rs b/src/web/api/v1/contexts/user/mod.rs index c7974a9c..3a4267c0 100644 --- a/src/web/api/v1/contexts/user/mod.rs +++ b/src/web/api/v1/contexts/user/mod.rs @@ -243,3 +243,7 @@ //! **WARNING**: The admin can ban themselves. If they do, they will not be able //! to unban themselves. The only way to unban themselves is to manually remove //! the user from the banned user list in the database. +pub mod forms; +pub mod handlers; +pub mod responses; +pub mod routes; diff --git a/src/web/api/v1/contexts/user/responses.rs b/src/web/api/v1/contexts/user/responses.rs new file mode 100644 index 00000000..79f5b14f --- /dev/null +++ b/src/web/api/v1/contexts/user/responses.rs @@ -0,0 +1,17 @@ +use axum::Json; +use serde::{Deserialize, Serialize}; + +use crate::models::user::UserId; +use crate::web::api::v1::responses::OkResponse; + +#[derive(Serialize, Deserialize, Debug)] +pub struct NewUser { + pub user_id: UserId, +} + +/// Response after successfully creating a new user. +pub fn added_user(user_id: i64) -> Json> { + Json(OkResponse { + data: NewUser { user_id }, + }) +} diff --git a/src/web/api/v1/contexts/user/routes.rs b/src/web/api/v1/contexts/user/routes.rs new file mode 100644 index 00000000..a517f1d2 --- /dev/null +++ b/src/web/api/v1/contexts/user/routes.rs @@ -0,0 +1,15 @@ +//! API routes for the [`user`](crate::web::api::v1::contexts::user) API context. +//! +//! Refer to the [API endpoint documentation](crate::web::api::v1::contexts::user). +use std::sync::Arc; + +use axum::routing::post; +use axum::Router; + +use super::handlers::registration_handler; +use crate::common::AppData; + +/// Routes for the [`user`](crate::web::api::v1::contexts::user) API context. +pub fn router(app_data: Arc) -> Router { + Router::new().route("/register", post(registration_handler).with_state(app_data)) +} diff --git a/src/web/api/v1/mod.rs b/src/web/api/v1/mod.rs index 9d94e076..67490e6e 100644 --- a/src/web/api/v1/mod.rs +++ b/src/web/api/v1/mod.rs @@ -6,4 +6,5 @@ //! information. pub mod auth; pub mod contexts; +pub mod responses; pub mod routes; diff --git a/src/web/api/v1/responses.rs b/src/web/api/v1/responses.rs new file mode 100644 index 00000000..de9701c0 --- /dev/null +++ b/src/web/api/v1/responses.rs @@ -0,0 +1,25 @@ +//! Generic responses for the API. +use axum::response::{IntoResponse, Response}; +use serde::{Deserialize, Serialize}; + +use crate::databases::database; +use crate::errors::{http_status_code_for_service_error, map_database_error_to_service_error, ServiceError}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct OkResponse { + pub data: T, +} + +impl IntoResponse for database::Error { + fn into_response(self) -> Response { + let service_error = map_database_error_to_service_error(&self); + + (http_status_code_for_service_error(&service_error), service_error.to_string()).into_response() + } +} + +impl IntoResponse for ServiceError { + fn into_response(self) -> Response { + (http_status_code_for_service_error(&self), self.to_string()).into_response() + } +} diff --git a/src/web/api/v1/routes.rs b/src/web/api/v1/routes.rs index c980bd1e..1787e3bb 100644 --- a/src/web/api/v1/routes.rs +++ b/src/web/api/v1/routes.rs @@ -3,20 +3,16 @@ use std::sync::Arc; use axum::Router; -use super::contexts::about; +use super::contexts::{about, user}; use crate::common::AppData; /// Add all API routes to the router. #[allow(clippy::needless_pass_by_value)] pub fn router(app_data: Arc) -> Router { - let router = Router::new(); + let user_routes = user::routes::router(app_data.clone()); + let about_routes = about::routes::router(app_data); - add(router, app_data) -} - -/// Add the routes for the v1 API. -fn add(router: Router, app_data: Arc) -> Router { - let v1_prefix = "/v1".to_string(); + let api_routes = Router::new().nest("/user", user_routes).nest("/about", about_routes); - about::routes::add(&v1_prefix, router, app_data) + Router::new().nest("/v1", api_routes) } diff --git a/tests/common/contexts/user/asserts.rs b/tests/common/contexts/user/asserts.rs new file mode 100644 index 00000000..c366a577 --- /dev/null +++ b/tests/common/contexts/user/asserts.rs @@ -0,0 +1,9 @@ +use crate::common::asserts::assert_json_ok; +use crate::common::contexts::user::responses::AddedUserResponse; +use crate::common::responses::TextResponse; + +pub fn assert_added_user_response(response: &TextResponse) { + let _added_user_response: AddedUserResponse = serde_json::from_str(&response.body) + .unwrap_or_else(|_| panic!("response {:#?} should be a AddedUserResponse", response.body)); + assert_json_ok(response); +} diff --git a/tests/common/contexts/user/fixtures.rs b/tests/common/contexts/user/fixtures.rs index 3eda8502..fea39e7f 100644 --- a/tests/common/contexts/user/fixtures.rs +++ b/tests/common/contexts/user/fixtures.rs @@ -2,7 +2,7 @@ use rand::Rng; use crate::common::contexts::user::forms::RegistrationForm; -pub fn random_user_registration() -> RegistrationForm { +pub fn random_user_registration_form() -> RegistrationForm { let user_id = random_user_id(); RegistrationForm { username: format!("username_{user_id}"), diff --git a/tests/common/contexts/user/mod.rs b/tests/common/contexts/user/mod.rs index 6f27f51d..cfe5dd24 100644 --- a/tests/common/contexts/user/mod.rs +++ b/tests/common/contexts/user/mod.rs @@ -1,3 +1,4 @@ +pub mod asserts; pub mod fixtures; pub mod forms; pub mod responses; diff --git a/tests/common/contexts/user/responses.rs b/tests/common/contexts/user/responses.rs index 8f6e84b2..1a9a3837 100644 --- a/tests/common/contexts/user/responses.rs +++ b/tests/common/contexts/user/responses.rs @@ -1,5 +1,15 @@ use serde::Deserialize; +#[derive(Deserialize, Debug)] +pub struct AddedUserResponse { + pub data: NewUserData, +} + +#[derive(Deserialize, Debug)] +pub struct NewUserData { + pub user_id: i64, +} + #[derive(Deserialize, Debug)] pub struct SuccessfulLoginResponse { pub data: LoggedInUserData, diff --git a/tests/e2e/config.rs b/tests/e2e/config.rs index f3179f43..abf056fd 100644 --- a/tests/e2e/config.rs +++ b/tests/e2e/config.rs @@ -14,6 +14,9 @@ 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 Axum implementation will not be executed +pub const ENV_VAR_E2E_EXCLUDE_AXUM_IMPL: &str = "TORRUST_IDX_BACK_E2E_EXCLUDE_AXUM_IMPL"; + // Default values pub const ENV_VAR_E2E_DEFAULT_CONFIG_PATH: &str = "./config-idx-back.local.toml"; diff --git a/tests/e2e/contexts/user/contract.rs b/tests/e2e/contexts/user/contract.rs index 24abc0de..c436d2c3 100644 --- a/tests/e2e/contexts/user/contract.rs +++ b/tests/e2e/contexts/user/contract.rs @@ -2,7 +2,7 @@ use torrust_index_backend::web::api; use crate::common::client::Client; -use crate::common::contexts::user::fixtures::random_user_registration; +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, @@ -44,7 +44,7 @@ async fn it_should_allow_a_guest_user_to_register() { env.start(api::Implementation::ActixWeb).await; let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - let form = random_user_registration(); + let form = random_user_registration_form(); let response = client.register_user(form).await; @@ -191,3 +191,33 @@ mod banned_user_list { assert_eq!(response.status, 401); } } + +mod with_axum_implementation { + use std::env; + + use torrust_index_backend::web::api; + + use crate::common::client::Client; + use crate::common::contexts::user::asserts::assert_added_user_response; + use crate::common::contexts::user::fixtures::random_user_registration_form; + use crate::e2e::config::ENV_VAR_E2E_EXCLUDE_AXUM_IMPL; + use crate::e2e::environment::TestEnv; + + #[tokio::test] + async fn it_should_allow_a_guest_user_to_register() { + let mut env = TestEnv::new(); + env.start(api::Implementation::Axum).await; + + if env::var(ENV_VAR_E2E_EXCLUDE_AXUM_IMPL).is_ok() { + return; + } + + let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); + + let form = random_user_registration_form(); + + let response = client.register_user(form).await; + + assert_added_user_response(&response); + } +} diff --git a/tests/e2e/contexts/user/steps.rs b/tests/e2e/contexts/user/steps.rs index c58a5c59..f4892325 100644 --- a/tests/e2e/contexts/user/steps.rs +++ b/tests/e2e/contexts/user/steps.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use torrust_index_backend::databases::database; use crate::common::client::Client; -use crate::common::contexts::user::fixtures::random_user_registration; +use crate::common::contexts::user::fixtures::random_user_registration_form; use crate::common::contexts::user::forms::{LoginForm, RegisteredUser}; use crate::common::contexts::user::responses::{LoggedInUserData, SuccessfulLoginResponse}; use crate::e2e::environment::TestEnv; @@ -64,7 +64,7 @@ pub async fn new_logged_in_user(env: &TestEnv) -> LoggedInUserData { pub async fn new_registered_user(env: &TestEnv) -> RegisteredUser { let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); - let form = random_user_registration(); + let form = random_user_registration_form(); let registered_user = form.clone();