From 87edb36bfb73bffd1b18fcff30323322c28ffaf3 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 4 May 2023 16:02:33 +0100 Subject: [PATCH 1/2] test: [#130] do not update settings for shared test evns The test `it_should_allow_admins_to_update_all_the_settings` overwrites the config file `config.toml`. It uses the same values but: 1. That does not assert that the file was actually updated. 2. It can lead to conflicts with other tests since all of them use the same shared env. For isolated environments, it should not be a problem becuase we inject the configuration into the app directly wuthout getting it from the environment via confif file or env var. --- tests/common/client.rs | 7 +- tests/common/contexts/settings/form.rs | 2 +- tests/e2e/contexts/settings/contract.rs | 107 +++--------------------- tests/e2e/environment.rs | 4 + 4 files changed, 23 insertions(+), 97 deletions(-) diff --git a/tests/common/client.rs b/tests/common/client.rs index ed899741..d0f84991 100644 --- a/tests/common/client.rs +++ b/tests/common/client.rs @@ -3,7 +3,7 @@ use serde::Serialize; use super::connection_info::ConnectionInfo; use super::contexts::category::forms::{AddCategoryForm, DeleteCategoryForm}; -use super::contexts::settings::form::UpdateSettingsForm; +use super::contexts::settings::form::UpdateSettings; use super::contexts::torrent::forms::UpdateTorrentFrom; use super::contexts::torrent::requests::TorrentId; use super::contexts::user::forms::{LoginForm, RegistrationForm, TokenRenewalForm, TokenVerificationForm, Username}; @@ -16,6 +16,9 @@ pub struct Client { } impl Client { + // todo: forms in POST requests can be passed by reference. It's already + // changed for the `update_settings` method. + pub fn unauthenticated(bind_address: &str) -> Self { Self::new(ConnectionInfo::anonymous(bind_address)) } @@ -80,7 +83,7 @@ impl Client { self.http_client.get("settings", Query::empty()).await } - pub async fn update_settings(&self, update_settings_form: UpdateSettingsForm) -> TextResponse { + pub async fn update_settings(&self, update_settings_form: &UpdateSettings) -> TextResponse { self.http_client.post("settings", &update_settings_form).await } diff --git a/tests/common/contexts/settings/form.rs b/tests/common/contexts/settings/form.rs index f2f21086..1a20fddb 100644 --- a/tests/common/contexts/settings/form.rs +++ b/tests/common/contexts/settings/form.rs @@ -1,3 +1,3 @@ use super::Settings; -pub type UpdateSettingsForm = Settings; +pub type UpdateSettings = Settings; diff --git a/tests/e2e/contexts/settings/contract.rs b/tests/e2e/contexts/settings/contract.rs index 96d4e3a2..809dd631 100644 --- a/tests/e2e/contexts/settings/contract.rs +++ b/tests/e2e/contexts/settings/contract.rs @@ -1,7 +1,5 @@ use crate::common::client::Client; -use crate::common::contexts::settings::form::UpdateSettingsForm; use crate::common::contexts::settings::responses::{AllSettingsResponse, Public, PublicSettingsResponse, SiteNameResponse}; -use crate::common::contexts::settings::{Auth, Database, ImageCache, Mail, Net, Settings, Tracker, Website}; use crate::e2e::contexts::user::steps::new_logged_in_admin; use crate::e2e::environment::TestEnv; @@ -69,106 +67,27 @@ async fn it_should_allow_admins_to_get_all_the_settings() { #[tokio::test] async fn it_should_allow_admins_to_update_all_the_settings() { let mut env = TestEnv::new(); + + 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; + } + env.start().await; let logged_in_admin = new_logged_in_admin(&env).await; let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_in_admin.token); - // todo: we can't actually change the settings because it would affect other E2E tests. - // Location for the `config.toml` file is hardcoded. We could use a ENV variable to change it. - - let response = client - .update_settings(UpdateSettingsForm { - website: Website { - name: "Torrust".to_string(), - }, - tracker: Tracker { - url: "udp://tracker:6969".to_string(), - mode: "Public".to_string(), - api_url: "http://tracker:1212".to_string(), - token: "MyAccessToken".to_string(), - token_valid_seconds: 7_257_600, - }, - net: Net { - port: 3000, - base_url: None, - }, - auth: Auth { - email_on_signup: "Optional".to_string(), - min_password_length: 6, - max_password_length: 64, - secret_key: "MaxVerstappenWC2021".to_string(), - }, - database: Database { - connect_url: "sqlite://storage/database/torrust_index_backend_e2e_testing.db?mode=rwc".to_string(), - torrent_info_update_interval: 3600, - }, - mail: Mail { - email_verification_enabled: false, - from: "example@email.com".to_string(), - reply_to: "noreply@email.com".to_string(), - username: String::new(), - password: String::new(), - server: "mailcatcher".to_string(), - port: 1025, - }, - image_cache: ImageCache { - max_request_timeout_ms: 1000, - capacity: 128_000_000, - entry_size_limit: 4_000_000, - user_quota_period_seconds: 3600, - user_quota_bytes: 64_000_000, - }, - }) - .await; + 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, - Settings { - website: Website { - name: "Torrust".to_string(), - }, - tracker: Tracker { - url: "udp://tracker:6969".to_string(), - mode: "Public".to_string(), - api_url: "http://tracker:1212".to_string(), - token: "MyAccessToken".to_string(), - token_valid_seconds: 7_257_600, - }, - net: Net { - port: 3000, - base_url: None, - }, - auth: Auth { - email_on_signup: "Optional".to_string(), - min_password_length: 6, - max_password_length: 64, - secret_key: "MaxVerstappenWC2021".to_string(), - }, - database: Database { - connect_url: "sqlite://storage/database/torrust_index_backend_e2e_testing.db?mode=rwc".to_string(), - torrent_info_update_interval: 3600, - }, - mail: Mail { - email_verification_enabled: false, - from: "example@email.com".to_string(), - reply_to: "noreply@email.com".to_string(), - username: String::new(), - password: String::new(), - server: "mailcatcher".to_string(), - port: 1025, - }, - image_cache: ImageCache { - max_request_timeout_ms: 1000, - capacity: 128_000_000, - entry_size_limit: 4_000_000, - user_quota_period_seconds: 3600, - user_quota_bytes: 64_000_000, - }, - } - ); + assert_eq!(res.data, new_settings); if let Some(content_type) = &response.content_type { assert_eq!(content_type, "application/json"); } diff --git a/tests/e2e/environment.rs b/tests/e2e/environment.rs index 221719ea..513cd53e 100644 --- a/tests/e2e/environment.rs +++ b/tests/e2e/environment.rs @@ -27,6 +27,10 @@ impl TestEnv { Self::default() } + pub fn is_isolated(&self) -> bool { + matches!(self.mode, State::RunningIsolated) + } + pub async fn start(&mut self) { let e2e_shared = "TORRUST_IDX_BACK_E2E_SHARED"; // bool From b97da41c20d1e2e06fcbb62a3e62b0b231e99682 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 4 May 2023 17:33:47 +0100 Subject: [PATCH 2/2] refactor: [#130] configuration for E2E tests Clean code for E2E test env configuration initialization. And the configuration is loaded from the same config file used to start the docker container: `config-idx-back.local.toml`. --- .dockerignore | 8 +- ...k.toml.local => config-idx-back.local.toml | 0 ...er.toml.local => config-tracker.local.toml | 0 docker/README.md | 6 +- docker/bin/e2e-env-up.sh | 4 +- src/bootstrap/config.rs | 23 +- src/routes/settings.rs | 7 +- tests/common/contexts/settings/mod.rs | 125 ++++++++-- tests/e2e/config.rs | 44 ++++ tests/e2e/contexts/settings/contract.rs | 8 +- tests/e2e/contexts/user/contract.rs | 4 +- tests/e2e/environment.rs | 227 ++++++------------ tests/e2e/mod.rs | 4 +- tests/environments/app_starter.rs | 5 + tests/environments/isolated.rs | 8 + tests/environments/shared.rs | 4 + 16 files changed, 288 insertions(+), 189 deletions(-) rename config-idx-back.toml.local => config-idx-back.local.toml (100%) rename config-tracker.toml.local => config-tracker.local.toml (100%) create mode 100644 tests/e2e/config.rs diff --git a/.dockerignore b/.dockerignore index 89d167c9..74351152 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,15 +5,15 @@ /.github /.gitignore /.vscode -/data_v2.db* -/data.db* /bin/ -/config-idx-back.toml.local -/config-tracker.toml.local +/config-idx-back.local.toml +/config-tracker.local.toml /config.toml /config.toml.local /cspell.json +/data_v2.db* /data.db +/data.db* /docker/ /project-words.txt /README.md diff --git a/config-idx-back.toml.local b/config-idx-back.local.toml similarity index 100% rename from config-idx-back.toml.local rename to config-idx-back.local.toml diff --git a/config-tracker.toml.local b/config-tracker.local.toml similarity index 100% rename from config-tracker.toml.local rename to config-tracker.local.toml diff --git a/docker/README.md b/docker/README.md index d38a8066..3dbfa038 100644 --- a/docker/README.md +++ b/docker/README.md @@ -46,7 +46,7 @@ docker run -it \ ### With docker-compose -The docker-compose configuration includes the MySQL service configuration. If you want to use MySQL instead of SQLite you have to change your `config.toml` or `config-idx-back.toml.local` configuration from: +The docker-compose configuration includes the MySQL service configuration. If you want to use MySQL instead of SQLite you have to change your `config.toml` or `config-idx-back.local.toml` configuration from: ```toml connect_url = "sqlite://storage/database/data.db?mode=rwc" @@ -64,8 +64,8 @@ Build and run it locally: ```s TORRUST_IDX_BACK_USER_UID=${TORRUST_IDX_BACK_USER_UID:-1000} \ - TORRUST_IDX_BACK_CONFIG=$(cat config-idx-back.toml.local) \ - TORRUST_TRACKER_CONFIG=$(cat config-tracker.toml.local) \ + TORRUST_IDX_BACK_CONFIG=$(cat config-idx-back.local.toml) \ + TORRUST_TRACKER_CONFIG=$(cat config-tracker.local.toml) \ TORRUST_TRACKER_API_TOKEN=${TORRUST_TRACKER_API_TOKEN:-MyAccessToken} \ docker compose up -d --build ``` diff --git a/docker/bin/e2e-env-up.sh b/docker/bin/e2e-env-up.sh index a5de770c..fd7cd427 100755 --- a/docker/bin/e2e-env-up.sh +++ b/docker/bin/e2e-env-up.sh @@ -4,7 +4,7 @@ TORRUST_IDX_BACK_USER_UID=${TORRUST_IDX_BACK_USER_UID:-1000} \ docker compose build TORRUST_IDX_BACK_USER_UID=${TORRUST_IDX_BACK_USER_UID:-1000} \ - TORRUST_IDX_BACK_CONFIG=$(cat config-idx-back.toml.local) \ - TORRUST_TRACKER_CONFIG=$(cat config-tracker.toml.local) \ + TORRUST_IDX_BACK_CONFIG=$(cat config-idx-back.local.toml) \ + TORRUST_TRACKER_CONFIG=$(cat config-tracker.local.toml) \ TORRUST_TRACKER_API_TOKEN=${TORRUST_TRACKER_API_TOKEN:-MyAccessToken} \ docker compose up -d diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index 1f130344..22a5590f 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -1,7 +1,16 @@ +//! Initialize configuration from file or env var. +//! +//! All environment variables are prefixed with `TORRUST_IDX_BACK_`. use std::env; -pub const CONFIG_PATH: &str = "./config.toml"; -pub const CONFIG_ENV_VAR_NAME: &str = "TORRUST_IDX_BACK_CONFIG"; +// Environment variables + +/// The whole `config.toml` file content. It has priority over the config file. +pub const ENV_VAR_CONFIG: &str = "TORRUST_IDX_BACK_CONFIG"; + +// Default values + +pub const ENV_VAR_DEFAULT_CONFIG_PATH: &str = "./config.toml"; use crate::config::Configuration; @@ -11,14 +20,14 @@ use crate::config::Configuration; /// /// Will panic if configuration is not found or cannot be parsed pub async fn init_configuration() -> Configuration { - if env::var(CONFIG_ENV_VAR_NAME).is_ok() { - println!("Loading configuration from env var `{}`", CONFIG_ENV_VAR_NAME); + if env::var(ENV_VAR_CONFIG).is_ok() { + println!("Loading configuration from env var `{}`", ENV_VAR_CONFIG); - Configuration::load_from_env_var(CONFIG_ENV_VAR_NAME).unwrap() + Configuration::load_from_env_var(ENV_VAR_CONFIG).unwrap() } else { - println!("Loading configuration from config file `{}`", CONFIG_PATH); + println!("Loading configuration from config file `{}`", ENV_VAR_DEFAULT_CONFIG_PATH); - match Configuration::load_from_file(CONFIG_PATH).await { + match Configuration::load_from_file(ENV_VAR_DEFAULT_CONFIG_PATH).await { Ok(config) => config, Err(error) => { panic!("{}", error) diff --git a/src/routes/settings.rs b/src/routes/settings.rs index 08a1d821..ba44317b 100644 --- a/src/routes/settings.rs +++ b/src/routes/settings.rs @@ -1,6 +1,6 @@ use actix_web::{web, HttpRequest, HttpResponse, Responder}; -use crate::bootstrap::config::CONFIG_PATH; +use crate::bootstrap::config::ENV_VAR_DEFAULT_CONFIG_PATH; use crate::common::WebAppData; use crate::config::TorrustConfig; use crate::errors::{ServiceError, ServiceResult}; @@ -60,7 +60,10 @@ pub async fn update_settings( return Err(ServiceError::Unauthorized); } - let _ = app_data.cfg.update_settings(payload.into_inner(), CONFIG_PATH).await; + let _ = app_data + .cfg + .update_settings(payload.into_inner(), ENV_VAR_DEFAULT_CONFIG_PATH) + .await; let settings = app_data.cfg.settings.read().await; diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 1384f2a2..2871602c 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -2,24 +2,28 @@ pub mod form; pub mod responses; use serde::{Deserialize, Serialize}; +use torrust_index_backend::config::{ + Auth as DomainAuth, Database as DomainDatabase, ImageCache as DomainImageCache, Mail as DomainMail, Network as DomainNetwork, + TorrustConfig as DomainSettings, Tracker as DomainTracker, Website as DomainWebsite, +}; -#[derive(Deserialize, Serialize, PartialEq, Debug)] +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Settings { pub website: Website, pub tracker: Tracker, - pub net: Net, + pub net: Network, pub auth: Auth, pub database: Database, pub mail: Mail, pub image_cache: ImageCache, } -#[derive(Deserialize, Serialize, PartialEq, Debug)] +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Website { pub name: String, } -#[derive(Deserialize, Serialize, PartialEq, Debug)] +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Tracker { pub url: String, pub mode: String, @@ -28,27 +32,27 @@ pub struct Tracker { pub token_valid_seconds: u64, } -#[derive(Deserialize, Serialize, PartialEq, Debug)] -pub struct Net { - pub port: u64, +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct Network { + pub port: u16, pub base_url: Option, } -#[derive(Deserialize, Serialize, PartialEq, Debug)] +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Auth { pub email_on_signup: String, - pub min_password_length: u64, - pub max_password_length: u64, + pub min_password_length: usize, + pub max_password_length: usize, pub secret_key: String, } -#[derive(Deserialize, Serialize, PartialEq, Debug)] +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Database { pub connect_url: String, pub torrent_info_update_interval: u64, } -#[derive(Deserialize, Serialize, PartialEq, Debug)] +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Mail { pub email_verification_enabled: bool, pub from: String, @@ -56,14 +60,101 @@ pub struct Mail { pub username: String, pub password: String, pub server: String, - pub port: u64, + pub port: u16, } -#[derive(Deserialize, Serialize, PartialEq, Debug)] +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct ImageCache { pub max_request_timeout_ms: u64, - pub capacity: u64, - pub entry_size_limit: u64, + pub capacity: usize, + pub entry_size_limit: usize, pub user_quota_period_seconds: u64, - pub user_quota_bytes: u64, + pub user_quota_bytes: usize, +} + +impl From for Settings { + fn from(settings: DomainSettings) -> Self { + Settings { + website: Website::from(settings.website), + tracker: Tracker::from(settings.tracker), + net: Network::from(settings.net), + auth: Auth::from(settings.auth), + database: Database::from(settings.database), + mail: Mail::from(settings.mail), + image_cache: ImageCache::from(settings.image_cache), + } + } +} + +impl From for Website { + fn from(website: DomainWebsite) -> Self { + Website { name: website.name } + } +} + +impl From for Tracker { + fn from(tracker: DomainTracker) -> Self { + Tracker { + url: tracker.url, + mode: format!("{:?}", tracker.mode), + api_url: tracker.api_url, + token: tracker.token, + token_valid_seconds: tracker.token_valid_seconds, + } + } +} + +impl From for Network { + fn from(net: DomainNetwork) -> Self { + Network { + port: net.port, + base_url: net.base_url, + } + } +} + +impl From for Auth { + fn from(auth: DomainAuth) -> Self { + Auth { + email_on_signup: format!("{:?}", auth.email_on_signup), + min_password_length: auth.min_password_length, + max_password_length: auth.max_password_length, + secret_key: auth.secret_key, + } + } +} + +impl From for Database { + fn from(database: DomainDatabase) -> Self { + Database { + connect_url: database.connect_url, + torrent_info_update_interval: database.torrent_info_update_interval, + } + } +} + +impl From for Mail { + fn from(mail: DomainMail) -> Self { + Mail { + email_verification_enabled: mail.email_verification_enabled, + from: mail.from, + reply_to: mail.reply_to, + username: mail.username, + password: mail.password, + server: mail.server, + port: mail.port, + } + } +} + +impl From for ImageCache { + fn from(image_cache: DomainImageCache) -> Self { + ImageCache { + max_request_timeout_ms: image_cache.max_request_timeout_ms, + capacity: image_cache.capacity, + entry_size_limit: image_cache.entry_size_limit, + user_quota_period_seconds: image_cache.user_quota_period_seconds, + user_quota_bytes: image_cache.user_quota_bytes, + } + } } diff --git a/tests/e2e/config.rs b/tests/e2e/config.rs new file mode 100644 index 00000000..f0c20397 --- /dev/null +++ b/tests/e2e/config.rs @@ -0,0 +1,44 @@ +//! Initialize configuration for the shared E2E tests environment from a +//! config file `config.toml` or env var. +//! +//! All environment variables are prefixed with `TORRUST_IDX_BACK_`. +use std::env; + +use torrust_index_backend::config::Configuration; + +// Environment variables + +/// If present, E2E tests will run against a shared instance of the server +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"; + +// Default values + +pub const ENV_VAR_E2E_DEFAULT_CONFIG_PATH: &str = "./config-idx-back.local.toml"; + +/// Initialize configuration from file or env var. +/// +/// # Panics +/// +/// Will panic if configuration is not found or cannot be parsed +pub async fn init_shared_env_configuration() -> Configuration { + if env::var(ENV_VAR_E2E_CONFIG).is_ok() { + println!("Loading configuration for E2E env from env var `{}`", ENV_VAR_E2E_CONFIG); + + Configuration::load_from_env_var(ENV_VAR_E2E_CONFIG).unwrap() + } else { + println!( + "Loading configuration for E2E env from config file `{}`", + ENV_VAR_E2E_DEFAULT_CONFIG_PATH + ); + + match Configuration::load_from_file(ENV_VAR_E2E_DEFAULT_CONFIG_PATH).await { + Ok(config) => config, + Err(error) => { + panic!("{}", error) + } + } + } +} diff --git a/tests/e2e/contexts/settings/contract.rs b/tests/e2e/contexts/settings/contract.rs index 809dd631..38645b2a 100644 --- a/tests/e2e/contexts/settings/contract.rs +++ b/tests/e2e/contexts/settings/contract.rs @@ -16,10 +16,10 @@ async fn it_should_allow_guests_to_get_the_public_settings() { assert_eq!( res.data, Public { - website_name: "Torrust".to_string(), - tracker_url: env.tracker_url(), - tracker_mode: "Public".to_string(), - email_on_signup: "Optional".to_string(), + 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 { diff --git a/tests/e2e/contexts/user/contract.rs b/tests/e2e/contexts/user/contract.rs index ef82c55c..06a12f79 100644 --- a/tests/e2e/contexts/user/contract.rs +++ b/tests/e2e/contexts/user/contract.rs @@ -37,7 +37,7 @@ the mailcatcher API. // Responses data #[tokio::test] -async fn it_should_allow_a_guess_user_to_register() { +async fn it_should_allow_a_guest_user_to_register() { let mut env = TestEnv::new(); env.start().await; let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); @@ -175,7 +175,7 @@ mod banned_user_list { } #[tokio::test] - async fn it_should_not_allow_guess_to_ban_a_user() { + async fn it_should_not_allow_a_guest_to_ban_a_user() { let mut env = TestEnv::new(); env.start().await; let client = Client::unauthenticated(&env.server_socket_addr().unwrap()); diff --git a/tests/e2e/environment.rs b/tests/e2e/environment.rs index 513cd53e..43eb7af3 100644 --- a/tests/e2e/environment.rs +++ b/tests/e2e/environment.rs @@ -1,7 +1,8 @@ use std::env; -use crate::common::contexts::settings::{Auth, Database, ImageCache, Mail, Net, Settings, Tracker, Website}; -use crate::environments::{self, isolated, shared}; +use super::config::{init_shared_env_configuration, ENV_VAR_E2E_SHARED}; +use crate::common::contexts::settings::Settings; +use crate::environments::{isolated, shared}; enum State { Stopped, @@ -9,186 +10,118 @@ enum State { RunningIsolated, } +/// Test environment for E2E tests. It's a wrapper around the shared or isolated +/// test environment. +/// +/// Shared test environment: +/// +/// - It's a out-of-process test environment. +/// - It has to be started before running the tests. +/// - All tests run against the same instance of the server. +/// +/// Isolated test environment: +/// +/// - It's an in-process test environment. +/// - It's started automatically when the test starts. +/// - Each test runs against a different instance of the server. +#[derive(Default)] pub struct TestEnv { - mode: State, + /// Copy of the settings when the test environment was started. + starting_settings: Option, + /// Shared independent test environment if we start using it. shared: Option, + /// Isolated test environment if we start an isolate test environment. isolated: Option, } impl TestEnv { - // todo: this class needs a big refactor: - // - It should load the `server_settings` rom both shared or isolated env. - // And `tracker_url`, `server_socket_addr`, `database_connect_url` methods - // should get the values from `server_settings`. - // - We should consider extracting a trait for test environments, so we can - // only one attribute like `AppStarter`. + // code-review: consider extracting a trait for test environments. The state + // could be only `Running` or `Stopped`, and we could have a single + // attribute with the current started test environment (`Option`). pub fn new() -> Self { Self::default() } + pub fn is_shared(&self) -> bool { + self.shared.is_some() + } + pub fn is_isolated(&self) -> bool { - matches!(self.mode, State::RunningIsolated) + self.isolated.is_some() } + /// It starts the test environment. It can be a shared or isolated test + /// environment depending on the value of the `ENV_VAR_E2E_SHARED` env var. pub async fn start(&mut self) { - let e2e_shared = "TORRUST_IDX_BACK_E2E_SHARED"; // bool + let e2e_shared = ENV_VAR_E2E_SHARED; // bool - if let Ok(_val) = env::var(e2e_shared) { - let env = shared::TestEnv::running().await; - self.mode = State::RunningShared; - self.shared = Some(env); - } + if let Ok(_e2e_test_env_is_shared) = env::var(e2e_shared) { + // Using the shared test env. + let shared_env = shared::TestEnv::running().await; - let isolated_env = isolated::TestEnv::running().await; - self.mode = State::RunningIsolated; - self.isolated = Some(isolated_env); - } + self.shared = Some(shared_env); + self.starting_settings = self.server_settings_for_shared_env().await; + } else { + // Using an isolated test env. + let isolated_env = isolated::TestEnv::running().await; - pub fn tracker_url(&self) -> String { - // todo: get from `server_settings` - match self.mode { - // todo: for shared instance, get it from env var - // `TORRUST_IDX_BACK_CONFIG` or `TORRUST_IDX_BACK_CONFIG_PATH` - State::RunningShared => "udp://tracker:6969".to_string(), - // todo - State::RunningIsolated => "udp://localhost:6969".to_string(), - State::Stopped => panic!("TestEnv is not running"), + self.isolated = Some(isolated_env); + self.starting_settings = self.server_settings_for_isolated_env(); } } /// Some test requires the real tracker to be running, so they can only /// be run in shared mode. pub fn provides_a_tracker(&self) -> bool { - matches!(self.mode, State::RunningShared) + self.is_shared() + } + + /// Returns the server starting settings if the servers was already started. + /// We do not know the settings until we start the server. + pub fn server_settings(&self) -> Option { + self.starting_settings.as_ref().cloned() } + /// Provides the API server socket address. + /// For example: `localhost:3000`. pub fn server_socket_addr(&self) -> Option { - // todo: get from `server_settings` - match self.mode { - // todo: for shared instance, get it from env var - // `TORRUST_IDX_BACK_CONFIG` or `TORRUST_IDX_BACK_CONFIG_PATH` - State::RunningShared => match &self.shared { - Some(env) => env.server_socket_addr(), - None => panic!("TestEnv is not running"), - }, - State::RunningIsolated => match &self.isolated { - Some(env) => env.server_socket_addr(), - None => panic!("TestEnv is not running"), - }, - State::Stopped => panic!("TestEnv is not running"), + match self.state() { + State::RunningShared => self.shared.as_ref().unwrap().server_socket_addr(), + State::RunningIsolated => self.isolated.as_ref().unwrap().server_socket_addr(), + State::Stopped => None, } } + /// Provides the database connect URL. + /// For example: `sqlite://storage/database/torrust_index_backend_e2e_testing.db?mode=rwc`. pub fn database_connect_url(&self) -> Option { - // todo: get from `server_settings` - match self.mode { - State::Stopped => None, - State::RunningShared => Some("sqlite://storage/database/torrust_index_backend_e2e_testing.db?mode=rwc".to_string()), - State::RunningIsolated => self - .isolated - .as_ref() - .map(environments::isolated::TestEnv::database_connect_url), - } + self.starting_settings + .as_ref() + .map(|settings| settings.database.connect_url.clone()) } - pub fn server_settings(&self) -> Option { - // todo: - // - For shared instance, get it from env var: `TORRUST_IDX_BACK_CONFIG` or `TORRUST_IDX_BACK_CONFIG_PATH`. - // - For isolated instance, get it from the isolated env configuration (`TorrustConfig`). - match self.mode { - State::Stopped => None, - State::RunningShared => Some(Settings { - website: Website { - name: "Torrust".to_string(), - }, - tracker: Tracker { - url: self.tracker_url(), - mode: "Public".to_string(), - api_url: "http://tracker:1212".to_string(), - token: "MyAccessToken".to_string(), - token_valid_seconds: 7_257_600, - }, - net: Net { - port: 3000, - base_url: None, - }, - auth: Auth { - email_on_signup: "Optional".to_string(), - min_password_length: 6, - max_password_length: 64, - secret_key: "MaxVerstappenWC2021".to_string(), - }, - database: Database { - connect_url: self.database_connect_url().unwrap(), - torrent_info_update_interval: 3600, - }, - mail: Mail { - email_verification_enabled: false, - from: "example@email.com".to_string(), - reply_to: "noreply@email.com".to_string(), - username: String::new(), - password: String::new(), - server: "mailcatcher".to_string(), - port: 1025, - }, - image_cache: ImageCache { - max_request_timeout_ms: 1000, - capacity: 128_000_000, - entry_size_limit: 4_000_000, - user_quota_period_seconds: 3600, - user_quota_bytes: 64_000_000, - }, - }), - State::RunningIsolated => Some(Settings { - website: Website { - name: "Torrust".to_string(), - }, - tracker: Tracker { - url: self.tracker_url(), - mode: "Public".to_string(), - api_url: "http://localhost:1212".to_string(), - token: "MyAccessToken".to_string(), - token_valid_seconds: 7_257_600, - }, - net: Net { port: 0, base_url: None }, - auth: Auth { - email_on_signup: "Optional".to_string(), - min_password_length: 6, - max_password_length: 64, - secret_key: "MaxVerstappenWC2021".to_string(), - }, - database: Database { - connect_url: self.database_connect_url().unwrap(), - torrent_info_update_interval: 3600, - }, - mail: Mail { - email_verification_enabled: false, - from: "example@email.com".to_string(), - reply_to: "noreply@email.com".to_string(), - username: String::new(), - password: String::new(), - server: String::new(), - port: 25, - }, - image_cache: ImageCache { - max_request_timeout_ms: 1000, - capacity: 128_000_000, - entry_size_limit: 4_000_000, - user_quota_period_seconds: 3600, - user_quota_bytes: 64_000_000, - }, - }), + fn state(&self) -> State { + if self.is_shared() { + return State::RunningShared; } - } -} -impl Default for TestEnv { - fn default() -> Self { - Self { - mode: State::Stopped, - shared: None, - isolated: None, + if self.is_isolated() { + return State::RunningIsolated; } + + State::Stopped + } + + fn server_settings_for_isolated_env(&self) -> Option { + self.isolated + .as_ref() + .map(|env| Settings::from(env.app_starter.server_configuration())) + } + + async fn server_settings_for_shared_env(&self) -> Option { + let configuration = init_shared_env_configuration().await; + let settings = configuration.settings.read().await; + Some(Settings::from(settings.clone())) } } diff --git a/tests/e2e/mod.rs b/tests/e2e/mod.rs index e9613a90..71386b0f 100644 --- a/tests/e2e/mod.rs +++ b/tests/e2e/mod.rs @@ -34,6 +34,8 @@ //! ``` //! //! You can also make that change permanent, please refer to your OS -//! documentation. +//! documentation. See for more +//! information. +pub mod config; pub mod contexts; pub mod environment; diff --git a/tests/environments/app_starter.rs b/tests/environments/app_starter.rs index 4adef42c..7e3b28d1 100644 --- a/tests/environments/app_starter.rs +++ b/tests/environments/app_starter.rs @@ -71,6 +71,11 @@ impl AppStarter { } } + #[must_use] + pub fn server_configuration(&self) -> TorrustConfig { + self.configuration.clone() + } + #[must_use] pub fn server_socket_addr(&self) -> Option { self.running_state.as_ref().map(|running_state| running_state.socket_addr) diff --git a/tests/environments/isolated.rs b/tests/environments/isolated.rs index 40fa74a9..470488b2 100644 --- a/tests/environments/isolated.rs +++ b/tests/environments/isolated.rs @@ -38,11 +38,19 @@ impl TestEnv { self.app_starter.start().await; } + /// Provides the whole server configuration. + #[must_use] + pub fn server_configuration(&self) -> TorrustConfig { + self.app_starter.server_configuration() + } + /// Provides the API server socket address. + #[must_use] pub fn server_socket_addr(&self) -> Option { self.app_starter.server_socket_addr().map(|addr| addr.to_string()) } + #[must_use] pub fn database_connect_url(&self) -> String { self.app_starter.database_connect_url() } diff --git a/tests/environments/shared.rs b/tests/environments/shared.rs index 9478ce02..1920f0cd 100644 --- a/tests/environments/shared.rs +++ b/tests/environments/shared.rs @@ -22,7 +22,11 @@ impl TestEnv { } /// Provides the API server socket address. + #[must_use] pub fn server_socket_addr(&self) -> Option { + // If the E2E configuration uses port 0 in the future instead of a + // predefined port (right now we are using port 3000) we will + // need to pass an env var with the port used by the server. Some(self.authority.clone()) } }