Skip to content

Commit

Permalink
test: [torrust#581] add some tests to configration before refactoring
Browse files Browse the repository at this point in the history
We are going to overhaul the configuration, so it's convenient to have some tests.
  • Loading branch information
josecelano committed May 17, 2024
1 parent b2d864a commit eafb8aa
Showing 1 changed file with 256 additions and 13 deletions.
269 changes: 256 additions & 13 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ impl From<ConfigError> for Error {
}

/// Information displayed to the user in the website.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Website {
/// The name of the website.
pub name: String,
Expand All @@ -139,7 +139,7 @@ impl Default for Website {

/// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives)
/// crate for more information.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TrackerMode {
// todo: use https://crates.io/crates/torrust-tracker-primitives
/// Will track every new info hash and serve every peer.
Expand Down Expand Up @@ -171,7 +171,7 @@ impl TrackerMode {
}

/// Configuration for the associated tracker.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Tracker {
/// Connection string for the tracker. For example: `udp://TRACKER_IP:6969`.
pub url: String,
Expand Down Expand Up @@ -211,7 +211,7 @@ impl Default for Tracker {
pub const FREE_PORT: u16 = 0;

/// The the base URL for the API.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Network {
/// The port to listen on. Default to `3001`.
pub port: u16,
Expand All @@ -233,7 +233,7 @@ impl Default for Network {
}

/// Whether the email is required on signup or not.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum EmailOnSignup {
/// The email is required on signup.
Required,
Expand All @@ -250,7 +250,7 @@ impl Default for EmailOnSignup {
}

/// Authentication options.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Auth {
/// Whether or not to require an email on signup.
pub email_on_signup: EmailOnSignup,
Expand Down Expand Up @@ -280,7 +280,7 @@ impl Auth {
}

/// Database configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Database {
/// The connection string for the database. For example: `sqlite://data.db?mode=rwc`.
pub connect_url: String,
Expand All @@ -295,7 +295,7 @@ impl Default for Database {
}

/// SMTP configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Mail {
/// Whether or not to enable email verification on signup.
pub email_verification_enabled: bool,
Expand Down Expand Up @@ -335,7 +335,7 @@ impl Default for Mail {
/// proxy. The proxy will not download new images if the user has reached the
/// quota.
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ImageCache {
/// Maximum time in seconds to wait for downloading the image form the original source.
pub max_request_timeout_ms: u64,
Expand All @@ -352,7 +352,7 @@ pub struct ImageCache {
}

/// Core configuration for the API
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Api {
/// The default page size for torrent lists.
pub default_torrent_page_size: u8,
Expand All @@ -370,7 +370,7 @@ impl Default for Api {
}

/// Configuration for the tracker statistics importer.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TrackerStatisticsImporter {
/// The interval in seconds to get statistics from the tracker.
pub torrent_info_update_interval: u64,
Expand Down Expand Up @@ -425,7 +425,7 @@ impl Tsl {
}

/// The whole configuration for the index.
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
pub struct TorrustIndex {
/// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`,
/// `Debug` and `Trace`. Default is `Info`.
Expand Down Expand Up @@ -637,10 +637,253 @@ fn parse_url(url_str: &str) -> Result<Url, url::ParseError> {

/// The public index configuration.
/// There is an endpoint to get this configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ConfigurationPublic {
website_name: String,
tracker_url: String,
tracker_mode: TrackerMode,
email_on_signup: EmailOnSignup,
}

#[cfg(test)]
mod tests {

use crate::config::{Configuration, ConfigurationPublic, Info};

#[cfg(test)]
fn default_config_toml() -> String {
let config = r#"[website]
name = "Torrust"
[tracker]
url = "udp://localhost:6969"
mode = "Public"
api_url = "http://localhost:1212"
token = "MyAccessToken"
token_valid_seconds = 7257600
[net]
port = 3001
[auth]
email_on_signup = "Optional"
min_password_length = 6
max_password_length = 64
secret_key = "MaxVerstappenWC2021"
[database]
connect_url = "sqlite://data.db?mode=rwc"
[mail]
email_verification_enabled = false
from = "example@email.com"
reply_to = "noreply@email.com"
username = ""
password = ""
server = ""
port = 25
[image_cache]
max_request_timeout_ms = 1000
capacity = 128000000
entry_size_limit = 4000000
user_quota_period_seconds = 3600
user_quota_bytes = 64000000
[api]
default_torrent_page_size = 10
max_torrent_page_size = 30
[tracker_statistics_importer]
torrent_info_update_interval = 3600
port = 3002
"#
.lines()
.map(str::trim_start)
.collect::<Vec<&str>>()
.join("\n");
config
}

#[tokio::test]
async fn configuration_should_build_settings_with_default_values() {
let configuration = Configuration::default().get_all().await;

let toml = toml::to_string(&configuration).expect("Could not encode TOML value for configuration");

assert_eq!(toml, default_config_toml());
}

#[tokio::test]
async fn configuration_should_return_all_settings() {
let configuration = Configuration::default().get_all().await;

let toml = toml::to_string(&configuration).expect("Could not encode TOML value for configuration");

assert_eq!(toml, default_config_toml());
}

#[tokio::test]
async fn configuration_should_return_only_public_settings() {
let configuration = Configuration::default();
let all_settings = configuration.get_all().await;

assert_eq!(
configuration.get_public().await,
ConfigurationPublic {
website_name: all_settings.website.name,
tracker_url: all_settings.tracker.url,
tracker_mode: all_settings.tracker.mode,
email_on_signup: all_settings.auth.email_on_signup,
}
);
}

#[tokio::test]
async fn configuration_should_return_the_site_name() {
let configuration = Configuration::default();
assert_eq!(configuration.get_site_name().await, "Torrust".to_string());
}

#[tokio::test]
async fn configuration_should_return_the_api_base_url() {
let configuration = Configuration::default();
assert_eq!(configuration.get_api_base_url().await, None);

let mut settings_lock = configuration.settings.write().await;
settings_lock.net.base_url = Some("http://localhost".to_string());
drop(settings_lock);

assert_eq!(configuration.get_api_base_url().await, Some("http://localhost".to_string()));
}

#[tokio::test]
async fn configuration_could_be_saved_in_a_toml_config_file() {
use std::{env, fs};

use uuid::Uuid;

// Build temp config file path
let temp_directory = env::temp_dir();
let temp_file = temp_directory.join(format!("test_config_{}.toml", Uuid::new_v4()));

// Convert to argument type for Configuration::save_to_file
let config_file_path = temp_file;
let path = config_file_path.to_string_lossy().to_string();

let default_configuration = Configuration::default();

default_configuration.save_to_file(&path).await;

let contents = fs::read_to_string(&path).expect("written toml configuration file should be read");

assert_eq!(contents, default_config_toml());
}

#[tokio::test]
async fn configuration_could_be_loaded_from_a_toml_config_file() {
use std::{env, fs};

use uuid::Uuid;

// Build temp config file path
let temp_directory = env::temp_dir();
let temp_file = temp_directory.join(format!("test_config_{}.toml", Uuid::new_v4()));

let default_configuration = Configuration::default();

// Serialize the default configuration to TOML string
let toml_string = toml::to_string(&default_configuration.get_all().await).unwrap();

// Write the TOML string to the file
fs::write(&temp_file, toml_string).expect("Failed to write default configuration to a temp toml file");

// Convert to argument type for Configuration::save_to_file
let config_file_path = temp_file;
let path = config_file_path.to_string_lossy().to_string();

let configuration = Configuration::load_from_file(&path)
.await
.expect("Failed to load configuration from toml file");

assert_eq!(configuration.get_all().await, Configuration::default().get_all().await);
}

#[tokio::test]
async fn configuration_could_be_loaded_from_a_toml_string() {
let info = Info {
index_toml: default_config_toml(),
tracker_api_token: None,
auth_secret_key: None,
};

let configuration = Configuration::load(&info).expect("Failed to load configuration from info");

assert_eq!(configuration.get_all().await, Configuration::default().get_all().await);
}

#[tokio::test]
async fn configuration_should_allow_to_override_the_tracker_api_token_provided_in_the_toml_file() {
let info = Info {
index_toml: default_config_toml(),
tracker_api_token: Some("OVERRIDDEN API TOKEN".to_string()),
auth_secret_key: None,
};

let configuration = Configuration::load(&info).expect("Failed to load configuration from info");

assert_eq!(
configuration.get_all().await.tracker.token,
"OVERRIDDEN API TOKEN".to_string()
);
}

#[tokio::test]
async fn configuration_should_allow_to_override_the_authentication_secret_key_provided_in_the_toml_file() {
let info = Info {
index_toml: default_config_toml(),
tracker_api_token: None,
auth_secret_key: Some("OVERRIDDEN AUTH SECRET KEY".to_string()),
};

let configuration = Configuration::load(&info).expect("Failed to load configuration from info");

assert_eq!(
configuration.get_all().await.auth.secret_key,
"OVERRIDDEN AUTH SECRET KEY".to_string()
);
}

mod syntax_checks {
// todo: use rich types in configuration structs for basic syntax checks.

use crate::config::Configuration;

#[tokio::test]
async fn tracker_url_should_be_a_valid_url() {
let configuration = Configuration::default();

let mut settings_lock = configuration.settings.write().await;
settings_lock.tracker.url = "INVALID URL".to_string();
drop(settings_lock);

assert!(configuration.validate().await.is_err());
}
}

mod semantic_validation {
use crate::config::{Configuration, TrackerMode};

#[tokio::test]
async fn udp_trackers_in_close_mode_are_not_supported() {
let configuration = Configuration::default();

let mut settings_lock = configuration.settings.write().await;
settings_lock.tracker.mode = TrackerMode::Private;
settings_lock.tracker.url = "udp://localhost:6969".to_string();
drop(settings_lock);

assert!(configuration.validate().await.is_err());
}
}
}

0 comments on commit eafb8aa

Please sign in to comment.