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(
704 &dir,
705 &format!("config_{}.json", i),
706 &network_data.to_string(),
707 );
708 }
709
710 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
711
712 assert!(result.is_ok());
713 let networks = result.unwrap();
714 assert_eq!(networks.len(), 100);
715 }
716
717 #[test]
718 fn test_networks_source_deserialization() {
719 let list_json = r#"[{"type": "evm", "network": "test", "chain_id": 1, "rpc_urls": ["http://localhost:8545"], "symbol": "ETH", "required_confirmations": 1}]"#;
721 let source: NetworksSource =
722 serde_json::from_str(list_json).expect("Failed to deserialize list");
723
724 match source {
725 NetworksSource::List(networks) => {
726 assert_eq!(networks.len(), 1);
727 assert_eq!(networks[0].network_name(), "test");
728 }
729 NetworksSource::Path(_) => panic!("Expected List variant"),
730 }
731
732 let path_json = r#""/path/to/configs""#;
734 let source: NetworksSource =
735 serde_json::from_str(path_json).expect("Failed to deserialize path");
736
737 match source {
738 NetworksSource::Path(path) => {
739 assert_eq!(path, "/path/to/configs");
740 }
741 NetworksSource::List(_) => panic!("Expected Path variant"),
742 }
743 }
744
745 #[cfg(unix)]
746 #[test]
747 fn test_load_from_directory_with_permission_issues() {
748 let dir = tempdir().expect("Failed to create temp dir");
749 let network_data = create_valid_evm_network_json();
750 create_temp_file(&dir, "config.json", &network_data.to_string());
751
752 let mut perms = std::fs::metadata(dir.path())
754 .expect("Failed to get metadata")
755 .permissions();
756 perms.set_mode(0o000);
757 std::fs::set_permissions(dir.path(), perms).expect("Failed to set permissions");
758
759 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
760
761 let mut perms = std::fs::metadata(dir.path())
763 .expect("Failed to get metadata")
764 .permissions();
765 perms.set_mode(0o755);
766 std::fs::set_permissions(dir.path(), perms).expect("Failed to restore permissions");
767
768 assert!(result.is_err());
769 assert!(matches!(
770 result.unwrap_err(),
771 ConfigFileError::InvalidFormat(_)
772 ));
773 }
774
775 #[test]
776 fn test_validate_directory_has_configs_with_nonexistent_directory() {
777 let dir = tempdir().expect("Failed to create temp dir");
778 let non_existent_path = dir.path().join("non_existent");
779
780 let result = NetworkFileLoader::validate_directory_has_configs(&non_existent_path);
781
782 assert!(result.is_err());
783 assert!(matches!(
784 result.unwrap_err(),
785 ConfigFileError::InvalidFormat(_)
786 ));
787 }
788
789 #[test]
790 fn test_is_json_file_with_nonexistent_file() {
791 let dir = tempdir().expect("Failed to create temp dir");
792 let non_existent_file = dir.path().join("nonexistent.json");
793
794 assert!(!NetworkFileLoader::is_json_file(&non_existent_file));
796 }
797
798 #[cfg(unix)]
799 #[test]
800 fn test_load_from_directory_with_file_permission_issues() {
801 let dir = tempdir().expect("Failed to create temp dir");
802 let network_data = create_valid_evm_network_json();
803 create_temp_file(&dir, "config.json", &network_data.to_string());
804
805 let file_path = dir.path().join("config.json");
807 let mut perms = std::fs::metadata(&file_path)
808 .expect("Failed to get file metadata")
809 .permissions();
810 perms.set_mode(0o000);
811 std::fs::set_permissions(&file_path, perms).expect("Failed to set file permissions");
812
813 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
814
815 assert!(result.is_err());
816 assert!(matches!(
817 result.unwrap_err(),
818 ConfigFileError::InvalidFormat(_)
819 ));
820 }
821
822 #[test]
823 fn test_load_from_directory_empty_directory() {
824 let dir = tempdir().expect("Failed to create temp dir");
825
826 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
828
829 assert!(result.is_err());
830 assert!(matches!(
831 result.unwrap_err(),
832 ConfigFileError::InvalidFormat(_)
833 ));
834 }
835
836 #[test]
837 fn test_load_from_directory_with_json_containing_extra_fields() {
838 let dir = tempdir().expect("Failed to create temp dir");
839
840 let network_with_extra_fields = json!({
842 "networks": [
843 {
844 "type": "evm",
845 "network": "test-with-extra",
846 "chain_id": 1,
847 "rpc_urls": ["http://localhost:8545"],
848 "symbol": "ETH",
849 "extra_field": "should_cause_error"
850 }
851 ]
852 });
853
854 create_temp_file(
855 &dir,
856 "extra_fields.json",
857 &network_with_extra_fields.to_string(),
858 );
859
860 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
861
862 assert!(result.is_err());
864 assert!(matches!(
865 result.unwrap_err(),
866 ConfigFileError::InvalidFormat(_)
867 ));
868 }
869
870 #[test]
871 fn test_load_from_directory_with_json_containing_extra_top_level_fields() {
872 let dir = tempdir().expect("Failed to create temp dir");
873
874 let network_with_extra_top_level = json!({
876 "networks": [
877 {
878 "type": "evm",
879 "network": "test-with-extra-top",
880 "chain_id": 1,
881 "rpc_urls": ["http://localhost:8545"],
882 "symbol": "ETH",
883 "required_confirmations": 1
884 }
885 ],
886 "extra_top_level": "ignored",
887 "another_extra": 42
888 });
889
890 create_temp_file(
891 &dir,
892 "extra_top_level.json",
893 &network_with_extra_top_level.to_string(),
894 );
895
896 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
897
898 assert!(result.is_ok());
900 let networks = result.unwrap();
901 assert_eq!(networks.len(), 1);
902 assert_eq!(networks[0].network_name(), "test-with-extra-top");
903 }
904
905 #[test]
906 fn test_load_from_directory_with_very_large_json() {
907 let dir = tempdir().expect("Failed to create temp dir");
908
909 let mut networks_array = Vec::new();
910 for i in 0..1000 {
911 networks_array.push(json!({
912 "type": "evm",
913 "network": format!("large-test-{}", i),
914 "chain_id": i + 1,
915 "rpc_urls": [format!("http://localhost:{}", 8545 + i)],
916 "symbol": "ETH"
917 }));
918 }
919
920 let large_json = json!({
921 "networks": networks_array
922 });
923
924 create_temp_file(&dir, "large.json", &large_json.to_string());
925
926 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
927
928 assert!(result.is_ok());
929 let networks = result.unwrap();
930 assert_eq!(networks.len(), 1000);
931 }
932
933 #[test]
934 fn test_load_from_directory_with_deeply_nested_json() {
935 let dir = tempdir().expect("Failed to create temp dir");
936
937 let complex_network = json!({
938 "networks": [
939 {
940 "type": "evm",
941 "network": "complex-nested",
942 "chain_id": 1,
943 "rpc_urls": ["http://localhost:8545"],
944 "symbol": "ETH",
945 "tags": ["mainnet", "production", "high-security"]
946 }
947 ]
948 });
949
950 create_temp_file(&dir, "complex.json", &complex_network.to_string());
951
952 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
953
954 assert!(result.is_ok());
955 let networks = result.unwrap();
956 assert_eq!(networks.len(), 1);
957 assert_eq!(networks[0].network_name(), "complex-nested");
958 }
959
960 #[test]
961 fn test_load_from_directory_with_null_values() {
962 let dir = tempdir().expect("Failed to create temp dir");
963
964 let network_with_nulls = json!({
966 "networks": [
967 {
968 "type": "evm",
969 "network": "test-nulls",
970 "chain_id": 1,
971 "rpc_urls": ["http://localhost:8545"],
972 "symbol": "ETH",
973 "tags": null,
974 "features": null
975 }
976 ]
977 });
978
979 create_temp_file(&dir, "nulls.json", &network_with_nulls.to_string());
980
981 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
982
983 assert!(result.is_ok());
984 let networks = result.unwrap();
985 assert_eq!(networks.len(), 1);
986 assert_eq!(networks[0].network_name(), "test-nulls");
987 }
988
989 #[test]
990 fn test_load_from_directory_with_special_characters_in_content() {
991 let dir = tempdir().expect("Failed to create temp dir");
992
993 let special_chars_network = json!({
994 "networks": [
995 {
996 "type": "evm",
997 "network": "test-special-chars-\n\t\r\"\\",
998 "chain_id": 1,
999 "rpc_urls": ["http://localhost:8545"],
1000 "symbol": "ETH"
1001 }
1002 ]
1003 });
1004
1005 create_temp_file(&dir, "special.json", &special_chars_network.to_string());
1006
1007 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
1008
1009 assert!(result.is_ok());
1010 let networks = result.unwrap();
1011 assert_eq!(networks.len(), 1);
1012 assert_eq!(networks[0].network_name(), "test-special-chars-\n\t\r\"\\");
1013 }
1014
1015 #[cfg(unix)]
1016 #[test]
1017 fn test_load_from_directory_with_symbolic_links() {
1018 let dir = tempdir().expect("Failed to create temp dir");
1019 let network_data = create_valid_evm_network_json();
1020
1021 create_temp_file(&dir, "regular.json", &network_data.to_string());
1022
1023 let regular_path = dir.path().join("regular.json");
1025 let symlink_path = dir.path().join("symlink.json");
1026
1027 if std::os::unix::fs::symlink(®ular_path, &symlink_path).is_ok() {
1028 let result = NetworkFileLoader::load_networks_from_directory(dir.path());
1029
1030 assert!(result.is_ok());
1031 let networks = result.unwrap();
1032 assert_eq!(networks.len(), 2);
1034 }
1035 }
1036
1037 #[test]
1038 fn test_load_from_source_with_list_containing_networks() {
1039 let evm_network_json = create_valid_evm_network_json();
1041 let networks: Vec<NetworkFileConfig> =
1042 serde_json::from_value(evm_network_json["networks"].clone())
1043 .expect("Failed to deserialize networks");
1044
1045 let source = NetworksSource::List(networks.clone());
1046 let result = NetworkFileLoader::load_from_source(source);
1047
1048 assert!(result.is_ok());
1049 let loaded_networks = result.unwrap();
1050 assert_eq!(loaded_networks.len(), 1);
1051 assert_eq!(loaded_networks[0].network_name(), "test-evm");
1052 }
1053
1054 #[test]
1055 fn test_directory_network_list_deserialization() {
1056 let json_str = r#"{"networks": []}"#;
1058 let result: Result<DirectoryNetworkList, _> = serde_json::from_str(json_str);
1059 assert!(result.is_ok());
1060 assert_eq!(result.unwrap().networks.len(), 0);
1061
1062 let invalid_json = r#"{"not_networks": []}"#;
1064 let result: Result<DirectoryNetworkList, _> = serde_json::from_str(invalid_json);
1065 assert!(result.is_err());
1066 }
1067
1068 #[test]
1069 fn test_networks_source_clone_and_debug() {
1070 let source = NetworksSource::Path("/test/path".to_string());
1072 let cloned = source.clone();
1073
1074 match (source, cloned) {
1075 (NetworksSource::Path(path1), NetworksSource::Path(path2)) => {
1076 assert_eq!(path1, path2);
1077 }
1078 _ => panic!("Clone didn't preserve variant"),
1079 }
1080
1081 let source = NetworksSource::List(vec![]);
1083 let debug_str = format!("{:?}", source);
1084 assert!(debug_str.contains("List"));
1085 }
1086
1087 #[test]
1088 fn test_is_json_file_edge_cases() {
1089 let dir = tempdir().expect("Failed to create temp dir");
1090
1091 let misleading_file = dir.path().join("config.json.backup");
1093 File::create(&misleading_file).expect("Failed to create misleading file");
1094 assert!(!NetworkFileLoader::is_json_file(&misleading_file));
1095
1096 let multi_dot_file = dir.path().join("config.test.json");
1098 File::create(&multi_dot_file).expect("Failed to create multi-dot file");
1099 assert!(NetworkFileLoader::is_json_file(&multi_dot_file));
1100
1101 let mixed_case_file = dir.path().join("config.Json");
1103 File::create(&mixed_case_file).expect("Failed to create mixed case file");
1104 assert!(NetworkFileLoader::is_json_file(&mixed_case_file));
1105 }
1106
1107 #[cfg(unix)]
1108 #[test]
1109 fn test_validate_directory_has_configs_with_permission_issues() {
1110 let dir = tempdir().expect("Failed to create temp dir");
1111 create_temp_file(&dir, "config.json", r#"{"networks": []}"#);
1112
1113 let mut perms = std::fs::metadata(dir.path())
1115 .expect("Failed to get metadata")
1116 .permissions();
1117 perms.set_mode(0o000);
1118 std::fs::set_permissions(dir.path(), perms).expect("Failed to set permissions");
1119
1120 let result = NetworkFileLoader::validate_directory_has_configs(dir.path());
1121
1122 assert!(result.is_err());
1123 assert!(matches!(
1124 result.unwrap_err(),
1125 ConfigFileError::InvalidFormat(_)
1126 ));
1127 }
1128
1129 #[test]
1130 fn test_networks_source_default() {
1131 let default_source = NetworksSource::default();
1132 match default_source {
1133 NetworksSource::Path(path) => {
1134 assert_eq!(path, "./config/networks");
1135 }
1136 _ => panic!("Default should be a Path variant"),
1137 }
1138 }
1139
1140 #[test]
1141 fn test_networks_source_deserialize_null() {
1142 let json = r#"null"#;
1143 let result: Result<NetworksSource, _> = serde_json::from_str(json);
1144 assert!(result.is_ok());
1145
1146 match result.unwrap() {
1147 NetworksSource::Path(path) => {
1148 assert_eq!(path, "./config/networks");
1149 }
1150 _ => panic!("Expected default Path variant"),
1151 }
1152 }
1153
1154 #[test]
1155 fn test_networks_source_deserialize_empty_string() {
1156 let json = r#""""#;
1157 let result: Result<NetworksSource, _> = serde_json::from_str(json);
1158 assert!(result.is_ok());
1159
1160 match result.unwrap() {
1161 NetworksSource::Path(path) => {
1162 assert_eq!(path, "./config/networks");
1163 }
1164 _ => panic!("Expected default Path variant"),
1165 }
1166 }
1167
1168 #[test]
1169 fn test_networks_source_deserialize_valid_path() {
1170 let json = r#""/custom/path""#;
1171 let result: Result<NetworksSource, _> = serde_json::from_str(json);
1172 assert!(result.is_ok());
1173
1174 match result.unwrap() {
1175 NetworksSource::Path(path) => {
1176 assert_eq!(path, "/custom/path");
1177 }
1178 _ => panic!("Expected Path variant"),
1179 }
1180 }
1181
1182 #[test]
1183 fn test_networks_source_deserialize_array() {
1184 let json = r#"[{"type": "evm", "network": "test", "chain_id": 1, "rpc_urls": ["http://localhost:8545"], "symbol": "ETH", "required_confirmations": 1}]"#;
1185 let result: Result<NetworksSource, _> = serde_json::from_str(json);
1186 assert!(result.is_ok());
1187
1188 match result.unwrap() {
1189 NetworksSource::List(networks) => {
1190 assert_eq!(networks.len(), 1);
1191 assert_eq!(networks[0].network_name(), "test");
1192 }
1193 _ => panic!("Expected List variant"),
1194 }
1195 }
1196
1197 #[test]
1198 fn test_networks_source_deserialize_invalid_type() {
1199 let json = r#"42"#;
1200 let result: Result<NetworksSource, _> = serde_json::from_str(json);
1201 assert!(result.is_err());
1202 }
1203}