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, Default, Serialize, Deserialize)]
29#[serde(rename_all = "lowercase")]
30pub enum DependencySource {
31 #[default]
32 Registry,
33 Git {
34 url: String,
35 rev: Option<String>,
36 },
37 Path {
38 path: String,
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 downloads: 0,
339 }
340 }
341
342 fn create_dependency(name: &str, version_req: &str) -> Dependency {
343 Dependency {
344 name: name.to_string(),
345 version_req: version_req.to_string(),
346 optional: false,
347 features: vec![],
348 source: DependencySource::Registry,
349 }
350 }
351
352 #[test]
354 fn test_dependency_clone() {
355 let dep = Dependency {
356 name: "test-dep".to_string(),
357 version_req: "^1.0.0".to_string(),
358 optional: true,
359 features: vec!["feature1".to_string(), "feature2".to_string()],
360 source: DependencySource::Registry,
361 };
362
363 let cloned = dep.clone();
364 assert_eq!(dep.name, cloned.name);
365 assert_eq!(dep.version_req, cloned.version_req);
366 assert_eq!(dep.optional, cloned.optional);
367 assert_eq!(dep.features.len(), cloned.features.len());
368 }
369
370 #[test]
371 fn test_dependency_debug() {
372 let dep = create_dependency("my-dep", "^2.0");
373 let debug = format!("{:?}", dep);
374 assert!(debug.contains("Dependency"));
375 assert!(debug.contains("my-dep"));
376 }
377
378 #[test]
379 fn test_dependency_serialize() {
380 let dep = create_dependency("test-dep", ">=1.0.0");
381 let json = serde_json::to_string(&dep).unwrap();
382 assert!(json.contains("\"name\":\"test-dep\""));
383 assert!(json.contains("\"version_req\":\">=1.0.0\""));
384 }
385
386 #[test]
387 fn test_dependency_deserialize() {
388 let json = r#"{
389 "name": "parsed-dep",
390 "version_req": "~1.2.0",
391 "optional": true,
392 "features": ["async"],
393 "source": "registry"
394 }"#;
395
396 let dep: Dependency = serde_json::from_str(json).unwrap();
397 assert_eq!(dep.name, "parsed-dep");
398 assert_eq!(dep.version_req, "~1.2.0");
399 assert!(dep.optional);
400 assert_eq!(dep.features, vec!["async"]);
401 }
402
403 #[test]
404 fn test_dependency_with_features() {
405 let dep = Dependency {
406 name: "feature-dep".to_string(),
407 version_req: "1.0.0".to_string(),
408 optional: false,
409 features: vec!["serde".to_string(), "async".to_string(), "full".to_string()],
410 source: DependencySource::Registry,
411 };
412
413 assert_eq!(dep.features.len(), 3);
414 assert!(dep.features.contains(&"serde".to_string()));
415 }
416
417 #[test]
419 fn test_dependency_source_default() {
420 let source = DependencySource::default();
421 assert!(matches!(source, DependencySource::Registry));
422 }
423
424 #[test]
425 fn test_dependency_source_registry_serialize() {
426 let source = DependencySource::Registry;
427 let json = serde_json::to_string(&source).unwrap();
428 assert_eq!(json, "\"registry\"");
429 }
430
431 #[test]
432 fn test_dependency_source_git_serialize() {
433 let source = DependencySource::Git {
434 url: "https://github.com/test/repo".to_string(),
435 rev: Some("main".to_string()),
436 };
437 let json = serde_json::to_string(&source).unwrap();
438 assert!(json.contains("git"));
439 assert!(json.contains("github.com"));
440 assert!(json.contains("main"));
441 }
442
443 #[test]
444 fn test_dependency_source_git_without_rev() {
445 let source = DependencySource::Git {
446 url: "https://github.com/test/repo".to_string(),
447 rev: None,
448 };
449 let json = serde_json::to_string(&source).unwrap();
450 assert!(json.contains("git"));
451 }
452
453 #[test]
454 fn test_dependency_source_path_serialize() {
455 let source = DependencySource::Path {
456 path: "/local/path/to/dep".to_string(),
457 };
458 let json = serde_json::to_string(&source).unwrap();
459 assert!(json.contains("path"));
460 assert!(json.contains("/local/path"));
461 }
462
463 #[test]
464 fn test_dependency_source_deserialize_registry() {
465 let source: DependencySource = serde_json::from_str("\"registry\"").unwrap();
466 assert!(matches!(source, DependencySource::Registry));
467 }
468
469 #[test]
470 fn test_dependency_source_deserialize_git() {
471 let json = r#"{"git": {"url": "https://github.com/test/repo", "rev": "v1.0.0"}}"#;
472 let source: DependencySource = serde_json::from_str(json).unwrap();
473 match source {
474 DependencySource::Git { url, rev } => {
475 assert!(url.contains("github.com"));
476 assert_eq!(rev, Some("v1.0.0".to_string()));
477 }
478 _ => panic!("Expected Git source"),
479 }
480 }
481
482 #[test]
483 fn test_dependency_source_clone() {
484 let source = DependencySource::Git {
485 url: "https://test.com".to_string(),
486 rev: Some("abc123".to_string()),
487 };
488 let cloned = source.clone();
489 match cloned {
490 DependencySource::Git { url, rev } => {
491 assert_eq!(url, "https://test.com");
492 assert_eq!(rev, Some("abc123".to_string()));
493 }
494 _ => panic!("Expected Git source"),
495 }
496 }
497
498 #[test]
499 fn test_dependency_source_debug() {
500 let source = DependencySource::Path {
501 path: "./local".to_string(),
502 };
503 let debug = format!("{:?}", source);
504 assert!(debug.contains("Path"));
505 }
506
507 #[test]
509 fn test_resolved_dependency_clone() {
510 let resolved = ResolvedDependency {
511 name: "resolved-pkg".to_string(),
512 version: Version::parse("1.2.3").unwrap(),
513 dependencies: vec![create_dependency("dep-a", "^1.0")],
514 };
515
516 let cloned = resolved.clone();
517 assert_eq!(resolved.name, cloned.name);
518 assert_eq!(resolved.version, cloned.version);
519 assert_eq!(resolved.dependencies.len(), cloned.dependencies.len());
520 }
521
522 #[test]
523 fn test_resolved_dependency_debug() {
524 let resolved = ResolvedDependency {
525 name: "test-pkg".to_string(),
526 version: Version::parse("2.0.0").unwrap(),
527 dependencies: vec![],
528 };
529
530 let debug = format!("{:?}", resolved);
531 assert!(debug.contains("ResolvedDependency"));
532 assert!(debug.contains("test-pkg"));
533 }
534
535 #[test]
537 fn test_dependency_resolver_new() {
538 let _resolver = DependencyResolver::new();
539 }
541
542 #[test]
543 fn test_dependency_resolver_default() {
544 let _resolver = DependencyResolver::default();
545 }
547
548 #[test]
549 fn test_dependency_resolver_add_package_versions() {
550 let mut resolver = DependencyResolver::new();
551
552 resolver.add_package_versions(
553 "my-package".to_string(),
554 vec![
555 create_version_entry("1.0.0", false),
556 create_version_entry("1.1.0", false),
557 ],
558 );
559
560 resolver.add_package_versions(
562 "other-package".to_string(),
563 vec![create_version_entry("2.0.0", false)],
564 );
565 }
566
567 #[test]
568 fn test_dependency_resolution() {
569 let mut resolver = DependencyResolver::new();
570
571 resolver.add_package_versions(
573 "package-a".to_string(),
574 vec![
575 create_version_entry("1.0.0", false),
576 create_version_entry("1.1.0", false),
577 ],
578 );
579
580 let deps = vec![create_dependency("package-a", "^1.0")];
581
582 let root_version = Version::parse("1.0.0").unwrap();
583 let resolved = resolver.resolve("root", &root_version, deps);
584
585 assert!(resolved.is_ok());
586 let resolved = resolved.unwrap();
587 assert_eq!(resolved.len(), 1);
588 assert_eq!(resolved[0].name, "package-a");
589 assert_eq!(resolved[0].version, Version::parse("1.1.0").unwrap());
590 }
591
592 #[test]
593 fn test_dependency_resolution_exact_version() {
594 let mut resolver = DependencyResolver::new();
595
596 resolver.add_package_versions(
597 "exact-pkg".to_string(),
598 vec![
599 create_version_entry("1.0.0", false),
600 create_version_entry("1.1.0", false),
601 create_version_entry("2.0.0", false),
602 ],
603 );
604
605 let deps = vec![create_dependency("exact-pkg", "=1.0.0")];
606 let root_version = Version::parse("1.0.0").unwrap();
607 let resolved = resolver.resolve("root", &root_version, deps).unwrap();
608
609 assert_eq!(resolved[0].version, Version::parse("1.0.0").unwrap());
610 }
611
612 #[test]
613 fn test_dependency_resolution_yanked_excluded() {
614 let mut resolver = DependencyResolver::new();
615
616 resolver.add_package_versions(
617 "yanked-pkg".to_string(),
618 vec![
619 create_version_entry("1.0.0", false),
620 create_version_entry("1.1.0", true), create_version_entry("1.0.5", false),
622 ],
623 );
624
625 let deps = vec![create_dependency("yanked-pkg", "^1.0")];
626 let root_version = Version::parse("1.0.0").unwrap();
627 let resolved = resolver.resolve("root", &root_version, deps).unwrap();
628
629 assert_eq!(resolved[0].version, Version::parse("1.0.5").unwrap());
631 }
632
633 #[test]
634 fn test_dependency_resolution_package_not_found() {
635 let resolver = DependencyResolver::new();
636
637 let deps = vec![create_dependency("nonexistent", "^1.0")];
638 let root_version = Version::parse("1.0.0").unwrap();
639 let result = resolver.resolve("root", &root_version, deps);
640
641 assert!(result.is_err());
642 }
643
644 #[test]
645 fn test_dependency_resolution_no_compatible_version() {
646 let mut resolver = DependencyResolver::new();
647
648 resolver.add_package_versions(
649 "old-pkg".to_string(),
650 vec![create_version_entry("0.5.0", false)],
651 );
652
653 let deps = vec![create_dependency("old-pkg", "^1.0")];
654 let root_version = Version::parse("1.0.0").unwrap();
655 let result = resolver.resolve("root", &root_version, deps);
656
657 assert!(result.is_err());
658 }
659
660 #[test]
661 fn test_dependency_resolution_invalid_version_req() {
662 let mut resolver = DependencyResolver::new();
663
664 resolver
665 .add_package_versions("pkg".to_string(), vec![create_version_entry("1.0.0", false)]);
666
667 let deps = vec![create_dependency("pkg", "invalid-req")];
668 let root_version = Version::parse("1.0.0").unwrap();
669 let result = resolver.resolve("root", &root_version, deps);
670
671 assert!(result.is_err());
672 }
673
674 #[test]
675 fn test_dependency_resolution_multiple_deps() {
676 let mut resolver = DependencyResolver::new();
677
678 resolver
679 .add_package_versions("pkg-a".to_string(), vec![create_version_entry("1.0.0", false)]);
680 resolver
681 .add_package_versions("pkg-b".to_string(), vec![create_version_entry("2.0.0", false)]);
682 resolver
683 .add_package_versions("pkg-c".to_string(), vec![create_version_entry("3.0.0", false)]);
684
685 let deps = vec![
686 create_dependency("pkg-a", "^1.0"),
687 create_dependency("pkg-b", "^2.0"),
688 create_dependency("pkg-c", "^3.0"),
689 ];
690
691 let root_version = Version::parse("1.0.0").unwrap();
692 let resolved = resolver.resolve("root", &root_version, deps).unwrap();
693
694 assert_eq!(resolved.len(), 3);
695 }
696
697 #[test]
698 fn test_circular_dependency_detection() {
699 let resolver = DependencyResolver::new();
700
701 let resolved = vec![
703 ResolvedDependency {
704 name: "package-a".to_string(),
705 version: Version::parse("1.0.0").unwrap(),
706 dependencies: vec![create_dependency("package-b", "1.0")],
707 },
708 ResolvedDependency {
709 name: "package-b".to_string(),
710 version: Version::parse("1.0.0").unwrap(),
711 dependencies: vec![create_dependency("package-a", "1.0")],
712 },
713 ];
714
715 let result = resolver.check_circular_dependencies(&resolved);
716 assert!(result.is_err());
717 }
718
719 #[test]
720 fn test_no_circular_dependency() {
721 let resolver = DependencyResolver::new();
722
723 let resolved = vec![
725 ResolvedDependency {
726 name: "package-a".to_string(),
727 version: Version::parse("1.0.0").unwrap(),
728 dependencies: vec![create_dependency("package-b", "1.0")],
729 },
730 ResolvedDependency {
731 name: "package-b".to_string(),
732 version: Version::parse("1.0.0").unwrap(),
733 dependencies: vec![create_dependency("package-c", "1.0")],
734 },
735 ResolvedDependency {
736 name: "package-c".to_string(),
737 version: Version::parse("1.0.0").unwrap(),
738 dependencies: vec![],
739 },
740 ];
741
742 let result = resolver.check_circular_dependencies(&resolved);
743 assert!(result.is_ok());
744 }
745
746 #[test]
747 fn test_circular_dependency_three_node_cycle() {
748 let resolver = DependencyResolver::new();
749
750 let resolved = vec![
752 ResolvedDependency {
753 name: "a".to_string(),
754 version: Version::parse("1.0.0").unwrap(),
755 dependencies: vec![create_dependency("b", "1.0")],
756 },
757 ResolvedDependency {
758 name: "b".to_string(),
759 version: Version::parse("1.0.0").unwrap(),
760 dependencies: vec![create_dependency("c", "1.0")],
761 },
762 ResolvedDependency {
763 name: "c".to_string(),
764 version: Version::parse("1.0.0").unwrap(),
765 dependencies: vec![create_dependency("a", "1.0")],
766 },
767 ];
768
769 let result = resolver.check_circular_dependencies(&resolved);
770 assert!(result.is_err());
771 }
772
773 #[test]
775 fn test_calculate_install_order_simple() {
776 let resolver = DependencyResolver::new();
777
778 let resolved = vec![
779 ResolvedDependency {
780 name: "root".to_string(),
781 version: Version::parse("1.0.0").unwrap(),
782 dependencies: vec![create_dependency("leaf", "1.0")],
783 },
784 ResolvedDependency {
785 name: "leaf".to_string(),
786 version: Version::parse("1.0.0").unwrap(),
787 dependencies: vec![],
788 },
789 ];
790
791 let order = resolver.calculate_install_order(&resolved).unwrap();
792 assert_eq!(order.len(), 2);
793 let leaf_pos = order.iter().position(|x| x == "leaf").unwrap();
795 let root_pos = order.iter().position(|x| x == "root").unwrap();
796 assert!(leaf_pos < root_pos);
797 }
798
799 #[test]
800 fn test_calculate_install_order_chain() {
801 let resolver = DependencyResolver::new();
802
803 let resolved = vec![
805 ResolvedDependency {
806 name: "a".to_string(),
807 version: Version::parse("1.0.0").unwrap(),
808 dependencies: vec![create_dependency("b", "1.0")],
809 },
810 ResolvedDependency {
811 name: "b".to_string(),
812 version: Version::parse("1.0.0").unwrap(),
813 dependencies: vec![create_dependency("c", "1.0")],
814 },
815 ResolvedDependency {
816 name: "c".to_string(),
817 version: Version::parse("1.0.0").unwrap(),
818 dependencies: vec![],
819 },
820 ];
821
822 let order = resolver.calculate_install_order(&resolved).unwrap();
823 let c_pos = order.iter().position(|x| x == "c").unwrap();
824 let b_pos = order.iter().position(|x| x == "b").unwrap();
825 let a_pos = order.iter().position(|x| x == "a").unwrap();
826
827 assert!(c_pos < b_pos);
828 assert!(b_pos < a_pos);
829 }
830
831 #[test]
832 fn test_calculate_install_order_no_deps() {
833 let resolver = DependencyResolver::new();
834
835 let resolved = vec![ResolvedDependency {
836 name: "standalone".to_string(),
837 version: Version::parse("1.0.0").unwrap(),
838 dependencies: vec![],
839 }];
840
841 let order = resolver.calculate_install_order(&resolved).unwrap();
842 assert_eq!(order, vec!["standalone"]);
843 }
844
845 #[test]
846 fn test_calculate_install_order_empty() {
847 let resolver = DependencyResolver::new();
848 let resolved: Vec<ResolvedDependency> = vec![];
849
850 let order = resolver.calculate_install_order(&resolved).unwrap();
851 assert!(order.is_empty());
852 }
853
854 #[test]
856 fn test_dependency_conflict_clone() {
857 let conflict = DependencyConflict {
858 package: "conflicting-pkg".to_string(),
859 required_by: vec![
860 ConflictRequirement {
861 package: "pkg-a".to_string(),
862 version_req: "^1.0".to_string(),
863 },
864 ConflictRequirement {
865 package: "pkg-b".to_string(),
866 version_req: "^2.0".to_string(),
867 },
868 ],
869 };
870
871 let cloned = conflict.clone();
872 assert_eq!(conflict.package, cloned.package);
873 assert_eq!(conflict.required_by.len(), cloned.required_by.len());
874 }
875
876 #[test]
877 fn test_dependency_conflict_debug() {
878 let conflict = DependencyConflict {
879 package: "test-conflict".to_string(),
880 required_by: vec![],
881 };
882
883 let debug = format!("{:?}", conflict);
884 assert!(debug.contains("DependencyConflict"));
885 assert!(debug.contains("test-conflict"));
886 }
887
888 #[test]
889 fn test_dependency_conflict_serialize() {
890 let conflict = DependencyConflict {
891 package: "pkg".to_string(),
892 required_by: vec![ConflictRequirement {
893 package: "requirer".to_string(),
894 version_req: "^1.0".to_string(),
895 }],
896 };
897
898 let json = serde_json::to_string(&conflict).unwrap();
899 assert!(json.contains("\"package\":\"pkg\""));
900 assert!(json.contains("requirer"));
901 }
902
903 #[test]
905 fn test_conflict_requirement_clone() {
906 let req = ConflictRequirement {
907 package: "req-pkg".to_string(),
908 version_req: ">=1.0.0".to_string(),
909 };
910
911 let cloned = req.clone();
912 assert_eq!(req.package, cloned.package);
913 assert_eq!(req.version_req, cloned.version_req);
914 }
915
916 #[test]
917 fn test_conflict_requirement_debug() {
918 let req = ConflictRequirement {
919 package: "debug-pkg".to_string(),
920 version_req: "~1.2".to_string(),
921 };
922
923 let debug = format!("{:?}", req);
924 assert!(debug.contains("ConflictRequirement"));
925 }
926
927 #[test]
928 fn test_conflict_requirement_serialize() {
929 let req = ConflictRequirement {
930 package: "ser-pkg".to_string(),
931 version_req: "*".to_string(),
932 };
933
934 let json = serde_json::to_string(&req).unwrap();
935 assert!(json.contains("\"package\":\"ser-pkg\""));
936 assert!(json.contains("\"version_req\":\"*\""));
937 }
938
939 #[test]
940 fn test_conflict_requirement_deserialize() {
941 let json = r#"{"package": "de-pkg", "version_req": "^3.0"}"#;
942 let req: ConflictRequirement = serde_json::from_str(json).unwrap();
943 assert_eq!(req.package, "de-pkg");
944 assert_eq!(req.version_req, "^3.0");
945 }
946
947 #[test]
949 fn test_transitive_dependency_resolution() {
950 let mut resolver = DependencyResolver::new();
951
952 let mut deps_map = HashMap::new();
954 deps_map.insert("pkg-b".to_string(), "^1.0".to_string());
955
956 resolver.add_package_versions(
957 "pkg-a".to_string(),
958 vec![VersionEntry {
959 version: "1.0.0".to_string(),
960 download_url: "https://example.com/a.tar.gz".to_string(),
961 checksum: "abc".to_string(),
962 size: 1000,
963 published_at: "2025-01-01".to_string(),
964 yanked: false,
965 min_mockforge_version: None,
966 dependencies: deps_map,
967 downloads: 0,
968 }],
969 );
970
971 resolver
972 .add_package_versions("pkg-b".to_string(), vec![create_version_entry("1.0.0", false)]);
973
974 let deps = vec![create_dependency("pkg-a", "^1.0")];
975 let root_version = Version::parse("1.0.0").unwrap();
976 let resolved = resolver.resolve("root", &root_version, deps).unwrap();
977
978 assert_eq!(resolved.len(), 2);
980 let names: Vec<&str> = resolved.iter().map(|r| r.name.as_str()).collect();
981 assert!(names.contains(&"pkg-a"));
982 assert!(names.contains(&"pkg-b"));
983 }
984}