From b548e80e8cb5b1da87a14578d99df5f303cca345 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 9 Aug 2022 17:26:02 +0100 Subject: [PATCH 1/6] test: add test for config module --- Cargo.lock | 112 ++++++++++++++++++++++++++++- Cargo.toml | 2 + src/config.rs | 148 +++++++++++++++++++++++++++++++------- src/databases/database.rs | 2 +- src/main.rs | 4 +- 5 files changed, 237 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ee924fac..c52f6767b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -265,7 +265,7 @@ dependencies = [ "ansi_term", "atty", "bitflags", - "strsim", + "strsim 0.8.0", "textwrap", "unicode-width", "vec_map", @@ -352,6 +352,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -812,6 +847,12 @@ dependencies = [ "want", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.2.3" @@ -831,6 +872,7 @@ checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" dependencies = [ "autocfg", "hashbrown", + "serde 1.0.137", ] [[package]] @@ -1131,7 +1173,7 @@ dependencies = [ "subprocess", "thiserror", "time 0.2.27", - "uuid", + "uuid 0.8.2", ] [[package]] @@ -1254,6 +1296,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "once_cell" version = "1.10.0" @@ -1826,6 +1877,34 @@ dependencies = [ "serde 1.0.137", ] +[[package]] +name = "serde_with" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89df7a26519371a3cce44fbb914c2819c84d9b897890987fa3ab096491cc0ea8" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap", + "serde 1.0.137", + "serde_json", + "serde_with_macros", + "time 0.3.13", +] + +[[package]] +name = "serde_with_macros" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de337f322382fcdfbb21a014f7c224ee041a23785651db67b9827403178f698f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -1991,6 +2070,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "subprocess" version = "0.2.8" @@ -2096,6 +2181,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45" +dependencies = [ + "itoa", + "libc", + "num_threads", + "serde 1.0.137", +] + [[package]] name = "time-macros" version = "0.1.1" @@ -2259,9 +2356,11 @@ dependencies = [ "serde 1.0.137", "serde_bencode", "serde_json", + "serde_with", "thiserror", "tokio", "toml", + "uuid 1.1.2", "warp", ] @@ -2421,6 +2520,15 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +[[package]] +name = "uuid" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +dependencies = [ + "getrandom", +] + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index 53e2949db..554ba940d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ tokio = { version = "1.7", features = ["full"] } serde = { version = "1.0", features = ["derive"] } serde_bencode = "^0.2.3" serde_json = "1.0.72" +serde_with = "2.0.0" hex = "0.4.3" percent-encoding = "2.1.0" binascii = "0.1" @@ -48,3 +49,4 @@ futures = "0.3.21" async-trait = "0.1.52" aquatic_udp_protocol = "0.2.0" +uuid = { version = "1.1.2", features = ["v4"] } diff --git a/src/config.rs b/src/config.rs index e2cd167f5..8acda3f63 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,37 +6,39 @@ use std::path::Path; use std::str::FromStr; use config::{Config, ConfigError, File}; -use serde::{Deserialize, Serialize, Serializer}; +use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, NoneAsEmptyString}; use toml; use crate::databases::database::DatabaseDrivers; use crate::mode::TrackerMode; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, PartialEq, Debug)] pub struct UdpTrackerConfig { pub enabled: bool, pub bind_address: String, } -#[derive(Serialize, Deserialize, Debug)] +#[serde_as] +#[derive(Serialize, Deserialize, PartialEq, Debug)] pub struct HttpTrackerConfig { pub enabled: bool, pub bind_address: String, pub ssl_enabled: bool, - #[serde(serialize_with = "none_as_empty_string")] + #[serde_as(as = "NoneAsEmptyString")] pub ssl_cert_path: Option, - #[serde(serialize_with = "none_as_empty_string")] + #[serde_as(as = "NoneAsEmptyString")] pub ssl_key_path: Option, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, PartialEq, Debug)] pub struct HttpApiConfig { pub enabled: bool, pub bind_address: String, pub access_tokens: HashMap, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, PartialEq, Debug)] pub struct Configuration { pub log_level: Option, pub mode: TrackerMode, @@ -75,18 +77,6 @@ impl std::fmt::Display for ConfigurationError { impl std::error::Error for ConfigurationError {} -pub fn none_as_empty_string(option: &Option, serializer: S) -> Result - where - T: Serialize, - S: Serializer, -{ - if let Some(value) = option { - value.serialize(serializer) - } else { - "".serialize(serializer) - } -} - impl Configuration { pub fn load(data: &[u8]) -> Result { toml::from_slice(data) @@ -161,18 +151,16 @@ impl Configuration { configuration } - pub fn load_from_file() -> Result { + pub fn load_from_file(path: &str) -> Result { let mut config = Config::new(); - const CONFIG_PATH: &str = "config.toml"; - - if Path::new(CONFIG_PATH).exists() { - config.merge(File::with_name(CONFIG_PATH))?; + if Path::new(path).exists() { + config.merge(File::with_name(path))?; } else { eprintln!("No config file found."); eprintln!("Creating config file.."); let config = Configuration::default(); - let _ = config.save_to_file(); + let _ = config.save_to_file(path); return Err(ConfigError::Message(format!("Please edit the config.TOML in the root folder and restart the tracker."))); } @@ -181,9 +169,115 @@ impl Configuration { Ok(torrust_config) } - pub fn save_to_file(&self) -> Result<(), ()> { + pub fn save_to_file(&self, path: &str) -> Result<(), ()> { let toml_string = toml::to_string(self).expect("Could not encode TOML value"); - fs::write("config.toml", toml_string).expect("Could not write to file!"); + fs::write(path, toml_string).expect("Could not write to file!"); Ok(()) } } + +mod configuration { + + #[cfg(test)] + fn default_config_toml() -> String { + let config = r#"log_level = "info" + mode = "public" + db_driver = "Sqlite3" + db_path = "data.db" + announce_interval = 120 + min_announce_interval = 120 + max_peer_timeout = 900 + on_reverse_proxy = false + external_ip = "0.0.0.0" + tracker_usage_statistics = true + persistent_torrent_completed_stat = false + inactive_peer_cleanup_interval = 600 + remove_peerless_torrents = true + + [[udp_trackers]] + enabled = false + bind_address = "0.0.0.0:6969" + + [[http_trackers]] + enabled = false + bind_address = "0.0.0.0:6969" + ssl_enabled = false + ssl_cert_path = "" + ssl_key_path = "" + + [http_api] + enabled = true + bind_address = "127.0.0.1:1212" + + [http_api.access_tokens] + admin = "MyAccessToken" + "#.lines().map(|line| line.trim_start()).collect::>().join("\n"); + config + } + + #[test] + fn should_have_a_default_value_for_the_log_level() { + use crate::Configuration; + + let configuration = Configuration::default(); + + assert_eq!(configuration.log_level, Option::from(String::from("info")), "Expected default log level to be: {:?}, got {:?}", Option::from(String::from("info")), configuration.log_level); + } + + #[test] + fn should_be_saved_in_a_toml_config_file() { + use std::env; + use crate::Configuration; + use std::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.clone(); + let path = config_file_path.to_string_lossy().to_string(); + + let default_configuration = Configuration::default(); + + default_configuration.save_to_file(&path).expect("Could not save configuration to file"); + + let contents = fs::read_to_string(&path).expect("Something went wrong reading the file"); + + assert_eq!(contents, default_config_toml()); + } + + #[cfg(test)] + fn create_temp_config_file_with_default_config()-> String { + use std::env; + use std::fs::File; + use std::io::Write; + 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::load_from_file + let config_file_path = temp_file.clone(); + let path = config_file_path.to_string_lossy().to_string(); + + // Write file contents + let mut file = File::create(temp_file).unwrap(); + writeln!(&mut file, "{}", default_config_toml()).unwrap(); + + path + } + + #[test] + fn should_be_loaded_from_a_toml_config_file() { + use crate::Configuration; + + let config_file_path = create_temp_config_file_with_default_config(); + + let configuration = Configuration::load_from_file(&config_file_path).expect("Could not load configuration from file"); + + assert_eq!(configuration, Configuration::default()); + } +} \ No newline at end of file diff --git a/src/databases/database.rs b/src/databases/database.rs index 721dfd00d..915c5381e 100644 --- a/src/databases/database.rs +++ b/src/databases/database.rs @@ -7,7 +7,7 @@ use crate::tracker::key::AuthKey; use crate::databases::mysql::MysqlDatabase; use crate::databases::sqlite::SqliteDatabase; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, PartialEq, Debug)] pub enum DatabaseDrivers { Sqlite3, MySQL, diff --git a/src/main.rs b/src/main.rs index 794cda4bb..963419f03 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,8 +7,10 @@ use torrust_tracker::tracker::tracker::TorrentTracker; #[tokio::main] async fn main() { + const CONFIG_PATH: &str = "config.toml"; + // Initialize Torrust config - let config = match Configuration::load_from_file() { + let config = match Configuration::load_from_file(CONFIG_PATH) { Ok(config) => Arc::new(config), Err(error) => { panic!("{}", error) From 1e4cbb8f5af17cf09f07828a84615d930ea1bd87 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 10 Aug 2022 08:21:39 +0100 Subject: [PATCH 2/6] test: refactor to follow rust conventions And add missing annotation for tests module. --- src/config.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/config.rs b/src/config.rs index 8acda3f63..c6414de5e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -176,7 +176,8 @@ impl Configuration { } } -mod configuration { +#[cfg(test)] +mod tests { #[cfg(test)] fn default_config_toml() -> String { @@ -216,7 +217,7 @@ mod configuration { } #[test] - fn should_have_a_default_value_for_the_log_level() { + fn configuration_should_have_a_default_value_for_the_log_level() { use crate::Configuration; let configuration = Configuration::default(); @@ -225,7 +226,7 @@ mod configuration { } #[test] - fn should_be_saved_in_a_toml_config_file() { + fn configuration_should_be_saved_in_a_toml_config_file() { use std::env; use crate::Configuration; use std::fs; @@ -271,7 +272,7 @@ mod configuration { } #[test] - fn should_be_loaded_from_a_toml_config_file() { + fn configuration_should_be_loaded_from_a_toml_config_file() { use crate::Configuration; let config_file_path = create_temp_config_file_with_default_config(); From 228585eee884437ac33d65ed089a3def45e1dd80 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 10 Aug 2022 08:24:17 +0100 Subject: [PATCH 3/6] test: remove test for config default options --- src/config.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/config.rs b/src/config.rs index c6414de5e..3d6405621 100644 --- a/src/config.rs +++ b/src/config.rs @@ -216,15 +216,6 @@ mod tests { config } - #[test] - fn configuration_should_have_a_default_value_for_the_log_level() { - use crate::Configuration; - - let configuration = Configuration::default(); - - assert_eq!(configuration.log_level, Option::from(String::from("info")), "Expected default log level to be: {:?}, got {:?}", Option::from(String::from("info")), configuration.log_level); - } - #[test] fn configuration_should_be_saved_in_a_toml_config_file() { use std::env; From 1a9b7dbfd076a30afd0f5471f8cf5a54c22d1fd0 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 10 Aug 2022 08:26:19 +0100 Subject: [PATCH 4/6] refactor: remove duplicate impl block --- src/config.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 3d6405621..0eaaafc45 100644 --- a/src/config.rs +++ b/src/config.rs @@ -107,9 +107,7 @@ impl Configuration { } } } -} -impl Configuration { pub fn default() -> Configuration { let mut configuration = Configuration { log_level: Option::from(String::from("info")), From 749e8963732ee6adf53622df49d3be7b8f11b36f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 10 Aug 2022 09:53:13 +0100 Subject: [PATCH 5/6] refactor: remove unused code --- src/config.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/config.rs b/src/config.rs index 0eaaafc45..8e79c45ef 100644 --- a/src/config.rs +++ b/src/config.rs @@ -78,23 +78,6 @@ impl std::fmt::Display for ConfigurationError { impl std::error::Error for ConfigurationError {} impl Configuration { - pub fn load(data: &[u8]) -> Result { - toml::from_slice(data) - } - - pub fn load_file(path: &str) -> Result { - match std::fs::read(path) { - Err(e) => Err(ConfigurationError::IOError(e)), - Ok(data) => { - match Self::load(data.as_slice()) { - Ok(cfg) => { - Ok(cfg) - } - Err(e) => Err(ConfigurationError::ParseError(e)), - } - } - } - } pub fn get_ext_ip(&self) -> Option { match &self.external_ip { From d7dfe0252dd4a3a021ec4c7b390f512f1fb8ce67 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 10 Aug 2022 10:01:10 +0100 Subject: [PATCH 6/6] test: add more tests to configuration --- src/config.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/config.rs b/src/config.rs index 8e79c45ef..005705f78 100644 --- a/src/config.rs +++ b/src/config.rs @@ -197,6 +197,26 @@ mod tests { config } + #[test] + fn configuration_should_have_default_values() { + use crate::Configuration; + + let configuration = Configuration::default(); + + let toml = toml::to_string(&configuration).expect("Could not encode TOML value"); + + assert_eq!(toml, default_config_toml()); + } + + #[test] + fn configuration_should_contain_the_external_ip() { + use crate::Configuration; + + let configuration = Configuration::default(); + + assert_eq!(configuration.external_ip, Option::Some(String::from("0.0.0.0"))); + } + #[test] fn configuration_should_be_saved_in_a_toml_config_file() { use std::env; @@ -253,4 +273,13 @@ mod tests { assert_eq!(configuration, Configuration::default()); } + + #[test] + fn configuration_error_could_be_displayed() { + use crate::ConfigurationError; + + let error = ConfigurationError::TrackerModeIncompatible; + + assert_eq!(format!("{}", error), "TrackerModeIncompatible"); + } } \ No newline at end of file