1use super::NetworkFileConfig;
55use crate::config::ConfigFileError;
56use serde::Deserialize;
57use std::fs;
58use std::path::Path;
59
60#[derive(Deserialize, Debug, Clone)]
62struct DirectoryNetworkList {
63 networks: Vec<NetworkFileConfig>,
64}
65
66pub struct NetworkFileLoader;
67
68impl NetworkFileLoader {
69 pub fn load_networks_from_directory(
78 path: impl AsRef<Path>,
79 ) -> Result<Vec<NetworkFileConfig>, ConfigFileError> {
80 let path = path.as_ref();
81
82 if !path.exists() {
83 return Err(ConfigFileError::InvalidFormat(format!(
84 "Path '{}' does not exist",
85 path.display()
86 )));
87 }
88
89 if !path.is_dir() {
90 return Err(ConfigFileError::InvalidFormat(format!(
91 "Path '{}' is not a directory",
92 path.display()
93 )));
94 }
95
96 Self::validate_directory_has_configs(path)?;
98
99 let mut aggregated_networks = Vec::new();
100
101 let entries = fs::read_dir(path).map_err(|e| {
103 ConfigFileError::InvalidFormat(format!(
104 "Failed to read directory '{}': {}",
105 path.display(),
106 e
107 ))
108 })?;
109
110 for entry_result in entries {
111 let entry = entry_result.map_err(|e| {
112 ConfigFileError::InvalidFormat(format!(
113 "Failed to read directory entry in '{}': {}",
114 path.display(),
115 e
116 ))
117 })?;
118
119 let file_path = entry.path();
120
121 if Self::is_json_file(&file_path) {
123 match Self::load_network_file(&file_path) {
124 Ok(mut networks) => {
125 aggregated_networks.append(&mut networks);
126 }
127 Err(e) => {
128 return Err(ConfigFileError::InvalidFormat(format!(
130 "Failed to load network configuration from file '{}': {}",
131 file_path.display(),
132 e
133 )));
134 }
135 }
136 }
137 }
138
139 Ok(aggregated_networks)
140 }
141
142 fn load_network_file(file_path: &Path) -> Result<Vec<NetworkFileConfig>, ConfigFileError> {
151 let file_content = fs::read_to_string(file_path)
152 .map_err(|e| ConfigFileError::InvalidFormat(format!("Failed to read file: {e}")))?;
153
154 let dir_network_list: DirectoryNetworkList = serde_json::from_str(&file_content)
155 .map_err(|e| ConfigFileError::InvalidFormat(format!("Failed to parse JSON: {e}")))?;
156
157 Ok(dir_network_list.networks)
158 }
159
160 fn is_json_file(path: &Path) -> bool {
169 path.is_file()
170 && path
171 .extension()
172 .and_then(|ext| ext.to_str())
173 .map(|ext| ext.eq_ignore_ascii_case("json"))
174 .unwrap_or(false)
175 }
176
177 pub fn validate_directory_has_configs(path: impl AsRef<Path>) -> Result<(), ConfigFileError> {
186 let path = path.as_ref();
187
188 if !path.is_dir() {
189 return Err(ConfigFileError::InvalidFormat(format!(
190 "Path '{}' is not a directory",
191 path.display()
192 )));
193 }
194
195 let entries = fs::read_dir(path).map_err(|e| {
196 ConfigFileError::InvalidFormat(format!(
197 "Failed to read directory '{}': {}",
198 path.display(),
199 e
200 ))
201 })?;
202
203 let has_json_files = entries
204 .filter_map(|entry| entry.ok())
205 .any(|entry| Self::is_json_file(&entry.path()));
206
207 if !has_json_files {
208 return Err(ConfigFileError::InvalidFormat(format!(
209 "Directory '{}' contains no JSON configuration files",
210 path.display()
211 )));
212 }
213
214 Ok(())
215 }
216
217 pub fn load_from_source(
229 source: NetworksSource,
230 ) -> Result<Vec<NetworkFileConfig>, ConfigFileError> {
231 match source {
232 NetworksSource::List(networks) => Ok(networks),
233 NetworksSource::Path(path_str) => Self::load_networks_from_directory(&path_str),
234 }
235 }
236}
237
238#[derive(Debug, Clone)]
240pub enum NetworksSource {
241 List(Vec<NetworkFileConfig>),
242 Path(String),
243}
244
245impl Default for NetworksSource {
246 fn default() -> Self {
247 NetworksSource::Path("./config/networks".to_string())
248 }
249}
250
251impl<'de> serde::Deserialize<'de> for NetworksSource {
252 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
253 where
254 D: serde::Deserializer<'de>,
255 {
256 use serde::de;
257 use serde_json::Value;
258
259 let value = Value::deserialize(deserializer)?;
261
262 match value {
263 Value::Null => Ok(NetworksSource::default()),
264 Value::String(s) => {
265 if s.is_empty() {
266 Ok(NetworksSource::default())
267 } else {
268 Ok(NetworksSource::Path(s))
269 }
270 }
271 Value::Array(arr) => {
272 let networks: Vec<NetworkFileConfig> = serde_json::from_value(Value::Array(arr))
273 .map_err(|e| {
274 de::Error::custom(format!("Failed to deserialize network array: {e}"))
275 })?;
276 Ok(NetworksSource::List(networks))
277 }
278 _ => Err(de::Error::custom("Expected an array, string, or null")),
279 }
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use crate::config::config_file::network::test_utils::*;
287 use serde_json::json;
288 use std::fs::{create_dir, File};
289 use std::os::unix::fs::PermissionsExt;
290 use tempfile::tempdir;
291
292 #[test]
293 fn test_load_from_single_file() {
294 let dir = tempdir().expect("Failed to create temp dir");
295 let network_data = create_valid_evm_network_json();
296 create_temp_file(&dir, "config1.json", &network_data.to_string());
297
298 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
299 assert!(result.is_ok());
300 let networks = result.unwrap();
301 assert_eq!(networks.len(), 1);
302 assert_eq!(networks[0].network_name(), "test-evm");
303 }
304
305 #[test]
306 fn test_load_from_multiple_files() {
307 let dir = tempdir().expect("Failed to create temp dir");
308 let evm_data = create_valid_evm_network_json();
309 let solana_data = create_valid_solana_network_json();
310
311 create_temp_file(&dir, "evm.json", &evm_data.to_string());
312 create_temp_file(&dir, "solana.json", &solana_data.to_string());
313
314 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
315
316 assert!(result.is_ok());
317 let networks = result.unwrap();
318 assert_eq!(networks.len(), 2);
319
320 let network_names: Vec<&str> = networks.iter().map(|n| n.network_name()).collect();
321 assert!(network_names.contains(&"test-evm"));
322 assert!(network_names.contains(&"test-solana"));
323 }
324
325 #[test]
326 fn test_load_from_directory_multiple_networks_per_file() {
327 let dir = tempdir().expect("Failed to create temp dir");
328
329 let multi_network_data = json!({
330 "networks": [
331 {
332 "type": "evm",
333 "network": "evm-1",
334 "chain_id": 1,
335 "rpc_urls": ["http://localhost:8545"],
336 "symbol": "ETH"
337 },
338 {
339 "type": "evm",
340 "network": "evm-2",
341 "chain_id": 2,
342 "rpc_urls": ["http://localhost:8546"],
343 "symbol": "ETH2"
344 }
345 ]
346 });
347
348 create_temp_file(&dir, "multi.json", &multi_network_data.to_string());
349
350 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
351
352 assert!(result.is_ok());
353 let networks = result.unwrap();
354 assert_eq!(networks.len(), 2);
355 assert_eq!(networks[0].network_name(), "evm-1");
356 assert_eq!(networks[1].network_name(), "evm-2");
357 }
358
359 #[test]
360 fn test_load_from_directory_with_mixed_file_types() {
361 let dir = tempdir().expect("Failed to create temp dir");
362
363 let network_data = create_valid_evm_network_json();
364 create_temp_file(&dir, "config.json", &network_data.to_string());
365 create_temp_file(&dir, "readme.txt", "This is not a JSON file");
366 create_temp_file(&dir, "config.yaml", "networks: []");
367
368 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
369
370 assert!(result.is_ok());
371 let networks = result.unwrap();
372 assert_eq!(networks.len(), 1);
373 assert_eq!(networks[0].network_name(), "test-evm");
374 }
375
376 #[test]
377 fn test_load_from_directory_with_subdirectories() {
378 let dir = tempdir().expect("Failed to create temp dir");
379
380 let network_data = create_valid_evm_network_json();
381 create_temp_file(&dir, "config.json", &network_data.to_string());
382
383 let subdir_path = dir.path().join("subdir");
385 create_dir(&subdir_path).expect("Failed to create subdirectory");
386
387 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
388
389 assert!(result.is_ok());
390 let networks = result.unwrap();
391 assert_eq!(networks.len(), 1);
392 }
393
394 #[test]
395 fn test_load_from_nonexistent_directory() {
396 let dir = tempdir().expect("Failed to create temp dir");
397 let non_existent_path = dir.path().join("non_existent");
398
399 let result = NetworkFileLoader::load_networks_from_directory(&non_existent_path);
400
401 assert!(result.is_err());
402 assert!(matches!(
403 result.unwrap_err(),
404 ConfigFileError::InvalidFormat(_)
405 ));
406 }
407
408 #[test]
409 fn test_load_from_file_instead_of_directory() {
410 let dir = tempdir().expect("Failed to create temp dir");
411 let file_path = dir.path().join("not_a_dir.json");
412 File::create(&file_path).expect("Failed to create file");
413
414 let result = NetworkFileLoader::load_networks_from_directory(&file_path);
415
416 assert!(result.is_err());
417 assert!(matches!(
418 result.unwrap_err(),
419 ConfigFileError::InvalidFormat(_)
420 ));
421 }
422
423 #[test]
424 fn test_load_from_directory_with_no_json_files() {
425 let dir = tempdir().expect("Failed to create temp dir");
426
427 create_temp_file(&dir, "readme.txt", "This is not a JSON file");
428 create_temp_file(&dir, "config.yaml", "networks: []");
429
430 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
431
432 assert!(result.is_err());
433 assert!(matches!(
434 result.unwrap_err(),
435 ConfigFileError::InvalidFormat(_)
436 ));
437 }
438
439 #[test]
440 fn test_load_from_directory_with_invalid_json() {
441 let dir = tempdir().expect("Failed to create temp dir");
442
443 create_temp_file(
444 &dir,
445 "invalid.json",
446 r#"{"networks": [{"type": "evm", "network": "broken""#,
447 );
448
449 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
450
451 assert!(result.is_err());
452 assert!(matches!(
453 result.unwrap_err(),
454 ConfigFileError::InvalidFormat(_)
455 ));
456 }
457
458 #[test]
459 fn test_load_from_directory_with_wrong_json_structure() {
460 let dir = tempdir().expect("Failed to create temp dir");
461
462 create_temp_file(&dir, "wrong.json", r#"{"foo": "bar"}"#);
463
464 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
465
466 assert!(result.is_err());
467 assert!(matches!(
468 result.unwrap_err(),
469 ConfigFileError::InvalidFormat(_)
470 ));
471 }
472
473 #[test]
474 fn test_load_from_directory_with_empty_networks_array() {
475 let dir = tempdir().expect("Failed to create temp dir");
476
477 create_temp_file(&dir, "empty.json", r#"{"networks": []}"#);
478
479 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
480
481 assert!(result.is_ok());
482 let networks = result.unwrap();
483 assert_eq!(networks.len(), 0);
484 }
485
486 #[test]
487 fn test_load_from_directory_with_invalid_network_structure() {
488 let dir = tempdir().expect("Failed to create temp dir");
489
490 let invalid_network = create_invalid_network_json();
491
492 create_temp_file(&dir, "invalid_network.json", &invalid_network.to_string());
493
494 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
495
496 assert!(result.is_err());
497 assert!(matches!(
498 result.unwrap_err(),
499 ConfigFileError::InvalidFormat(_)
500 ));
501 }
502
503 #[test]
504 fn test_load_from_directory_partial_failure() {
505 let dir = tempdir().expect("Failed to create temp dir");
506
507 let valid_data = create_valid_evm_network_json();
508 create_temp_file(&dir, "valid.json", &valid_data.to_string());
509 create_temp_file(&dir, "invalid.json", r#"{"networks": [malformed"#);
510
511 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
512
513 assert!(result.is_err());
515 assert!(matches!(
516 result.unwrap_err(),
517 ConfigFileError::InvalidFormat(_)
518 ));
519 }
520
521 #[test]
522 fn test_is_json_file() {
523 let dir = tempdir().expect("Failed to create temp dir");
524
525 let json_file = dir.path().join("config.json");
526 File::create(&json_file).expect("Failed to create JSON file");
527 assert!(NetworkFileLoader::is_json_file(&json_file));
528
529 let txt_file = dir.path().join("config.txt");
530 File::create(&txt_file).expect("Failed to create TXT file");
531 assert!(!NetworkFileLoader::is_json_file(&txt_file));
532
533 let json_upper_file = dir.path().join("config.JSON");
534 File::create(&json_upper_file).expect("Failed to create JSON file");
535 assert!(NetworkFileLoader::is_json_file(&json_upper_file));
536
537 let no_extension_file = dir.path().join("config");
538 File::create(&no_extension_file).expect("Failed to create file without extension");
539 assert!(!NetworkFileLoader::is_json_file(&no_extension_file));
540
541 let subdir = dir.path().join("subdir");
543 create_dir(&subdir).expect("Failed to create subdirectory");
544 assert!(!NetworkFileLoader::is_json_file(&subdir));
545 }
546
547 #[test]
548 fn test_validate_directory_has_configs() {
549 let dir = tempdir().expect("Failed to create temp dir");
550
551 let result = NetworkFileLoader::validate_directory_has_configs(dir.path());
553 assert!(result.is_err());
554 assert!(matches!(
555 result.unwrap_err(),
556 ConfigFileError::InvalidFormat(_)
557 ));
558
559 create_temp_file(&dir, "readme.txt", "Not JSON");
561 let result = NetworkFileLoader::validate_directory_has_configs(dir.path());
562 assert!(result.is_err());
563
564 create_temp_file(&dir, "config.json", r#"{"networks": []}"#);
566 let result = NetworkFileLoader::validate_directory_has_configs(dir.path());
567 assert!(result.is_ok());
568 }
569
570 #[test]
571 fn test_validate_directory_has_configs_with_file_path() {
572 let dir = tempdir().expect("Failed to create temp dir");
573 let file_path = dir.path().join("not_a_dir.json");
574 File::create(&file_path).expect("Failed to create file");
575
576 let result = NetworkFileLoader::validate_directory_has_configs(&file_path);
577
578 assert!(result.is_err());
579 assert!(matches!(
580 result.unwrap_err(),
581 ConfigFileError::InvalidFormat(_)
582 ));
583 }
584
585 #[test]
586 fn test_load_from_source_with_list() {
587 let networks = vec![]; let source = NetworksSource::List(networks.clone());
589
590 let result = NetworkFileLoader::load_from_source(source);
591
592 assert!(result.is_ok());
593 assert_eq!(result.unwrap().len(), 0);
594 }
595
596 #[test]
597 fn test_load_from_source_with_path() {
598 let dir = tempdir().expect("Failed to create temp dir");
599 let network_data = create_valid_evm_network_json();
600 create_temp_file(&dir, "config.json", &network_data.to_string());
601
602 let path_str = dir
603 .path()
604 .to_str()
605 .expect("Path should be valid UTF-8")
606 .to_string();
607 let source = NetworksSource::Path(path_str);
608
609 let result = NetworkFileLoader::load_from_source(source);
610
611 assert!(result.is_ok());
612 let networks = result.unwrap();
613 assert_eq!(networks.len(), 1);
614 assert_eq!(networks[0].network_name(), "test-evm");
615 }
616
617 #[test]
618 fn test_load_from_source_with_invalid_path() {
619 let source = NetworksSource::Path("/non/existent/path".to_string());
620
621 let result = NetworkFileLoader::load_from_source(source);
622
623 assert!(result.is_err());
624 assert!(matches!(
625 result.unwrap_err(),
626 ConfigFileError::InvalidFormat(_)
627 ));
628 }
629
630 #[test]
631 fn test_load_from_directory_with_unicode_filenames() {
632 let dir = tempdir().expect("Failed to create temp dir");
633
634 let network_data = create_valid_evm_network_json();
635 create_temp_file(&dir, "配置.json", &network_data.to_string());
636 create_temp_file(&dir, "конфиг.json", &network_data.to_string());
637
638 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
639
640 assert!(result.is_ok());
641 let networks = result.unwrap();
642 assert_eq!(networks.len(), 2);
643 }
644
645 #[test]
646 fn test_load_from_directory_with_unicode_content() {
647 let dir = tempdir().expect("Failed to create temp dir");
648
649 let unicode_network = json!({
650 "networks": [
651 {
652 "type": "evm",
653 "network": "测试网络",
654 "chain_id": 1,
655 "rpc_urls": ["http://localhost:8545"],
656 "symbol": "ETH"
657 }
658 ]
659 });
660
661 create_temp_file(&dir, "unicode.json", &unicode_network.to_string());
662
663 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
664
665 assert!(result.is_ok());
666 let networks = result.unwrap();
667 assert_eq!(networks.len(), 1);
668 assert_eq!(networks[0].network_name(), "测试网络");
669 }
670
671 #[test]
672 fn test_load_from_directory_with_json_extension_but_invalid_content() {
673 let dir = tempdir().expect("Failed to create temp dir");
674
675 create_temp_file(&dir, "fake.json", "This is not JSON content at all!");
676
677 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
678
679 assert!(result.is_err());
680 assert!(matches!(
681 result.unwrap_err(),
682 ConfigFileError::InvalidFormat(_)
683 ));
684 }
685
686 #[test]
687 fn test_load_from_directory_with_large_number_of_files() {
688 let dir = tempdir().expect("Failed to create temp dir");
689
690 for i in 0..100 {
692 let network_data = json!({
693 "networks": [
694 {
695 "type": "evm",
696 "network": format!("test-network-{}", i),
697 "chain_id": i + 1,
698 "rpc_urls": [format!("http://localhost:{}", 8545 + i)],
699 "symbol": "ETH"
700 }
701 ]
702 });
703 create_temp_file(&dir, &format!("config_{i}.json"), &network_data.to_string());
704 }
705
706 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
707
708 assert!(result.is_ok());
709 let networks = result.unwrap();
710 assert_eq!(networks.len(), 100);
711 }
712
713 #[test]
714 fn test_networks_source_deserialization() {
715 let list_json = r#"[{"type": "evm", "network": "test", "chain_id": 1, "rpc_urls": ["http://localhost:8545"], "symbol": "ETH", "required_confirmations": 1}]"#;
717 let source: NetworksSource =
718 serde_json::from_str(list_json).expect("Failed to deserialize list");
719
720 match source {
721 NetworksSource::List(networks) => {
722 assert_eq!(networks.len(), 1);
723 assert_eq!(networks[0].network_name(), "test");
724 }
725 NetworksSource::Path(_) => panic!("Expected List variant"),
726 }
727
728 let path_json = r#""/path/to/configs""#;
730 let source: NetworksSource =
731 serde_json::from_str(path_json).expect("Failed to deserialize path");
732
733 match source {
734 NetworksSource::Path(path) => {
735 assert_eq!(path, "/path/to/configs");
736 }
737 NetworksSource::List(_) => panic!("Expected Path variant"),
738 }
739 }
740
741 #[cfg(unix)]
742 #[test]
743 fn test_load_from_directory_with_permission_issues() {
744 let dir = tempdir().expect("Failed to create temp dir");
745 let network_data = create_valid_evm_network_json();
746 create_temp_file(&dir, "config.json", &network_data.to_string());
747
748 let mut perms = std::fs::metadata(dir.path())
750 .expect("Failed to get metadata")
751 .permissions();
752 perms.set_mode(0o000);
753 std::fs::set_permissions(dir.path(), perms).expect("Failed to set permissions");
754
755 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
756
757 let mut perms = std::fs::metadata(dir.path())
759 .expect("Failed to get metadata")
760 .permissions();
761 perms.set_mode(0o755);
762 std::fs::set_permissions(dir.path(), perms).expect("Failed to restore permissions");
763
764 assert!(result.is_err());
765 assert!(matches!(
766 result.unwrap_err(),
767 ConfigFileError::InvalidFormat(_)
768 ));
769 }
770
771 #[test]
772 fn test_validate_directory_has_configs_with_nonexistent_directory() {
773 let dir = tempdir().expect("Failed to create temp dir");
774 let non_existent_path = dir.path().join("non_existent");
775
776 let result = NetworkFileLoader::validate_directory_has_configs(&non_existent_path);
777
778 assert!(result.is_err());
779 assert!(matches!(
780 result.unwrap_err(),
781 ConfigFileError::InvalidFormat(_)
782 ));
783 }
784
785 #[test]
786 fn test_is_json_file_with_nonexistent_file() {
787 let dir = tempdir().expect("Failed to create temp dir");
788 let non_existent_file = dir.path().join("nonexistent.json");
789
790 assert!(!NetworkFileLoader::is_json_file(&non_existent_file));
792 }
793
794 #[cfg(unix)]
795 #[test]
796 fn test_load_from_directory_with_file_permission_issues() {
797 let dir = tempdir().expect("Failed to create temp dir");
798 let network_data = create_valid_evm_network_json();
799 create_temp_file(&dir, "config.json", &network_data.to_string());
800
801 let file_path = dir.path().join("config.json");
803 let mut perms = std::fs::metadata(&file_path)
804 .expect("Failed to get file metadata")
805 .permissions();
806 perms.set_mode(0o000);
807 std::fs::set_permissions(&file_path, perms).expect("Failed to set file permissions");
808
809 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
810
811 assert!(result.is_err());
812 assert!(matches!(
813 result.unwrap_err(),
814 ConfigFileError::InvalidFormat(_)
815 ));
816 }
817
818 #[test]
819 fn test_load_from_directory_empty_directory() {
820 let dir = tempdir().expect("Failed to create temp dir");
821
822 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
824
825 assert!(result.is_err());
826 assert!(matches!(
827 result.unwrap_err(),
828 ConfigFileError::InvalidFormat(_)
829 ));
830 }
831
832 #[test]
833 fn test_load_from_directory_with_json_containing_extra_fields() {
834 let dir = tempdir().expect("Failed to create temp dir");
835
836 let network_with_extra_fields = json!({
838 "networks": [
839 {
840 "type": "evm",
841 "network": "test-with-extra",
842 "chain_id": 1,
843 "rpc_urls": ["http://localhost:8545"],
844 "symbol": "ETH",
845 "extra_field": "should_cause_error"
846 }
847 ]
848 });
849
850 create_temp_file(
851 &dir,
852 "extra_fields.json",
853 &network_with_extra_fields.to_string(),
854 );
855
856 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
857
858 assert!(result.is_err());
860 assert!(matches!(
861 result.unwrap_err(),
862 ConfigFileError::InvalidFormat(_)
863 ));
864 }
865
866 #[test]
867 fn test_load_from_directory_with_json_containing_extra_top_level_fields() {
868 let dir = tempdir().expect("Failed to create temp dir");
869
870 let network_with_extra_top_level = json!({
872 "networks": [
873 {
874 "type": "evm",
875 "network": "test-with-extra-top",
876 "chain_id": 1,
877 "rpc_urls": ["http://localhost:8545"],
878 "symbol": "ETH",
879 "required_confirmations": 1
880 }
881 ],
882 "extra_top_level": "ignored",
883 "another_extra": 42
884 });
885
886 create_temp_file(
887 &dir,
888 "extra_top_level.json",
889 &network_with_extra_top_level.to_string(),
890 );
891
892 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
893
894 assert!(result.is_ok());
896 let networks = result.unwrap();
897 assert_eq!(networks.len(), 1);
898 assert_eq!(networks[0].network_name(), "test-with-extra-top");
899 }
900
901 #[test]
902 fn test_load_from_directory_with_very_large_json() {
903 let dir = tempdir().expect("Failed to create temp dir");
904
905 let mut networks_array = Vec::new();
906 for i in 0..1000 {
907 networks_array.push(json!({
908 "type": "evm",
909 "network": format!("large-test-{}", i),
910 "chain_id": i + 1,
911 "rpc_urls": [format!("http://localhost:{}", 8545 + i)],
912 "symbol": "ETH"
913 }));
914 }
915
916 let large_json = json!({
917 "networks": networks_array
918 });
919
920 create_temp_file(&dir, "large.json", &large_json.to_string());
921
922 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
923
924 assert!(result.is_ok());
925 let networks = result.unwrap();
926 assert_eq!(networks.len(), 1000);
927 }
928
929 #[test]
930 fn test_load_from_directory_with_deeply_nested_json() {
931 let dir = tempdir().expect("Failed to create temp dir");
932
933 let complex_network = json!({
934 "networks": [
935 {
936 "type": "evm",
937 "network": "complex-nested",
938 "chain_id": 1,
939 "rpc_urls": ["http://localhost:8545"],
940 "symbol": "ETH",
941 "tags": ["mainnet", "production", "high-security"]
942 }
943 ]
944 });
945
946 create_temp_file(&dir, "complex.json", &complex_network.to_string());
947
948 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
949
950 assert!(result.is_ok());
951 let networks = result.unwrap();
952 assert_eq!(networks.len(), 1);
953 assert_eq!(networks[0].network_name(), "complex-nested");
954 }
955
956 #[test]
957 fn test_load_from_directory_with_null_values() {
958 let dir = tempdir().expect("Failed to create temp dir");
959
960 let network_with_nulls = json!({
962 "networks": [
963 {
964 "type": "evm",
965 "network": "test-nulls",
966 "chain_id": 1,
967 "rpc_urls": ["http://localhost:8545"],
968 "symbol": "ETH",
969 "tags": null,
970 "features": null
971 }
972 ]
973 });
974
975 create_temp_file(&dir, "nulls.json", &network_with_nulls.to_string());
976
977 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
978
979 assert!(result.is_ok());
980 let networks = result.unwrap();
981 assert_eq!(networks.len(), 1);
982 assert_eq!(networks[0].network_name(), "test-nulls");
983 }
984
985 #[test]
986 fn test_load_from_directory_with_special_characters_in_content() {
987 let dir = tempdir().expect("Failed to create temp dir");
988
989 let special_chars_network = json!({
990 "networks": [
991 {
992 "type": "evm",
993 "network": "test-special-chars-\n\t\r\"\\",
994 "chain_id": 1,
995 "rpc_urls": ["http://localhost:8545"],
996 "symbol": "ETH"
997 }
998 ]
999 });
1000
1001 create_temp_file(&dir, "special.json", &special_chars_network.to_string());
1002
1003 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
1004
1005 assert!(result.is_ok());
1006 let networks = result.unwrap();
1007 assert_eq!(networks.len(), 1);
1008 assert_eq!(networks[0].network_name(), "test-special-chars-\n\t\r\"\\");
1009 }
1010
1011 #[cfg(unix)]
1012 #[test]
1013 fn test_load_from_directory_with_symbolic_links() {
1014 let dir = tempdir().expect("Failed to create temp dir");
1015 let network_data = create_valid_evm_network_json();
1016
1017 create_temp_file(&dir, "regular.json", &network_data.to_string());
1018
1019 let regular_path = dir.path().join("regular.json");
1021 let symlink_path = dir.path().join("symlink.json");
1022
1023 if std::os::unix::fs::symlink(®ular_path, &symlink_path).is_ok() {
1024 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
1025
1026 assert!(result.is_ok());
1027 let networks = result.unwrap();
1028 assert_eq!(networks.len(), 2);
1030 }
1031 }
1032
1033 #[test]
1034 fn test_load_from_source_with_list_containing_networks() {
1035 let evm_network_json = create_valid_evm_network_json();
1037 let networks: Vec<NetworkFileConfig> =
1038 serde_json::from_value(evm_network_json["networks"].clone())
1039 .expect("Failed to deserialize networks");
1040
1041 let source = NetworksSource::List(networks.clone());
1042 let result = NetworkFileLoader::load_from_source(source);
1043
1044 assert!(result.is_ok());
1045 let loaded_networks = result.unwrap();
1046 assert_eq!(loaded_networks.len(), 1);
1047 assert_eq!(loaded_networks[0].network_name(), "test-evm");
1048 }
1049
1050 #[test]
1051 fn test_directory_network_list_deserialization() {
1052 let json_str = r#"{"networks": []}"#;
1054 let result: Result<DirectoryNetworkList, _> = serde_json::from_str(json_str);
1055 assert!(result.is_ok());
1056 assert_eq!(result.unwrap().networks.len(), 0);
1057
1058 let invalid_json = r#"{"not_networks": []}"#;
1060 let result: Result<DirectoryNetworkList, _> = serde_json::from_str(invalid_json);
1061 assert!(result.is_err());
1062 }
1063
1064 #[test]
1065 fn test_networks_source_clone_and_debug() {
1066 let source = NetworksSource::Path("/test/path".to_string());
1068 let cloned = source.clone();
1069
1070 match (source, cloned) {
1071 (NetworksSource::Path(path1), NetworksSource::Path(path2)) => {
1072 assert_eq!(path1, path2);
1073 }
1074 _ => panic!("Clone didn't preserve variant"),
1075 }
1076
1077 let source = NetworksSource::List(vec![]);
1079 let debug_str = format!("{source:?}");
1080 assert!(debug_str.contains("List"));
1081 }
1082
1083 #[test]
1084 fn test_is_json_file_edge_cases() {
1085 let dir = tempdir().expect("Failed to create temp dir");
1086
1087 let misleading_file = dir.path().join("config.json.backup");
1089 File::create(&misleading_file).expect("Failed to create misleading file");
1090 assert!(!NetworkFileLoader::is_json_file(&misleading_file));
1091
1092 let multi_dot_file = dir.path().join("config.test.json");
1094 File::create(&multi_dot_file).expect("Failed to create multi-dot file");
1095 assert!(NetworkFileLoader::is_json_file(&multi_dot_file));
1096
1097 let mixed_case_file = dir.path().join("config.Json");
1099 File::create(&mixed_case_file).expect("Failed to create mixed case file");
1100 assert!(NetworkFileLoader::is_json_file(&mixed_case_file));
1101 }
1102
1103 #[cfg(unix)]
1104 #[test]
1105 fn test_validate_directory_has_configs_with_permission_issues() {
1106 let dir = tempdir().expect("Failed to create temp dir");
1107 create_temp_file(&dir, "config.json", r#"{"networks": []}"#);
1108
1109 let mut perms = std::fs::metadata(dir.path())
1111 .expect("Failed to get metadata")
1112 .permissions();
1113 perms.set_mode(0o000);
1114 std::fs::set_permissions(dir.path(), perms).expect("Failed to set permissions");
1115
1116 let result = NetworkFileLoader::validate_directory_has_configs(dir.path());
1117
1118 assert!(result.is_err());
1119 assert!(matches!(
1120 result.unwrap_err(),
1121 ConfigFileError::InvalidFormat(_)
1122 ));
1123 }
1124
1125 #[test]
1126 fn test_networks_source_default() {
1127 let default_source = NetworksSource::default();
1128 match default_source {
1129 NetworksSource::Path(path) => {
1130 assert_eq!(path, "./config/networks");
1131 }
1132 _ => panic!("Default should be a Path variant"),
1133 }
1134 }
1135
1136 #[test]
1137 fn test_networks_source_deserialize_null() {
1138 let json = r#"null"#;
1139 let result: Result<NetworksSource, _> = serde_json::from_str(json);
1140 assert!(result.is_ok());
1141
1142 match result.unwrap() {
1143 NetworksSource::Path(path) => {
1144 assert_eq!(path, "./config/networks");
1145 }
1146 _ => panic!("Expected default Path variant"),
1147 }
1148 }
1149
1150 #[test]
1151 fn test_networks_source_deserialize_empty_string() {
1152 let json = r#""""#;
1153 let result: Result<NetworksSource, _> = serde_json::from_str(json);
1154 assert!(result.is_ok());
1155
1156 match result.unwrap() {
1157 NetworksSource::Path(path) => {
1158 assert_eq!(path, "./config/networks");
1159 }
1160 _ => panic!("Expected default Path variant"),
1161 }
1162 }
1163
1164 #[test]
1165 fn test_networks_source_deserialize_valid_path() {
1166 let json = r#""/custom/path""#;
1167 let result: Result<NetworksSource, _> = serde_json::from_str(json);
1168 assert!(result.is_ok());
1169
1170 match result.unwrap() {
1171 NetworksSource::Path(path) => {
1172 assert_eq!(path, "/custom/path");
1173 }
1174 _ => panic!("Expected Path variant"),
1175 }
1176 }
1177
1178 #[test]
1179 fn test_networks_source_deserialize_array() {
1180 let json = r#"[{"type": "evm", "network": "test", "chain_id": 1, "rpc_urls": ["http://localhost:8545"], "symbol": "ETH", "required_confirmations": 1}]"#;
1181 let result: Result<NetworksSource, _> = serde_json::from_str(json);
1182 assert!(result.is_ok());
1183
1184 match result.unwrap() {
1185 NetworksSource::List(networks) => {
1186 assert_eq!(networks.len(), 1);
1187 assert_eq!(networks[0].network_name(), "test");
1188 }
1189 _ => panic!("Expected List variant"),
1190 }
1191 }
1192
1193 #[test]
1194 fn test_networks_source_deserialize_invalid_type() {
1195 let json = r#"42"#;
1196 let result: Result<NetworksSource, _> = serde_json::from_str(json);
1197 assert!(result.is_err());
1198 }
1199}