1use crate::{RegistryError, Result, VersionEntry};
4use semver::{Version, VersionReq};
5use serde::{Deserialize, Serialize};
6use std::collections::{HashMap, HashSet, VecDeque};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Dependency {
11 pub name: String,
13
14 pub version_req: String,
16
17 pub optional: bool,
19
20 pub features: Vec<String>,
22
23 pub source: DependencySource,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29#[serde(rename_all = "lowercase")]
30pub enum DependencySource {
31 Registry,
32 Git { url: String, rev: Option<String> },
33 Path { path: String },
34}
35
36impl Default for DependencySource {
37 fn default() -> Self {
38 Self::Registry
39 }
40}
41
42#[derive(Debug, Clone)]
44pub struct ResolvedDependency {
45 pub name: String,
46 pub version: Version,
47 pub dependencies: Vec<Dependency>,
48}
49
50#[allow(dead_code)]
52#[derive(Debug, Clone)]
53struct DependencyNode {
54 name: String,
55 version: Version,
56 dependencies: Vec<String>, }
58
59pub struct DependencyResolver {
61 available_versions: HashMap<String, Vec<VersionEntry>>,
63}
64
65impl DependencyResolver {
66 pub fn new() -> Self {
68 Self {
69 available_versions: HashMap::new(),
70 }
71 }
72
73 pub fn add_package_versions(&mut self, name: String, versions: Vec<VersionEntry>) {
75 self.available_versions.insert(name, versions);
76 }
77
78 pub fn resolve(
80 &self,
81 root_package: &str,
82 _root_version: &Version,
83 dependencies: Vec<Dependency>,
84 ) -> Result<Vec<ResolvedDependency>> {
85 let mut resolved = Vec::new();
86 let mut visited = HashSet::new();
87 let mut queue = VecDeque::new();
88
89 queue.push_back((root_package.to_string(), dependencies));
91
92 while let Some((_parent, deps)) = queue.pop_front() {
93 for dep in deps {
94 if visited.contains(&dep.name) {
96 continue;
97 }
98
99 let version_req = VersionReq::parse(&dep.version_req).map_err(|e| {
101 RegistryError::InvalidVersion(format!(
102 "Invalid version requirement '{}': {}",
103 dep.version_req, e
104 ))
105 })?;
106
107 let compatible_version =
109 self.find_compatible_version(&dep.name, &version_req)?.ok_or_else(|| {
110 RegistryError::InvalidVersion(format!(
111 "No compatible version found for {} with requirement {}",
112 dep.name, dep.version_req
113 ))
114 })?;
115
116 let version_entry =
118 self.get_version_entry(&dep.name, &compatible_version).ok_or_else(|| {
119 RegistryError::PluginNotFound(format!(
120 "{} {}",
121 dep.name, compatible_version
122 ))
123 })?;
124
125 let transitive_deps: Vec<Dependency> = version_entry
127 .dependencies
128 .iter()
129 .map(|(name, version_req)| Dependency {
130 name: name.clone(),
131 version_req: version_req.clone(),
132 optional: false,
133 features: vec![],
134 source: DependencySource::Registry,
135 })
136 .collect();
137
138 resolved.push(ResolvedDependency {
140 name: dep.name.clone(),
141 version: compatible_version.clone(),
142 dependencies: transitive_deps.clone(),
143 });
144
145 visited.insert(dep.name.clone());
146
147 if !transitive_deps.is_empty() {
149 queue.push_back((dep.name.clone(), transitive_deps));
150 }
151 }
152 }
153
154 self.check_circular_dependencies(&resolved)?;
156
157 Ok(resolved)
158 }
159
160 fn find_compatible_version(
162 &self,
163 package: &str,
164 version_req: &VersionReq,
165 ) -> Result<Option<Version>> {
166 let versions = self
167 .available_versions
168 .get(package)
169 .ok_or_else(|| RegistryError::PluginNotFound(package.to_string()))?;
170
171 let mut compatible_versions: Vec<Version> = versions
173 .iter()
174 .filter(|v| !v.yanked)
175 .filter_map(|v| Version::parse(&v.version).ok())
176 .filter(|v| version_req.matches(v))
177 .collect();
178
179 compatible_versions.sort();
181 compatible_versions.reverse();
182
183 Ok(compatible_versions.first().cloned())
184 }
185
186 fn get_version_entry(&self, package: &str, version: &Version) -> Option<&VersionEntry> {
188 self.available_versions
189 .get(package)?
190 .iter()
191 .find(|v| Version::parse(&v.version).ok().map(|v| &v == version).unwrap_or(false))
192 }
193
194 fn check_circular_dependencies(&self, resolved: &[ResolvedDependency]) -> Result<()> {
196 let mut graph: HashMap<String, Vec<String>> = HashMap::new();
197
198 for dep in resolved {
200 let deps: Vec<String> = dep.dependencies.iter().map(|d| d.name.clone()).collect();
201 graph.insert(dep.name.clone(), deps);
202 }
203
204 let mut visited = HashSet::new();
206 let mut rec_stack = HashSet::new();
207
208 for node in graph.keys() {
209 if Self::has_cycle_impl(&graph, node, &mut visited, &mut rec_stack) {
210 return Err(RegistryError::InvalidManifest(format!(
211 "Circular dependency detected involving package: {}",
212 node
213 )));
214 }
215 }
216
217 Ok(())
218 }
219
220 fn has_cycle_impl(
222 graph: &HashMap<String, Vec<String>>,
223 node: &str,
224 visited: &mut HashSet<String>,
225 rec_stack: &mut HashSet<String>,
226 ) -> bool {
227 if rec_stack.contains(node) {
228 return true;
229 }
230
231 if visited.contains(node) {
232 return false;
233 }
234
235 visited.insert(node.to_string());
236 rec_stack.insert(node.to_string());
237
238 if let Some(neighbors) = graph.get(node) {
239 for neighbor in neighbors {
240 if Self::has_cycle_impl(graph, neighbor, visited, rec_stack) {
241 return true;
242 }
243 }
244 }
245
246 rec_stack.remove(node);
247 false
248 }
249
250 pub fn calculate_install_order(&self, resolved: &[ResolvedDependency]) -> Result<Vec<String>> {
252 let mut graph: HashMap<String, Vec<String>> = HashMap::new();
253 let mut in_degree: HashMap<String, usize> = HashMap::new();
254
255 for dep in resolved {
257 in_degree.entry(dep.name.clone()).or_insert(0);
258
259 for child_dep in &dep.dependencies {
260 graph.entry(dep.name.clone()).or_default().push(child_dep.name.clone());
261
262 *in_degree.entry(child_dep.name.clone()).or_insert(0) += 1;
263 }
264 }
265
266 let mut queue: VecDeque<String> = in_degree
268 .iter()
269 .filter(|(_, °ree)| degree == 0)
270 .map(|(name, _)| name.clone())
271 .collect();
272
273 let mut order = Vec::new();
274
275 while let Some(node) = queue.pop_front() {
276 order.push(node.clone());
277
278 if let Some(neighbors) = graph.get(&node) {
279 for neighbor in neighbors {
280 if let Some(degree) = in_degree.get_mut(neighbor) {
281 *degree -= 1;
282 if *degree == 0 {
283 queue.push_back(neighbor.clone());
284 }
285 }
286 }
287 }
288 }
289
290 if order.len() != in_degree.len() {
292 return Err(RegistryError::InvalidManifest(
293 "Circular dependency detected during install order calculation".to_string(),
294 ));
295 }
296
297 order.reverse();
299
300 Ok(order)
301 }
302}
303
304impl Default for DependencyResolver {
305 fn default() -> Self {
306 Self::new()
307 }
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
312pub struct DependencyConflict {
313 pub package: String,
314 pub required_by: Vec<ConflictRequirement>,
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize)]
319pub struct ConflictRequirement {
320 pub package: String,
321 pub version_req: String,
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327
328 fn create_version_entry(version: &str, yanked: bool) -> VersionEntry {
329 VersionEntry {
330 version: version.to_string(),
331 download_url: format!("https://example.com/pkg-{}.tar.gz", version),
332 checksum: "abc123".to_string(),
333 size: 1000,
334 published_at: "2025-01-01".to_string(),
335 yanked,
336 min_mockforge_version: None,
337 dependencies: HashMap::new(),
338 }
339 }
340
341 fn create_dependency(name: &str, version_req: &str) -> Dependency {
342 Dependency {
343 name: name.to_string(),
344 version_req: version_req.to_string(),
345 optional: false,
346 features: vec![],
347 source: DependencySource::Registry,
348 }
349 }
350
351 #[test]
353 fn test_dependency_clone() {
354 let dep = Dependency {
355 name: "test-dep".to_string(),
356 version_req: "^1.0.0".to_string(),
357 optional: true,
358 features: vec!["feature1".to_string(), "feature2".to_string()],
359 source: DependencySource::Registry,
360 };
361
362 let cloned = dep.clone();
363 assert_eq!(dep.name, cloned.name);
364 assert_eq!(dep.version_req, cloned.version_req);
365 assert_eq!(dep.optional, cloned.optional);
366 assert_eq!(dep.features.len(), cloned.features.len());
367 }
368
369 #[test]
370 fn test_dependency_debug() {
371 let dep = create_dependency("my-dep", "^2.0");
372 let debug = format!("{:?}", dep);
373 assert!(debug.contains("Dependency"));
374 assert!(debug.contains("my-dep"));
375 }
376
377 #[test]
378 fn test_dependency_serialize() {
379 let dep = create_dependency("test-dep", ">=1.0.0");
380 let json = serde_json::to_string(&dep).unwrap();
381 assert!(json.contains("\"name\":\"test-dep\""));
382 assert!(json.contains("\"version_req\":\">=1.0.0\""));
383 }
384
385 #[test]
386 fn test_dependency_deserialize() {
387 let json = r#"{
388 "name": "parsed-dep",
389 "version_req": "~1.2.0",
390 "optional": true,
391 "features": ["async"],
392 "source": "registry"
393 }"#;
394
395 let dep: Dependency = serde_json::from_str(json).unwrap();
396 assert_eq!(dep.name, "parsed-dep");
397 assert_eq!(dep.version_req, "~1.2.0");
398 assert!(dep.optional);
399 assert_eq!(dep.features, vec!["async"]);
400 }
401
402 #[test]
403 fn test_dependency_with_features() {
404 let dep = Dependency {
405 name: "feature-dep".to_string(),
406 version_req: "1.0.0".to_string(),
407 optional: false,
408 features: vec!["serde".to_string(), "async".to_string(), "full".to_string()],
409 source: DependencySource::Registry,
410 };
411
412 assert_eq!(dep.features.len(), 3);
413 assert!(dep.features.contains(&"serde".to_string()));
414 }
415
416 #[test]
418 fn test_dependency_source_default() {
419 let source = DependencySource::default();
420 assert!(matches!(source, DependencySource::Registry));
421 }
422
423 #[test]
424 fn test_dependency_source_registry_serialize() {
425 let source = DependencySource::Registry;
426 let json = serde_json::to_string(&source).unwrap();
427 assert_eq!(json, "\"registry\"");
428 }
429
430 #[test]
431 fn test_dependency_source_git_serialize() {
432 let source = DependencySource::Git {
433 url: "https://github.com/test/repo".to_string(),
434 rev: Some("main".to_string()),
435 };
436 let json = serde_json::to_string(&source).unwrap();
437 assert!(json.contains("git"));
438 assert!(json.contains("github.com"));
439 assert!(json.contains("main"));
440 }
441
442 #[test]
443 fn test_dependency_source_git_without_rev() {
444 let source = DependencySource::Git {
445 url: "https://github.com/test/repo".to_string(),
446 rev: None,
447 };
448 let json = serde_json::to_string(&source).unwrap();
449 assert!(json.contains("git"));
450 }
451
452 #[test]
453 fn test_dependency_source_path_serialize() {
454 let source = DependencySource::Path {
455 path: "/local/path/to/dep".to_string(),
456 };
457 let json = serde_json::to_string(&source).unwrap();
458 assert!(json.contains("path"));
459 assert!(json.contains("/local/path"));
460 }
461
462 #[test]
463 fn test_dependency_source_deserialize_registry() {
464 let source: DependencySource = serde_json::from_str("\"registry\"").unwrap();
465 assert!(matches!(source, DependencySource::Registry));
466 }
467
468 #[test]
469 fn test_dependency_source_deserialize_git() {
470 let json = r#"{"git": {"url": "https://github.com/test/repo", "rev": "v1.0.0"}}"#;
471 let source: DependencySource = serde_json::from_str(json).unwrap();
472 match source {
473 DependencySource::Git { url, rev } => {
474 assert!(url.contains("github.com"));
475 assert_eq!(rev, Some("v1.0.0".to_string()));
476 }
477 _ => panic!("Expected Git source"),
478 }
479 }
480
481 #[test]
482 fn test_dependency_source_clone() {
483 let source = DependencySource::Git {
484 url: "https://test.com".to_string(),
485 rev: Some("abc123".to_string()),
486 };
487 let cloned = source.clone();
488 match cloned {
489 DependencySource::Git { url, rev } => {
490 assert_eq!(url, "https://test.com");
491 assert_eq!(rev, Some("abc123".to_string()));
492 }
493 _ => panic!("Expected Git source"),
494 }
495 }
496
497 #[test]
498 fn test_dependency_source_debug() {
499 let source = DependencySource::Path {
500 path: "./local".to_string(),
501 };
502 let debug = format!("{:?}", source);
503 assert!(debug.contains("Path"));
504 }
505
506 #[test]
508 fn test_resolved_dependency_clone() {
509 let resolved = ResolvedDependency {
510 name: "resolved-pkg".to_string(),
511 version: Version::parse("1.2.3").unwrap(),
512 dependencies: vec![create_dependency("dep-a", "^1.0")],
513 };
514
515 let cloned = resolved.clone();
516 assert_eq!(resolved.name, cloned.name);
517 assert_eq!(resolved.version, cloned.version);
518 assert_eq!(resolved.dependencies.len(), cloned.dependencies.len());
519 }
520
521 #[test]
522 fn test_resolved_dependency_debug() {
523 let resolved = ResolvedDependency {
524 name: "test-pkg".to_string(),
525 version: Version::parse("2.0.0").unwrap(),
526 dependencies: vec![],
527 };
528
529 let debug = format!("{:?}", resolved);
530 assert!(debug.contains("ResolvedDependency"));
531 assert!(debug.contains("test-pkg"));
532 }
533
534 #[test]
536 fn test_dependency_resolver_new() {
537 let _resolver = DependencyResolver::new();
538 }
540
541 #[test]
542 fn test_dependency_resolver_default() {
543 let _resolver = DependencyResolver::default();
544 }
546
547 #[test]
548 fn test_dependency_resolver_add_package_versions() {
549 let mut resolver = DependencyResolver::new();
550
551 resolver.add_package_versions(
552 "my-package".to_string(),
553 vec![
554 create_version_entry("1.0.0", false),
555 create_version_entry("1.1.0", false),
556 ],
557 );
558
559 resolver.add_package_versions(
561 "other-package".to_string(),
562 vec![create_version_entry("2.0.0", false)],
563 );
564 }
565
566 #[test]
567 fn test_dependency_resolution() {
568 let mut resolver = DependencyResolver::new();
569
570 resolver.add_package_versions(
572 "package-a".to_string(),
573 vec![
574 create_version_entry("1.0.0", false),
575 create_version_entry("1.1.0", false),
576 ],
577 );
578
579 let deps = vec![create_dependency("package-a", "^1.0")];
580
581 let root_version = Version::parse("1.0.0").unwrap();
582 let resolved = resolver.resolve("root", &root_version, deps);
583
584 assert!(resolved.is_ok());
585 let resolved = resolved.unwrap();
586 assert_eq!(resolved.len(), 1);
587 assert_eq!(resolved[0].name, "package-a");
588 assert_eq!(resolved[0].version, Version::parse("1.1.0").unwrap());
589 }
590
591 #[test]
592 fn test_dependency_resolution_exact_version() {
593 let mut resolver = DependencyResolver::new();
594
595 resolver.add_package_versions(
596 "exact-pkg".to_string(),
597 vec![
598 create_version_entry("1.0.0", false),
599 create_version_entry("1.1.0", false),
600 create_version_entry("2.0.0", false),
601 ],
602 );
603
604 let deps = vec![create_dependency("exact-pkg", "=1.0.0")];
605 let root_version = Version::parse("1.0.0").unwrap();
606 let resolved = resolver.resolve("root", &root_version, deps).unwrap();
607
608 assert_eq!(resolved[0].version, Version::parse("1.0.0").unwrap());
609 }
610
611 #[test]
612 fn test_dependency_resolution_yanked_excluded() {
613 let mut resolver = DependencyResolver::new();
614
615 resolver.add_package_versions(
616 "yanked-pkg".to_string(),
617 vec![
618 create_version_entry("1.0.0", false),
619 create_version_entry("1.1.0", true), create_version_entry("1.0.5", false),
621 ],
622 );
623
624 let deps = vec![create_dependency("yanked-pkg", "^1.0")];
625 let root_version = Version::parse("1.0.0").unwrap();
626 let resolved = resolver.resolve("root", &root_version, deps).unwrap();
627
628 assert_eq!(resolved[0].version, Version::parse("1.0.5").unwrap());
630 }
631
632 #[test]
633 fn test_dependency_resolution_package_not_found() {
634 let resolver = DependencyResolver::new();
635
636 let deps = vec![create_dependency("nonexistent", "^1.0")];
637 let root_version = Version::parse("1.0.0").unwrap();
638 let result = resolver.resolve("root", &root_version, deps);
639
640 assert!(result.is_err());
641 }
642
643 #[test]
644 fn test_dependency_resolution_no_compatible_version() {
645 let mut resolver = DependencyResolver::new();
646
647 resolver.add_package_versions(
648 "old-pkg".to_string(),
649 vec![create_version_entry("0.5.0", false)],
650 );
651
652 let deps = vec![create_dependency("old-pkg", "^1.0")];
653 let root_version = Version::parse("1.0.0").unwrap();
654 let result = resolver.resolve("root", &root_version, deps);
655
656 assert!(result.is_err());
657 }
658
659 #[test]
660 fn test_dependency_resolution_invalid_version_req() {
661 let mut resolver = DependencyResolver::new();
662
663 resolver
664 .add_package_versions("pkg".to_string(), vec![create_version_entry("1.0.0", false)]);
665
666 let deps = vec![create_dependency("pkg", "invalid-req")];
667 let root_version = Version::parse("1.0.0").unwrap();
668 let result = resolver.resolve("root", &root_version, deps);
669
670 assert!(result.is_err());
671 }
672
673 #[test]
674 fn test_dependency_resolution_multiple_deps() {
675 let mut resolver = DependencyResolver::new();
676
677 resolver
678 .add_package_versions("pkg-a".to_string(), vec![create_version_entry("1.0.0", false)]);
679 resolver
680 .add_package_versions("pkg-b".to_string(), vec![create_version_entry("2.0.0", false)]);
681 resolver
682 .add_package_versions("pkg-c".to_string(), vec![create_version_entry("3.0.0", false)]);
683
684 let deps = vec![
685 create_dependency("pkg-a", "^1.0"),
686 create_dependency("pkg-b", "^2.0"),
687 create_dependency("pkg-c", "^3.0"),
688 ];
689
690 let root_version = Version::parse("1.0.0").unwrap();
691 let resolved = resolver.resolve("root", &root_version, deps).unwrap();
692
693 assert_eq!(resolved.len(), 3);
694 }
695
696 #[test]
697 fn test_circular_dependency_detection() {
698 let resolver = DependencyResolver::new();
699
700 let resolved = vec![
702 ResolvedDependency {
703 name: "package-a".to_string(),
704 version: Version::parse("1.0.0").unwrap(),
705 dependencies: vec![create_dependency("package-b", "1.0")],
706 },
707 ResolvedDependency {
708 name: "package-b".to_string(),
709 version: Version::parse("1.0.0").unwrap(),
710 dependencies: vec![create_dependency("package-a", "1.0")],
711 },
712 ];
713
714 let result = resolver.check_circular_dependencies(&resolved);
715 assert!(result.is_err());
716 }
717
718 #[test]
719 fn test_no_circular_dependency() {
720 let resolver = DependencyResolver::new();
721
722 let resolved = vec![
724 ResolvedDependency {
725 name: "package-a".to_string(),
726 version: Version::parse("1.0.0").unwrap(),
727 dependencies: vec![create_dependency("package-b", "1.0")],
728 },
729 ResolvedDependency {
730 name: "package-b".to_string(),
731 version: Version::parse("1.0.0").unwrap(),
732 dependencies: vec![create_dependency("package-c", "1.0")],
733 },
734 ResolvedDependency {
735 name: "package-c".to_string(),
736 version: Version::parse("1.0.0").unwrap(),
737 dependencies: vec![],
738 },
739 ];
740
741 let result = resolver.check_circular_dependencies(&resolved);
742 assert!(result.is_ok());
743 }
744
745 #[test]
746 fn test_circular_dependency_three_node_cycle() {
747 let resolver = DependencyResolver::new();
748
749 let resolved = vec![
751 ResolvedDependency {
752 name: "a".to_string(),
753 version: Version::parse("1.0.0").unwrap(),
754 dependencies: vec![create_dependency("b", "1.0")],
755 },
756 ResolvedDependency {
757 name: "b".to_string(),
758 version: Version::parse("1.0.0").unwrap(),
759 dependencies: vec![create_dependency("c", "1.0")],
760 },
761 ResolvedDependency {
762 name: "c".to_string(),
763 version: Version::parse("1.0.0").unwrap(),
764 dependencies: vec![create_dependency("a", "1.0")],
765 },
766 ];
767
768 let result = resolver.check_circular_dependencies(&resolved);
769 assert!(result.is_err());
770 }
771
772 #[test]
774 fn test_calculate_install_order_simple() {
775 let resolver = DependencyResolver::new();
776
777 let resolved = vec![
778 ResolvedDependency {
779 name: "root".to_string(),
780 version: Version::parse("1.0.0").unwrap(),
781 dependencies: vec![create_dependency("leaf", "1.0")],
782 },
783 ResolvedDependency {
784 name: "leaf".to_string(),
785 version: Version::parse("1.0.0").unwrap(),
786 dependencies: vec![],
787 },
788 ];
789
790 let order = resolver.calculate_install_order(&resolved).unwrap();
791 assert_eq!(order.len(), 2);
792 let leaf_pos = order.iter().position(|x| x == "leaf").unwrap();
794 let root_pos = order.iter().position(|x| x == "root").unwrap();
795 assert!(leaf_pos < root_pos);
796 }
797
798 #[test]
799 fn test_calculate_install_order_chain() {
800 let resolver = DependencyResolver::new();
801
802 let resolved = vec![
804 ResolvedDependency {
805 name: "a".to_string(),
806 version: Version::parse("1.0.0").unwrap(),
807 dependencies: vec![create_dependency("b", "1.0")],
808 },
809 ResolvedDependency {
810 name: "b".to_string(),
811 version: Version::parse("1.0.0").unwrap(),
812 dependencies: vec![create_dependency("c", "1.0")],
813 },
814 ResolvedDependency {
815 name: "c".to_string(),
816 version: Version::parse("1.0.0").unwrap(),
817 dependencies: vec![],
818 },
819 ];
820
821 let order = resolver.calculate_install_order(&resolved).unwrap();
822 let c_pos = order.iter().position(|x| x == "c").unwrap();
823 let b_pos = order.iter().position(|x| x == "b").unwrap();
824 let a_pos = order.iter().position(|x| x == "a").unwrap();
825
826 assert!(c_pos < b_pos);
827 assert!(b_pos < a_pos);
828 }
829
830 #[test]
831 fn test_calculate_install_order_no_deps() {
832 let resolver = DependencyResolver::new();
833
834 let resolved = vec![ResolvedDependency {
835 name: "standalone".to_string(),
836 version: Version::parse("1.0.0").unwrap(),
837 dependencies: vec![],
838 }];
839
840 let order = resolver.calculate_install_order(&resolved).unwrap();
841 assert_eq!(order, vec!["standalone"]);
842 }
843
844 #[test]
845 fn test_calculate_install_order_empty() {
846 let resolver = DependencyResolver::new();
847 let resolved: Vec<ResolvedDependency> = vec![];
848
849 let order = resolver.calculate_install_order(&resolved).unwrap();
850 assert!(order.is_empty());
851 }
852
853 #[test]
855 fn test_dependency_conflict_clone() {
856 let conflict = DependencyConflict {
857 package: "conflicting-pkg".to_string(),
858 required_by: vec![
859 ConflictRequirement {
860 package: "pkg-a".to_string(),
861 version_req: "^1.0".to_string(),
862 },
863 ConflictRequirement {
864 package: "pkg-b".to_string(),
865 version_req: "^2.0".to_string(),
866 },
867 ],
868 };
869
870 let cloned = conflict.clone();
871 assert_eq!(conflict.package, cloned.package);
872 assert_eq!(conflict.required_by.len(), cloned.required_by.len());
873 }
874
875 #[test]
876 fn test_dependency_conflict_debug() {
877 let conflict = DependencyConflict {
878 package: "test-conflict".to_string(),
879 required_by: vec![],
880 };
881
882 let debug = format!("{:?}", conflict);
883 assert!(debug.contains("DependencyConflict"));
884 assert!(debug.contains("test-conflict"));
885 }
886
887 #[test]
888 fn test_dependency_conflict_serialize() {
889 let conflict = DependencyConflict {
890 package: "pkg".to_string(),
891 required_by: vec![ConflictRequirement {
892 package: "requirer".to_string(),
893 version_req: "^1.0".to_string(),
894 }],
895 };
896
897 let json = serde_json::to_string(&conflict).unwrap();
898 assert!(json.contains("\"package\":\"pkg\""));
899 assert!(json.contains("requirer"));
900 }
901
902 #[test]
904 fn test_conflict_requirement_clone() {
905 let req = ConflictRequirement {
906 package: "req-pkg".to_string(),
907 version_req: ">=1.0.0".to_string(),
908 };
909
910 let cloned = req.clone();
911 assert_eq!(req.package, cloned.package);
912 assert_eq!(req.version_req, cloned.version_req);
913 }
914
915 #[test]
916 fn test_conflict_requirement_debug() {
917 let req = ConflictRequirement {
918 package: "debug-pkg".to_string(),
919 version_req: "~1.2".to_string(),
920 };
921
922 let debug = format!("{:?}", req);
923 assert!(debug.contains("ConflictRequirement"));
924 }
925
926 #[test]
927 fn test_conflict_requirement_serialize() {
928 let req = ConflictRequirement {
929 package: "ser-pkg".to_string(),
930 version_req: "*".to_string(),
931 };
932
933 let json = serde_json::to_string(&req).unwrap();
934 assert!(json.contains("\"package\":\"ser-pkg\""));
935 assert!(json.contains("\"version_req\":\"*\""));
936 }
937
938 #[test]
939 fn test_conflict_requirement_deserialize() {
940 let json = r#"{"package": "de-pkg", "version_req": "^3.0"}"#;
941 let req: ConflictRequirement = serde_json::from_str(json).unwrap();
942 assert_eq!(req.package, "de-pkg");
943 assert_eq!(req.version_req, "^3.0");
944 }
945
946 #[test]
948 fn test_transitive_dependency_resolution() {
949 let mut resolver = DependencyResolver::new();
950
951 let mut deps_map = HashMap::new();
953 deps_map.insert("pkg-b".to_string(), "^1.0".to_string());
954
955 resolver.add_package_versions(
956 "pkg-a".to_string(),
957 vec![VersionEntry {
958 version: "1.0.0".to_string(),
959 download_url: "https://example.com/a.tar.gz".to_string(),
960 checksum: "abc".to_string(),
961 size: 1000,
962 published_at: "2025-01-01".to_string(),
963 yanked: false,
964 min_mockforge_version: None,
965 dependencies: deps_map,
966 }],
967 );
968
969 resolver
970 .add_package_versions("pkg-b".to_string(), vec![create_version_entry("1.0.0", false)]);
971
972 let deps = vec![create_dependency("pkg-a", "^1.0")];
973 let root_version = Version::parse("1.0.0").unwrap();
974 let resolved = resolver.resolve("root", &root_version, deps).unwrap();
975
976 assert_eq!(resolved.len(), 2);
978 let names: Vec<&str> = resolved.iter().map(|r| r.name.as_str()).collect();
979 assert!(names.contains(&"pkg-a"));
980 assert!(names.contains(&"pkg-b"));
981 }
982}