@@ -123,7 +123,7 @@ impl From<ConfigError> for Error {
123
123
}
124
124
125
125
/// Information displayed to the user in the website.
126
- #[ derive( Debug , Clone , Serialize , Deserialize ) ]
126
+ #[ derive( Debug , Clone , Serialize , Deserialize , PartialEq ) ]
127
127
pub struct Website {
128
128
/// The name of the website.
129
129
pub name : String ,
@@ -139,7 +139,7 @@ impl Default for Website {
139
139
140
140
/// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives)
141
141
/// crate for more information.
142
- #[ derive( Debug , Clone , Serialize , Deserialize ) ]
142
+ #[ derive( Debug , Clone , Serialize , Deserialize , PartialEq ) ]
143
143
pub enum TrackerMode {
144
144
// todo: use https://crates.io/crates/torrust-tracker-primitives
145
145
/// Will track every new info hash and serve every peer.
@@ -171,7 +171,7 @@ impl TrackerMode {
171
171
}
172
172
173
173
/// Configuration for the associated tracker.
174
- #[ derive( Debug , Clone , Serialize , Deserialize ) ]
174
+ #[ derive( Debug , Clone , Serialize , Deserialize , PartialEq ) ]
175
175
pub struct Tracker {
176
176
/// Connection string for the tracker. For example: `udp://TRACKER_IP:6969`.
177
177
pub url : String ,
@@ -211,7 +211,7 @@ impl Default for Tracker {
211
211
pub const FREE_PORT : u16 = 0 ;
212
212
213
213
/// The the base URL for the API.
214
- #[ derive( Debug , Clone , Serialize , Deserialize ) ]
214
+ #[ derive( Debug , Clone , Serialize , Deserialize , PartialEq ) ]
215
215
pub struct Network {
216
216
/// The port to listen on. Default to `3001`.
217
217
pub port : u16 ,
@@ -233,7 +233,7 @@ impl Default for Network {
233
233
}
234
234
235
235
/// Whether the email is required on signup or not.
236
- #[ derive( Debug , Clone , Serialize , Deserialize ) ]
236
+ #[ derive( Debug , Clone , Serialize , Deserialize , PartialEq ) ]
237
237
pub enum EmailOnSignup {
238
238
/// The email is required on signup.
239
239
Required ,
@@ -250,7 +250,7 @@ impl Default for EmailOnSignup {
250
250
}
251
251
252
252
/// Authentication options.
253
- #[ derive( Debug , Clone , Serialize , Deserialize ) ]
253
+ #[ derive( Debug , Clone , Serialize , Deserialize , PartialEq ) ]
254
254
pub struct Auth {
255
255
/// Whether or not to require an email on signup.
256
256
pub email_on_signup : EmailOnSignup ,
@@ -280,7 +280,7 @@ impl Auth {
280
280
}
281
281
282
282
/// Database configuration.
283
- #[ derive( Debug , Clone , Serialize , Deserialize ) ]
283
+ #[ derive( Debug , Clone , Serialize , Deserialize , PartialEq ) ]
284
284
pub struct Database {
285
285
/// The connection string for the database. For example: `sqlite://data.db?mode=rwc`.
286
286
pub connect_url : String ,
@@ -295,7 +295,7 @@ impl Default for Database {
295
295
}
296
296
297
297
/// SMTP configuration.
298
- #[ derive( Debug , Clone , Serialize , Deserialize ) ]
298
+ #[ derive( Debug , Clone , Serialize , Deserialize , PartialEq ) ]
299
299
pub struct Mail {
300
300
/// Whether or not to enable email verification on signup.
301
301
pub email_verification_enabled : bool ,
@@ -335,7 +335,7 @@ impl Default for Mail {
335
335
/// proxy. The proxy will not download new images if the user has reached the
336
336
/// quota.
337
337
#[ allow( clippy:: module_name_repetitions) ]
338
- #[ derive( Debug , Clone , Serialize , Deserialize ) ]
338
+ #[ derive( Debug , Clone , Serialize , Deserialize , PartialEq ) ]
339
339
pub struct ImageCache {
340
340
/// Maximum time in seconds to wait for downloading the image form the original source.
341
341
pub max_request_timeout_ms : u64 ,
@@ -352,7 +352,7 @@ pub struct ImageCache {
352
352
}
353
353
354
354
/// Core configuration for the API
355
- #[ derive( Debug , Clone , Serialize , Deserialize ) ]
355
+ #[ derive( Debug , Clone , Serialize , Deserialize , PartialEq ) ]
356
356
pub struct Api {
357
357
/// The default page size for torrent lists.
358
358
pub default_torrent_page_size : u8 ,
@@ -370,7 +370,7 @@ impl Default for Api {
370
370
}
371
371
372
372
/// Configuration for the tracker statistics importer.
373
- #[ derive( Debug , Clone , Serialize , Deserialize ) ]
373
+ #[ derive( Debug , Clone , Serialize , Deserialize , PartialEq ) ]
374
374
pub struct TrackerStatisticsImporter {
375
375
/// The interval in seconds to get statistics from the tracker.
376
376
pub torrent_info_update_interval : u64 ,
@@ -425,7 +425,7 @@ impl Tsl {
425
425
}
426
426
427
427
/// The whole configuration for the index.
428
- #[ derive( Debug , Default , Clone , Serialize , Deserialize ) ]
428
+ #[ derive( Debug , Default , Clone , Serialize , Deserialize , PartialEq ) ]
429
429
pub struct TorrustIndex {
430
430
/// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`,
431
431
/// `Debug` and `Trace`. Default is `Info`.
@@ -637,10 +637,253 @@ fn parse_url(url_str: &str) -> Result<Url, url::ParseError> {
637
637
638
638
/// The public index configuration.
639
639
/// There is an endpoint to get this configuration.
640
- #[ derive( Debug , Clone , Serialize , Deserialize ) ]
640
+ #[ derive( Debug , Clone , Serialize , Deserialize , PartialEq ) ]
641
641
pub struct ConfigurationPublic {
642
642
website_name : String ,
643
643
tracker_url : String ,
644
644
tracker_mode : TrackerMode ,
645
645
email_on_signup : EmailOnSignup ,
646
646
}
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