1use super::{InheritanceResolver, NetworkFileConfig, NetworkFileLoader, NetworksSource};
14use crate::config::config_file::ConfigFileNetworkType;
15use crate::config::ConfigFileError;
16use serde::de::{self, Deserializer};
17use serde::{Deserialize, Serialize};
18use std::collections::HashMap;
19use std::ops::Index;
20
21#[derive(Debug, Default, Serialize, Clone)]
26pub struct NetworksFileConfig {
27 pub networks: Vec<NetworkFileConfig>,
28 #[serde(skip)]
29 network_map: HashMap<(ConfigFileNetworkType, String), usize>,
30}
31
32impl<'de> Deserialize<'de> for NetworksFileConfig {
38 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
39 where
40 D: Deserializer<'de>,
41 {
42 let source_option: Option<NetworksSource> = Option::deserialize(deserializer)?;
44 let source = source_option.unwrap_or_default();
45
46 let final_networks =
47 NetworkFileLoader::load_from_source(source).map_err(de::Error::custom)?;
48
49 if final_networks.is_empty() {
51 return Err(de::Error::custom(
52 "NetworksFileConfig cannot be empty - networks must contain at least one network configuration"
53 ));
54 }
55
56 let unflattened_config = NetworksFileConfig::new(final_networks).map_err(|e| {
59 de::Error::custom(format!(
60 "Error creating initial NetworksFileConfig: {:?}",
61 e
62 ))
63 })?;
64
65 unflattened_config
67 .flatten()
68 .map_err(|e| de::Error::custom(format!("Error flattening NetworksFileConfig: {:?}", e)))
69 }
70}
71
72impl NetworksFileConfig {
73 pub fn new(networks: Vec<NetworkFileConfig>) -> Result<Self, ConfigFileError> {
79 let mut network_map = HashMap::new();
80
81 for (index, network) in networks.iter().enumerate() {
83 let name = network.network_name();
84 let network_type = network.network_type();
85 let key = (network_type, name.to_string());
86
87 if network_map.insert(key, index).is_some() {
88 return Err(ConfigFileError::DuplicateId(format!(
90 "{:?} network '{}'",
91 network_type, name
92 )));
93 }
94 }
95
96 let instance = Self {
97 networks,
98 network_map,
99 };
100
101 for network in &instance.networks {
103 if network.inherits_from().is_some() {
104 instance.trace_inheritance(network.network_name(), network.network_type())?;
105 }
106 }
107
108 Ok(instance)
109 }
110
111 pub fn get_network(
121 &self,
122 network_type: ConfigFileNetworkType,
123 name: &str,
124 ) -> Option<&NetworkFileConfig> {
125 let key = (network_type, name.to_string());
126 self.network_map
127 .get(&key)
128 .map(|&index| &self.networks[index])
129 }
130
131 pub fn flatten(&self) -> Result<NetworksFileConfig, ConfigFileError> {
141 self.validate()?;
143
144 let resolved_networks = self
146 .networks
147 .iter()
148 .map(|network| self.resolve_inheritance(network))
149 .collect::<Result<Vec<NetworkFileConfig>, ConfigFileError>>()?;
150
151 NetworksFileConfig::new(resolved_networks)
152 }
153
154 fn resolve_inheritance(
163 &self,
164 network: &NetworkFileConfig,
165 ) -> Result<NetworkFileConfig, ConfigFileError> {
166 if network.inherits_from().is_none() {
168 return Ok(network.clone());
169 }
170
171 let parent_name = network.inherits_from().unwrap();
172 let network_name = network.network_name();
173 let network_type = network.network_type();
174
175 let lookup_fn = move |name: &str| self.get_network(network_type, name);
177 let resolver = InheritanceResolver::new(&lookup_fn);
178
179 match network {
180 NetworkFileConfig::Evm(config) => {
181 let resolved_config =
182 resolver.resolve_evm_inheritance(config, network_name, parent_name)?;
183 Ok(NetworkFileConfig::Evm(resolved_config))
184 }
185 NetworkFileConfig::Solana(config) => {
186 let resolved_config =
187 resolver.resolve_solana_inheritance(config, network_name, parent_name)?;
188 Ok(NetworkFileConfig::Solana(resolved_config))
189 }
190 NetworkFileConfig::Stellar(config) => {
191 let resolved_config =
192 resolver.resolve_stellar_inheritance(config, network_name, parent_name)?;
193 Ok(NetworkFileConfig::Stellar(resolved_config))
194 }
195 }
196 }
197
198 pub fn validate(&self) -> Result<(), ConfigFileError> {
205 for network in &self.networks {
206 network.validate()?;
207 }
208 Ok(())
209 }
210
211 fn trace_inheritance(
221 &self,
222 start_network_name: &str,
223 network_type: ConfigFileNetworkType,
224 ) -> Result<(), ConfigFileError> {
225 let mut current_path_names = Vec::new();
226 let mut current_name = start_network_name;
227
228 loop {
229 if current_path_names.contains(¤t_name) {
231 let cycle_path_str = current_path_names.join(" -> ");
232 return Err(ConfigFileError::CircularInheritance(format!(
233 "Circular inheritance detected: {} -> {}",
234 cycle_path_str, current_name
235 )));
236 }
237
238 current_path_names.push(current_name);
239
240 let current_network =
241 self.get_network(network_type, current_name)
242 .ok_or_else(|| {
243 ConfigFileError::InvalidReference(format!(
244 "{:?} network '{}' not found in configuration",
245 network_type, current_name
246 ))
247 })?;
248
249 if let Some(source_name) = current_network.inherits_from() {
250 let derived_type = current_network.network_type();
251
252 if source_name == current_name {
253 return Err(ConfigFileError::InvalidReference(format!(
254 "Network '{}' cannot inherit from itself",
255 current_name
256 )));
257 }
258
259 let source_network =
260 self.get_network(network_type, source_name).ok_or_else(|| {
261 ConfigFileError::InvalidReference(format!(
262 "{:?} network '{}' inherits from non-existent network '{}'",
263 network_type, current_name, source_name
264 ))
265 })?;
266
267 let source_type = source_network.network_type();
268
269 if derived_type != source_type {
270 return Err(ConfigFileError::IncompatibleInheritanceType(format!(
271 "Network '{}' (type {:?}) tries to inherit from '{}' (type {:?})",
272 current_name, derived_type, source_name, source_type
273 )));
274 }
275 current_name = source_name;
276 } else {
277 break;
278 }
279 }
280
281 Ok(())
282 }
283
284 pub fn iter(&self) -> impl Iterator<Item = &NetworkFileConfig> {
286 self.networks.iter()
287 }
288
289 pub fn len(&self) -> usize {
291 self.networks.len()
292 }
293
294 pub fn is_empty(&self) -> bool {
296 self.networks.is_empty()
297 }
298
299 pub fn networks_by_type(
301 &self,
302 network_type: crate::config::config_file::ConfigFileNetworkType,
303 ) -> impl Iterator<Item = &NetworkFileConfig> {
304 self.networks
305 .iter()
306 .filter(move |network| network.network_type() == network_type)
307 }
308
309 pub fn network_names(&self) -> impl Iterator<Item = &str> {
311 self.networks.iter().map(|network| network.network_name())
312 }
313
314 pub fn first(&self) -> Option<&NetworkFileConfig> {
320 self.networks.first()
321 }
322
323 pub fn get(&self, index: usize) -> Option<&NetworkFileConfig> {
332 self.networks.get(index)
333 }
334}
335
336impl Index<usize> for NetworksFileConfig {
338 type Output = NetworkFileConfig;
339
340 fn index(&self, index: usize) -> &Self::Output {
341 &self.networks[index]
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348 use crate::config::config_file::network::test_utils::*;
349 use crate::config::config_file::ConfigFileNetworkType;
350 use std::fs::File;
351 use std::io::Write;
352 use tempfile::tempdir;
353
354 #[test]
355 fn test_new_with_single_network() {
356 let networks = vec![create_evm_network_wrapped("test-evm")];
357 let config = NetworksFileConfig::new(networks);
358
359 assert!(config.is_ok());
360 let config = config.unwrap();
361 assert_eq!(config.networks.len(), 1);
362 assert_eq!(config.network_map.len(), 1);
363 assert!(config
364 .network_map
365 .contains_key(&(ConfigFileNetworkType::Evm, "test-evm".to_string())));
366 }
367
368 #[test]
369 fn test_new_with_multiple_networks() {
370 let networks = vec![
371 create_evm_network_wrapped("evm-1"),
372 create_solana_network_wrapped("solana-1"),
373 create_stellar_network_wrapped("stellar-1"),
374 ];
375 let config = NetworksFileConfig::new(networks);
376
377 assert!(config.is_ok());
378 let config = config.unwrap();
379 assert_eq!(config.networks.len(), 3);
380 assert_eq!(config.network_map.len(), 3);
381 assert!(config
382 .network_map
383 .contains_key(&(ConfigFileNetworkType::Evm, "evm-1".to_string())));
384 assert!(config
385 .network_map
386 .contains_key(&(ConfigFileNetworkType::Solana, "solana-1".to_string())));
387 assert!(config
388 .network_map
389 .contains_key(&(ConfigFileNetworkType::Stellar, "stellar-1".to_string())));
390 }
391
392 #[test]
393 fn test_new_with_empty_networks() {
394 let networks = vec![];
395 let config = NetworksFileConfig::new(networks);
396
397 assert!(config.is_ok());
398 let config = config.unwrap();
399 assert_eq!(config.networks.len(), 0);
400 assert_eq!(config.network_map.len(), 0);
401 }
402
403 #[test]
404 fn test_new_with_valid_inheritance() {
405 let networks = vec![
406 create_evm_network_wrapped("parent"),
407 create_evm_network_wrapped_with_parent("child", "parent"),
408 ];
409 let config = NetworksFileConfig::new(networks);
410
411 assert!(config.is_ok());
412 let config = config.unwrap();
413 assert_eq!(config.networks.len(), 2);
414 }
415
416 #[test]
417 fn test_new_with_invalid_inheritance_reference() {
418 let networks = vec![create_evm_network_wrapped_with_parent(
419 "child",
420 "non-existent",
421 )];
422 let result = NetworksFileConfig::new(networks);
423
424 assert!(result.is_err());
425 assert!(matches!(
426 result.unwrap_err(),
427 ConfigFileError::InvalidReference(_)
428 ));
429 }
430
431 #[test]
432 fn test_new_with_self_inheritance() {
433 let networks = vec![create_evm_network_wrapped_with_parent(
434 "self-ref", "self-ref",
435 )];
436 let result = NetworksFileConfig::new(networks);
437
438 assert!(result.is_err());
439 assert!(matches!(
440 result.unwrap_err(),
441 ConfigFileError::InvalidReference(_)
442 ));
443 }
444
445 #[test]
446 fn test_new_with_circular_inheritance() {
447 let networks = vec![
448 create_evm_network_wrapped_with_parent("a", "b"),
449 create_evm_network_wrapped_with_parent("b", "c"),
450 create_evm_network_wrapped_with_parent("c", "a"),
451 ];
452 let result = NetworksFileConfig::new(networks);
453
454 assert!(result.is_err());
455 assert!(matches!(
456 result.unwrap_err(),
457 ConfigFileError::CircularInheritance(_)
458 ));
459 }
460
461 #[test]
462 fn test_new_with_incompatible_inheritance_types() {
463 let networks = vec![
464 create_evm_network_wrapped("evm-parent"),
465 create_solana_network_wrapped_with_parent("solana-child", "evm-parent"),
466 ];
467 let result = NetworksFileConfig::new(networks);
468
469 assert!(result.is_err());
470 let error = result.unwrap_err();
471 assert!(matches!(error, ConfigFileError::InvalidReference(_)));
472 }
473
474 #[test]
475 fn test_new_with_deep_inheritance_chain() {
476 let networks = vec![
477 create_evm_network_wrapped("root"),
478 create_evm_network_wrapped_with_parent("level1", "root"),
479 create_evm_network_wrapped_with_parent("level2", "level1"),
480 create_evm_network_wrapped_with_parent("level3", "level2"),
481 ];
482 let config = NetworksFileConfig::new(networks);
483
484 assert!(config.is_ok());
485 let config = config.unwrap();
486 assert_eq!(config.networks.len(), 4);
487 }
488
489 #[test]
490 fn test_get_network_existing() {
491 let networks = vec![
492 create_evm_network_wrapped("test-evm"),
493 create_solana_network_wrapped("test-solana"),
494 ];
495 let config = NetworksFileConfig::new(networks).unwrap();
496
497 let network = config.get_network(ConfigFileNetworkType::Evm, "test-evm");
498 assert!(network.is_some());
499 assert_eq!(network.unwrap().network_name(), "test-evm");
500
501 let network = config.get_network(ConfigFileNetworkType::Solana, "test-solana");
502 assert!(network.is_some());
503 assert_eq!(network.unwrap().network_name(), "test-solana");
504 }
505
506 #[test]
507 fn test_get_network_non_existent() {
508 let networks = vec![create_evm_network_wrapped("test-evm")];
509 let config = NetworksFileConfig::new(networks).unwrap();
510
511 let network = config.get_network(ConfigFileNetworkType::Evm, "non-existent");
512 assert!(network.is_none());
513 }
514
515 #[test]
516 fn test_get_network_empty_config() {
517 let config = NetworksFileConfig::new(vec![]).unwrap();
518
519 let network = config.get_network(ConfigFileNetworkType::Evm, "any-name");
520 assert!(network.is_none());
521 }
522
523 #[test]
524 fn test_get_network_case_sensitive() {
525 let networks = vec![create_evm_network_wrapped("Test-Network")];
526 let config = NetworksFileConfig::new(networks).unwrap();
527
528 assert!(config
529 .get_network(ConfigFileNetworkType::Evm, "Test-Network")
530 .is_some());
531 assert!(config
532 .get_network(ConfigFileNetworkType::Evm, "test-network")
533 .is_none());
534 assert!(config
535 .get_network(ConfigFileNetworkType::Evm, "TEST-NETWORK")
536 .is_none());
537 }
538
539 #[test]
540 fn test_validate_success() {
541 let networks = vec![
542 create_evm_network_wrapped("evm-1"),
543 create_solana_network_wrapped("solana-1"),
544 ];
545 let config = NetworksFileConfig::new(networks).unwrap();
546
547 let result = config.validate();
548 assert!(result.is_ok());
549 }
550
551 #[test]
552 fn test_validate_with_invalid_network() {
553 let networks = vec![
554 create_evm_network_wrapped("valid"),
555 create_invalid_evm_network_wrapped("invalid"),
556 ];
557 let config = NetworksFileConfig::new(networks).unwrap();
558
559 let result = config.validate();
560 assert!(result.is_err());
561 assert!(matches!(
562 result.unwrap_err(),
563 ConfigFileError::MissingField(_)
564 ));
565 }
566
567 #[test]
568 fn test_validate_empty_config() {
569 let config = NetworksFileConfig::new(vec![]).unwrap();
570
571 let result = config.validate();
572 assert!(result.is_ok()); }
574
575 #[test]
576 fn test_flatten_without_inheritance() {
577 let networks = vec![
578 create_evm_network_wrapped("evm-1"),
579 create_solana_network_wrapped("solana-1"),
580 ];
581 let config = NetworksFileConfig::new(networks).unwrap();
582
583 let flattened = config.flatten();
584 assert!(flattened.is_ok());
585 let flattened = flattened.unwrap();
586 assert_eq!(flattened.networks.len(), 2);
587 }
588
589 #[test]
590 fn test_flatten_with_simple_inheritance() {
591 let networks = vec![
592 create_evm_network_wrapped("parent"),
593 create_evm_network_wrapped_with_parent("child", "parent"),
594 ];
595 let config = NetworksFileConfig::new(networks).unwrap();
596
597 let flattened = config.flatten();
598 assert!(flattened.is_ok());
599 let flattened = flattened.unwrap();
600 assert_eq!(flattened.networks.len(), 2);
601
602 let child = flattened.get_network(ConfigFileNetworkType::Evm, "child");
604 assert!(child.is_some());
605 assert_eq!(child.unwrap().inherits_from(), Some("parent"));
607 }
608
609 #[test]
610 fn test_flatten_with_multi_level_inheritance() {
611 let networks = vec![
612 create_evm_network_wrapped("root"),
613 create_evm_network_wrapped_with_parent("middle", "root"),
614 create_evm_network_wrapped_with_parent("leaf", "middle"),
615 ];
616 let config = NetworksFileConfig::new(networks).unwrap();
617
618 let flattened = config.flatten();
619 assert!(flattened.is_ok());
620 let flattened = flattened.unwrap();
621 assert_eq!(flattened.networks.len(), 3);
622 }
623
624 #[test]
625 fn test_flatten_with_validation_failure() {
626 let networks = vec![
627 create_evm_network_wrapped("valid"),
628 create_invalid_evm_network_wrapped("invalid"),
629 ];
630 let config = NetworksFileConfig::new(networks).unwrap();
631
632 let result = config.flatten();
633 assert!(result.is_err());
634 assert!(matches!(
635 result.unwrap_err(),
636 ConfigFileError::MissingField(_)
637 ));
638 }
639
640 #[test]
641 fn test_flatten_with_mixed_network_types() {
642 let networks = vec![
643 create_evm_network_wrapped("evm-parent"),
644 create_evm_network_wrapped_with_parent("evm-child", "evm-parent"),
645 create_solana_network_wrapped("solana-parent"),
646 create_solana_network_wrapped_with_parent("solana-child", "solana-parent"),
647 create_stellar_network_wrapped("stellar-standalone"),
648 ];
649 let config = NetworksFileConfig::new(networks).unwrap();
650
651 let flattened = config.flatten();
652 assert!(flattened.is_ok());
653 let flattened = flattened.unwrap();
654 assert_eq!(flattened.networks.len(), 5);
655 }
656
657 #[test]
658 fn test_iter() {
659 let networks = vec![
660 create_evm_network_wrapped("evm-1"),
661 create_solana_network_wrapped("solana-1"),
662 ];
663 let config = NetworksFileConfig::new(networks).unwrap();
664
665 let collected: Vec<_> = config.iter().collect();
666 assert_eq!(collected.len(), 2);
667 assert_eq!(collected[0].network_name(), "evm-1");
668 assert_eq!(collected[1].network_name(), "solana-1");
669 }
670
671 #[test]
672 fn test_len() {
673 let config = NetworksFileConfig::new(vec![]).unwrap();
674 assert_eq!(config.len(), 0);
675
676 let networks = vec![create_evm_network_wrapped("test")];
677 let config = NetworksFileConfig::new(networks).unwrap();
678 assert_eq!(config.len(), 1);
679
680 let networks = vec![
681 create_evm_network_wrapped("test1"),
682 create_solana_network_wrapped("test2"),
683 create_stellar_network_wrapped("test3"),
684 ];
685 let config = NetworksFileConfig::new(networks).unwrap();
686 assert_eq!(config.len(), 3);
687 }
688
689 #[test]
690 fn test_is_empty() {
691 let config = NetworksFileConfig::new(vec![]).unwrap();
692 assert!(config.is_empty());
693
694 let networks = vec![create_evm_network_wrapped("test")];
695 let config = NetworksFileConfig::new(networks).unwrap();
696 assert!(!config.is_empty());
697 }
698
699 #[test]
700 fn test_networks_by_type() {
701 let networks = vec![
702 create_evm_network_wrapped("evm-1"),
703 create_evm_network_wrapped("evm-2"),
704 create_solana_network_wrapped("solana-1"),
705 create_stellar_network_wrapped("stellar-1"),
706 ];
707 let config = NetworksFileConfig::new(networks).unwrap();
708
709 let evm_networks: Vec<_> = config
710 .networks_by_type(ConfigFileNetworkType::Evm)
711 .collect();
712 assert_eq!(evm_networks.len(), 2);
713
714 let solana_networks: Vec<_> = config
715 .networks_by_type(ConfigFileNetworkType::Solana)
716 .collect();
717 assert_eq!(solana_networks.len(), 1);
718
719 let stellar_networks: Vec<_> = config
720 .networks_by_type(ConfigFileNetworkType::Stellar)
721 .collect();
722 assert_eq!(stellar_networks.len(), 1);
723 }
724
725 #[test]
726 fn test_networks_by_type_empty_result() {
727 let networks = vec![create_evm_network_wrapped("evm-only")];
728 let config = NetworksFileConfig::new(networks).unwrap();
729
730 let solana_networks: Vec<_> = config
731 .networks_by_type(ConfigFileNetworkType::Solana)
732 .collect();
733 assert_eq!(solana_networks.len(), 0);
734 }
735
736 #[test]
737 fn test_network_names() {
738 let networks = vec![
739 create_evm_network_wrapped("alpha"),
740 create_solana_network_wrapped("beta"),
741 create_stellar_network_wrapped("gamma"),
742 ];
743 let config = NetworksFileConfig::new(networks).unwrap();
744
745 let names: Vec<_> = config.network_names().collect();
746 assert_eq!(names.len(), 3);
747 assert!(names.contains(&"alpha"));
748 assert!(names.contains(&"beta"));
749 assert!(names.contains(&"gamma"));
750 }
751
752 #[test]
753 fn test_network_names_empty() {
754 let config = NetworksFileConfig::new(vec![]).unwrap();
755
756 let names: Vec<_> = config.network_names().collect();
757 assert_eq!(names.len(), 0);
758 }
759
760 #[test]
762 fn test_default() {
763 let config = NetworksFileConfig::default();
764
765 assert_eq!(config.networks.len(), 0);
766 assert_eq!(config.network_map.len(), 0);
767 assert!(config.is_empty());
768 }
769
770 #[test]
771 fn test_deserialize_from_array() {
772 let json = r#"[
773 {
774 "type": "evm",
775 "network": "test-evm",
776 "chain_id": 31337,
777 "required_confirmations": 1,
778 "symbol": "ETH",
779 "rpc_urls": ["https://rpc.test.example.com"]
780 }
781 ]"#;
782
783 let result: Result<NetworksFileConfig, _> = serde_json::from_str(json);
784 assert!(result.is_ok());
785 let config = result.unwrap();
786 assert_eq!(config.len(), 1);
787 assert!(config
788 .get_network(ConfigFileNetworkType::Evm, "test-evm")
789 .is_some());
790 }
791
792 #[test]
793 fn test_deserialize_empty_array_returns_error() {
794 let json = r#"[]"#;
795 let result: Result<NetworksFileConfig, _> = serde_json::from_str(json);
796
797 assert!(result.is_err());
798 let error_message = result.unwrap_err().to_string();
799 assert!(error_message.contains("NetworksFileConfig cannot be empty"));
800 }
801
802 #[test]
803 fn test_deserialize_from_directory() {
804 let dir = tempdir().expect("Failed to create temp dir");
805 let network_dir_path = dir.path();
806
807 let evm_file = network_dir_path.join("evm.json");
809 let mut file = File::create(&evm_file).expect("Failed to create EVM file");
810 writeln!(file, r#"{{"networks": [{{"type": "evm", "network": "test-evm-from-file", "chain_id": 31337, "required_confirmations": 1, "symbol": "ETH", "rpc_urls": ["https://rpc.test.example.com"]}}]}}"#).expect("Failed to write EVM file");
811
812 let solana_file = network_dir_path.join("solana.json");
813 let mut file = File::create(&solana_file).expect("Failed to create Solana file");
814 writeln!(file, r#"{{"networks": [{{"type": "solana", "network": "test-solana-from-file", "rpc_urls": ["https://rpc.solana.example.com"]}}]}}"#).expect("Failed to write Solana file");
815
816 let json = format!(r#""{}""#, network_dir_path.to_str().unwrap());
817
818 let result: Result<NetworksFileConfig, _> = serde_json::from_str(&json);
819 assert!(result.is_ok());
820 let config = result.unwrap();
821 assert_eq!(config.len(), 2);
822 assert!(config
823 .get_network(ConfigFileNetworkType::Evm, "test-evm-from-file")
824 .is_some());
825 assert!(config
826 .get_network(ConfigFileNetworkType::Solana, "test-solana-from-file")
827 .is_some());
828 }
829
830 #[test]
831 fn test_deserialize_invalid_directory() {
832 let json = r#""/non/existent/directory""#;
833
834 let result: Result<NetworksFileConfig, _> = serde_json::from_str(json);
835 assert!(result.is_err());
836 }
837
838 #[test]
839 fn test_deserialize_with_inheritance_resolution() {
840 let json = r#"[
841 {
842 "type": "evm",
843 "network": "parent",
844 "chain_id": 31337,
845 "required_confirmations": 1,
846 "symbol": "ETH",
847 "rpc_urls": ["https://rpc.parent.example.com"]
848 },
849 {
850 "type": "evm",
851 "network": "child",
852 "from": "parent",
853 "chain_id": 31338,
854 "required_confirmations": 1,
855 "symbol": "ETH"
856 }
857 ]"#;
858
859 let result: Result<NetworksFileConfig, _> = serde_json::from_str(json);
860 assert!(result.is_ok());
861 let config = result.unwrap();
862 assert_eq!(config.len(), 2);
863
864 let child = config
866 .get_network(ConfigFileNetworkType::Evm, "child")
867 .unwrap();
868 assert_eq!(child.inherits_from(), Some("parent")); if let NetworkFileConfig::Evm(child_evm) = child {
872 assert!(child_evm.common.rpc_urls.is_some()); assert_eq!(child_evm.chain_id, Some(31338)); }
875 }
876
877 #[test]
878 fn test_deserialize_with_invalid_inheritance() {
879 let json = r#"[
880 {
881 "type": "evm",
882 "network": "child",
883 "from": "non-existent-parent",
884 "chain_id": 31337,
885 "required_confirmations": 1,
886 "symbol": "ETH",
887 "rpc_urls": ["https://rpc.test.example.com"]
888 }
889 ]"#;
890
891 let result: Result<NetworksFileConfig, _> = serde_json::from_str(json);
892 assert!(result.is_err());
893 }
894
895 #[test]
897 fn test_large_number_of_networks() {
898 let mut networks = Vec::new();
899 for i in 0..100 {
900 networks.push(create_evm_network_wrapped(&format!("network-{}", i)));
901 }
902
903 let config = NetworksFileConfig::new(networks);
904 assert!(config.is_ok());
905 let config = config.unwrap();
906 assert_eq!(config.len(), 100);
907
908 for i in 0..100 {
910 assert!(config
911 .get_network(ConfigFileNetworkType::Evm, &format!("network-{}", i))
912 .is_some());
913 }
914 }
915
916 #[test]
917 fn test_unicode_network_names() {
918 let networks = vec![
919 create_evm_network_wrapped("测试网络"),
920 create_solana_network_wrapped("тестовая-сеть"),
921 create_stellar_network_wrapped("réseau-test"),
922 ];
923
924 let config = NetworksFileConfig::new(networks);
925 assert!(config.is_ok());
926 let config = config.unwrap();
927 assert_eq!(config.len(), 3);
928 assert!(config
929 .get_network(ConfigFileNetworkType::Evm, "测试网络")
930 .is_some());
931 assert!(config
932 .get_network(ConfigFileNetworkType::Solana, "тестовая-сеть")
933 .is_some());
934 assert!(config
935 .get_network(ConfigFileNetworkType::Stellar, "réseau-test")
936 .is_some());
937 }
938
939 #[test]
940 fn test_special_characters_in_network_names() {
941 let networks = vec![
942 create_evm_network_wrapped("test-network_123"),
943 create_solana_network_wrapped("test.network.with.dots"),
944 create_stellar_network_wrapped("test@network#with$symbols"),
945 ];
946
947 let config = NetworksFileConfig::new(networks);
948 assert!(config.is_ok());
949 let config = config.unwrap();
950 assert_eq!(config.len(), 3);
951 }
952
953 #[test]
954 fn test_very_long_network_names() {
955 let long_name = "a".repeat(1000);
956 let networks = vec![create_evm_network_wrapped(&long_name)];
957
958 let config = NetworksFileConfig::new(networks);
959 assert!(config.is_ok());
960 let config = config.unwrap();
961 assert!(config
962 .get_network(ConfigFileNetworkType::Evm, &long_name)
963 .is_some());
964 }
965
966 #[test]
967 fn test_complex_inheritance_scenario() {
968 let networks = vec![
969 create_evm_network_wrapped("evm-root"),
971 create_solana_network_wrapped("solana-root"),
972 create_evm_network_wrapped_with_parent("evm-child1", "evm-root"),
974 create_evm_network_wrapped_with_parent("evm-child2", "evm-root"),
975 create_solana_network_wrapped_with_parent("solana-child1", "solana-root"),
976 create_evm_network_wrapped_with_parent("evm-grandchild", "evm-child1"),
978 ];
979
980 let config = NetworksFileConfig::new(networks);
981 assert!(config.is_ok());
982 let config = config.unwrap();
983 assert_eq!(config.len(), 6);
984
985 let flattened = config.flatten();
986 assert!(flattened.is_ok());
987 let flattened = flattened.unwrap();
988 assert_eq!(flattened.len(), 6);
989 }
990
991 #[test]
992 fn test_new_with_duplicate_network_names_across_types() {
993 let networks = vec![
995 create_evm_network_wrapped("mainnet"),
996 create_solana_network_wrapped("mainnet"),
997 create_stellar_network_wrapped("mainnet"),
998 ];
999 let result = NetworksFileConfig::new(networks);
1000
1001 assert!(result.is_ok());
1002 let config = result.unwrap();
1003 assert_eq!(config.networks.len(), 3);
1004 assert_eq!(config.network_map.len(), 3);
1005
1006 assert!(config
1008 .get_network(ConfigFileNetworkType::Evm, "mainnet")
1009 .is_some());
1010 assert!(config
1011 .get_network(ConfigFileNetworkType::Solana, "mainnet")
1012 .is_some());
1013 assert!(config
1014 .get_network(ConfigFileNetworkType::Stellar, "mainnet")
1015 .is_some());
1016 }
1017
1018 #[test]
1019 fn test_new_with_duplicate_network_names_within_same_type() {
1020 let networks = vec![
1021 create_evm_network_wrapped("duplicate-evm"),
1022 create_evm_network_wrapped("duplicate-evm"),
1023 ];
1024 let result = NetworksFileConfig::new(networks);
1025
1026 assert!(result.is_err());
1027 assert!(matches!(
1028 result.unwrap_err(),
1029 ConfigFileError::DuplicateId(_)
1030 ));
1031 }
1032
1033 #[test]
1034 fn test_get_with_empty_config() {
1035 let config = NetworksFileConfig::new(vec![]).unwrap();
1036
1037 let network_0 = config.get(0);
1038 assert!(network_0.is_none());
1039 }
1040
1041 #[test]
1042 fn test_get_and_first_equivalence() {
1043 let networks = vec![create_evm_network_wrapped("test-network")];
1044 let config = NetworksFileConfig::new(networks).unwrap();
1045
1046 let network_via_get = config.get(0);
1048 let network_via_first = config.first();
1049
1050 assert!(network_via_get.is_some());
1051 assert!(network_via_first.is_some());
1052 assert_eq!(
1053 network_via_get.unwrap().network_name(),
1054 network_via_first.unwrap().network_name()
1055 );
1056 assert_eq!(network_via_get.unwrap().network_name(), "test-network");
1057 }
1058
1059 #[test]
1060 #[allow(clippy::get_first)]
1061 fn test_different_access_methods() {
1062 let networks = vec![
1063 create_evm_network_wrapped("network-0"),
1064 create_solana_network_wrapped("network-1"),
1065 ];
1066 let config = NetworksFileConfig::new(networks).unwrap();
1067
1068 let net_0_get = config.get(0);
1070 assert!(net_0_get.is_some());
1071 assert_eq!(net_0_get.unwrap().network_name(), "network-0");
1072
1073 let net_0_first = config.first();
1075 assert!(net_0_first.is_some());
1076 assert_eq!(net_0_first.unwrap().network_name(), "network-0");
1077
1078 let net_0_index = &config[0];
1080 assert_eq!(net_0_index.network_name(), "network-0");
1081
1082 let net_0_direct = config.networks.get(0);
1084 assert!(net_0_direct.is_some());
1085 assert_eq!(net_0_direct.unwrap().network_name(), "network-0");
1086
1087 assert_eq!(
1089 net_0_get.unwrap().network_name(),
1090 net_0_first.unwrap().network_name()
1091 );
1092 assert_eq!(
1093 net_0_get.unwrap().network_name(),
1094 net_0_index.network_name()
1095 );
1096 assert_eq!(
1097 net_0_get.unwrap().network_name(),
1098 net_0_direct.unwrap().network_name()
1099 );
1100 }
1101
1102 #[test]
1103 fn test_first_with_non_empty_config() {
1104 let networks = vec![
1105 create_evm_network_wrapped("first-network"),
1106 create_solana_network_wrapped("second-network"),
1107 ];
1108 let config = NetworksFileConfig::new(networks).unwrap();
1109
1110 let first_network = config.first();
1111 assert!(first_network.is_some());
1112 assert_eq!(first_network.unwrap().network_name(), "first-network");
1113 }
1114
1115 #[test]
1116 fn test_first_with_empty_config() {
1117 let config = NetworksFileConfig::new(vec![]).unwrap();
1118
1119 let first_network = config.first();
1120 assert!(first_network.is_none());
1121 }
1122
1123 #[test]
1124 fn test_get_with_valid_index() {
1125 let networks = vec![
1126 create_evm_network_wrapped("network-0"),
1127 create_solana_network_wrapped("network-1"),
1128 create_stellar_network_wrapped("network-2"),
1129 ];
1130 let config = NetworksFileConfig::new(networks).unwrap();
1131
1132 let network_0 = config.get(0);
1133 assert!(network_0.is_some());
1134 assert_eq!(network_0.unwrap().network_name(), "network-0");
1135
1136 let network_1 = config.get(1);
1137 assert!(network_1.is_some());
1138 assert_eq!(network_1.unwrap().network_name(), "network-1");
1139
1140 let network_2 = config.get(2);
1141 assert!(network_2.is_some());
1142 assert_eq!(network_2.unwrap().network_name(), "network-2");
1143 }
1144
1145 #[test]
1146 fn test_get_with_invalid_index() {
1147 let networks = vec![create_evm_network_wrapped("only-network")];
1148 let config = NetworksFileConfig::new(networks).unwrap();
1149
1150 let network_out_of_bounds = config.get(1);
1151 assert!(network_out_of_bounds.is_none());
1152
1153 let network_large_index = config.get(100);
1154 assert!(network_large_index.is_none());
1155 }
1156
1157 #[test]
1158 fn test_networks_source_default() {
1159 let default_source = NetworksSource::default();
1160 match default_source {
1161 NetworksSource::Path(path) => {
1162 assert_eq!(path, "./config/networks");
1163 }
1164 _ => panic!("Default should be a Path variant"),
1165 }
1166 }
1167}