Skip to content

Commit be3ab62

Browse files
committed
test: [#581] add some tests to configuration before refactoring
We will overhaul the configuration, so it's convenient to have some tests.
1 parent b2d864a commit be3ab62

File tree

1 file changed

+256
-13
lines changed

1 file changed

+256
-13
lines changed

src/config.rs

+256-13
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ impl From<ConfigError> for Error {
123123
}
124124

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

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

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

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

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

252252
/// Authentication options.
253-
#[derive(Debug, Clone, Serialize, Deserialize)]
253+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
254254
pub struct Auth {
255255
/// Whether or not to require an email on signup.
256256
pub email_on_signup: EmailOnSignup,
@@ -280,7 +280,7 @@ impl Auth {
280280
}
281281

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

297297
/// SMTP configuration.
298-
#[derive(Debug, Clone, Serialize, Deserialize)]
298+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
299299
pub struct Mail {
300300
/// Whether or not to enable email verification on signup.
301301
pub email_verification_enabled: bool,
@@ -335,7 +335,7 @@ impl Default for Mail {
335335
/// proxy. The proxy will not download new images if the user has reached the
336336
/// quota.
337337
#[allow(clippy::module_name_repetitions)]
338-
#[derive(Debug, Clone, Serialize, Deserialize)]
338+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
339339
pub struct ImageCache {
340340
/// Maximum time in seconds to wait for downloading the image form the original source.
341341
pub max_request_timeout_ms: u64,
@@ -352,7 +352,7 @@ pub struct ImageCache {
352352
}
353353

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

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

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

638638
/// The public index configuration.
639639
/// There is an endpoint to get this configuration.
640-
#[derive(Debug, Clone, Serialize, Deserialize)]
640+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
641641
pub struct ConfigurationPublic {
642642
website_name: String,
643643
tracker_url: String,
644644
tracker_mode: TrackerMode,
645645
email_on_signup: EmailOnSignup,
646646
}
647+
648+
#[cfg(test)]
649+
mod tests {
650+
651+
use crate::config::{Configuration, ConfigurationPublic, Info};
652+
653+
#[cfg(test)]
654+
fn default_config_toml() -> String {
655+
let config = r#"[website]
656+
name = "Torrust"
657+
658+
[tracker]
659+
url = "udp://localhost:6969"
660+
mode = "Public"
661+
api_url = "http://localhost:1212"
662+
token = "MyAccessToken"
663+
token_valid_seconds = 7257600
664+
665+
[net]
666+
port = 3001
667+
668+
[auth]
669+
email_on_signup = "Optional"
670+
min_password_length = 6
671+
max_password_length = 64
672+
secret_key = "MaxVerstappenWC2021"
673+
674+
[database]
675+
connect_url = "sqlite://data.db?mode=rwc"
676+
677+
[mail]
678+
email_verification_enabled = false
679+
from = "example@email.com"
680+
reply_to = "noreply@email.com"
681+
username = ""
682+
password = ""
683+
server = ""
684+
port = 25
685+
686+
[image_cache]
687+
max_request_timeout_ms = 1000
688+
capacity = 128000000
689+
entry_size_limit = 4000000
690+
user_quota_period_seconds = 3600
691+
user_quota_bytes = 64000000
692+
693+
[api]
694+
default_torrent_page_size = 10
695+
max_torrent_page_size = 30
696+
697+
[tracker_statistics_importer]
698+
torrent_info_update_interval = 3600
699+
port = 3002
700+
"#
701+
.lines()
702+
.map(str::trim_start)
703+
.collect::<Vec<&str>>()
704+
.join("\n");
705+
config
706+
}
707+
708+
#[tokio::test]
709+
async fn configuration_should_build_settings_with_default_values() {
710+
let configuration = Configuration::default().get_all().await;
711+
712+
let toml = toml::to_string(&configuration).expect("Could not encode TOML value for configuration");
713+
714+
assert_eq!(toml, default_config_toml());
715+
}
716+
717+
#[tokio::test]
718+
async fn configuration_should_return_all_settings() {
719+
let configuration = Configuration::default().get_all().await;
720+
721+
let toml = toml::to_string(&configuration).expect("Could not encode TOML value for configuration");
722+
723+
assert_eq!(toml, default_config_toml());
724+
}
725+
726+
#[tokio::test]
727+
async fn configuration_should_return_only_public_settings() {
728+
let configuration = Configuration::default();
729+
let all_settings = configuration.get_all().await;
730+
731+
assert_eq!(
732+
configuration.get_public().await,
733+
ConfigurationPublic {
734+
website_name: all_settings.website.name,
735+
tracker_url: all_settings.tracker.url,
736+
tracker_mode: all_settings.tracker.mode,
737+
email_on_signup: all_settings.auth.email_on_signup,
738+
}
739+
);
740+
}
741+
742+
#[tokio::test]
743+
async fn configuration_should_return_the_site_name() {
744+
let configuration = Configuration::default();
745+
assert_eq!(configuration.get_site_name().await, "Torrust".to_string());
746+
}
747+
748+
#[tokio::test]
749+
async fn configuration_should_return_the_api_base_url() {
750+
let configuration = Configuration::default();
751+
assert_eq!(configuration.get_api_base_url().await, None);
752+
753+
let mut settings_lock = configuration.settings.write().await;
754+
settings_lock.net.base_url = Some("http://localhost".to_string());
755+
drop(settings_lock);
756+
757+
assert_eq!(configuration.get_api_base_url().await, Some("http://localhost".to_string()));
758+
}
759+
760+
#[tokio::test]
761+
async fn configuration_could_be_saved_in_a_toml_config_file() {
762+
use std::{env, fs};
763+
764+
use uuid::Uuid;
765+
766+
// Build temp config file path
767+
let temp_directory = env::temp_dir();
768+
let temp_file = temp_directory.join(format!("test_config_{}.toml", Uuid::new_v4()));
769+
770+
// Convert to argument type for Configuration::save_to_file
771+
let config_file_path = temp_file;
772+
let path = config_file_path.to_string_lossy().to_string();
773+
774+
let default_configuration = Configuration::default();
775+
776+
default_configuration.save_to_file(&path).await;
777+
778+
let contents = fs::read_to_string(&path).expect("written toml configuration file should be read");
779+
780+
assert_eq!(contents, default_config_toml());
781+
}
782+
783+
#[tokio::test]
784+
async fn configuration_could_be_loaded_from_a_toml_config_file() {
785+
use std::{env, fs};
786+
787+
use uuid::Uuid;
788+
789+
// Build temp config file path
790+
let temp_directory = env::temp_dir();
791+
let temp_file = temp_directory.join(format!("test_config_{}.toml", Uuid::new_v4()));
792+
793+
let default_configuration = Configuration::default();
794+
795+
// Serialize the default configuration to TOML string
796+
let toml_string = toml::to_string(&default_configuration.get_all().await).unwrap();
797+
798+
// Write the TOML string to the file
799+
fs::write(&temp_file, toml_string).expect("Failed to write default configuration to a temp toml file");
800+
801+
// Convert to argument type for Configuration::save_to_file
802+
let config_file_path = temp_file;
803+
let path = config_file_path.to_string_lossy().to_string();
804+
805+
let configuration = Configuration::load_from_file(&path)
806+
.await
807+
.expect("Failed to load configuration from toml file");
808+
809+
assert_eq!(configuration.get_all().await, Configuration::default().get_all().await);
810+
}
811+
812+
#[tokio::test]
813+
async fn configuration_could_be_loaded_from_a_toml_string() {
814+
let info = Info {
815+
index_toml: default_config_toml(),
816+
tracker_api_token: None,
817+
auth_secret_key: None,
818+
};
819+
820+
let configuration = Configuration::load(&info).expect("Failed to load configuration from info");
821+
822+
assert_eq!(configuration.get_all().await, Configuration::default().get_all().await);
823+
}
824+
825+
#[tokio::test]
826+
async fn configuration_should_allow_to_override_the_tracker_api_token_provided_in_the_toml_file() {
827+
let info = Info {
828+
index_toml: default_config_toml(),
829+
tracker_api_token: Some("OVERRIDDEN API TOKEN".to_string()),
830+
auth_secret_key: None,
831+
};
832+
833+
let configuration = Configuration::load(&info).expect("Failed to load configuration from info");
834+
835+
assert_eq!(
836+
configuration.get_all().await.tracker.token,
837+
"OVERRIDDEN API TOKEN".to_string()
838+
);
839+
}
840+
841+
#[tokio::test]
842+
async fn configuration_should_allow_to_override_the_authentication_secret_key_provided_in_the_toml_file() {
843+
let info = Info {
844+
index_toml: default_config_toml(),
845+
tracker_api_token: None,
846+
auth_secret_key: Some("OVERRIDDEN AUTH SECRET KEY".to_string()),
847+
};
848+
849+
let configuration = Configuration::load(&info).expect("Failed to load configuration from info");
850+
851+
assert_eq!(
852+
configuration.get_all().await.auth.secret_key,
853+
"OVERRIDDEN AUTH SECRET KEY".to_string()
854+
);
855+
}
856+
857+
mod syntax_checks {
858+
// todo: use rich types in configuration structs for basic syntax checks.
859+
860+
use crate::config::Configuration;
861+
862+
#[tokio::test]
863+
async fn tracker_url_should_be_a_valid_url() {
864+
let configuration = Configuration::default();
865+
866+
let mut settings_lock = configuration.settings.write().await;
867+
settings_lock.tracker.url = "INVALID URL".to_string();
868+
drop(settings_lock);
869+
870+
assert!(configuration.validate().await.is_err());
871+
}
872+
}
873+
874+
mod semantic_validation {
875+
use crate::config::{Configuration, TrackerMode};
876+
877+
#[tokio::test]
878+
async fn udp_trackers_in_close_mode_are_not_supported() {
879+
let configuration = Configuration::default();
880+
881+
let mut settings_lock = configuration.settings.write().await;
882+
settings_lock.tracker.mode = TrackerMode::Private;
883+
settings_lock.tracker.url = "udp://localhost:6969".to_string();
884+
drop(settings_lock);
885+
886+
assert!(configuration.validate().await.is_err());
887+
}
888+
}
889+
}

0 commit comments

Comments
 (0)