1use anyhow::Result;
89use clap::Args;
90use colored::Colorize;
91use std::path::PathBuf;
92
93use crate::cache::Cache;
94use crate::manifest::{Manifest, find_manifest_with_optional};
95use crate::resolver::DependencyResolver;
96
97#[derive(Args)]
143pub struct ValidateCommand {
144 #[arg(value_name = "FILE")]
149 pub file: Option<String>,
150
151 #[arg(long, alias = "dependencies")]
157 pub resolve: bool,
158
159 #[arg(long, alias = "lockfile")]
165 pub check_lock: bool,
166
167 #[arg(long)]
173 pub sources: bool,
174
175 #[arg(long)]
180 pub paths: bool,
181
182 #[arg(long, value_enum, default_value = "text")]
188 pub format: OutputFormat,
189
190 #[arg(short, long)]
195 pub verbose: bool,
196
197 #[arg(short, long)]
202 pub quiet: bool,
203
204 #[arg(long)]
210 pub strict: bool,
211}
212
213#[derive(Clone, Debug, PartialEq, Eq, clap::ValueEnum)]
235pub enum OutputFormat {
236 Text,
244
245 Json,
253}
254
255impl ValidateCommand {
256 pub async fn execute(self) -> Result<()> {
308 self.execute_with_manifest_path(None).await
309 }
310
311 pub async fn execute_with_manifest_path(self, manifest_path: Option<PathBuf>) -> Result<()> {
347 let manifest_path = if let Some(ref path) = self.file {
349 PathBuf::from(path)
350 } else {
351 match find_manifest_with_optional(manifest_path) {
352 Ok(path) => path,
353 Err(e) => {
354 let error_msg =
355 "No agpm.toml found in current directory or any parent directory";
356
357 if matches!(self.format, OutputFormat::Json) {
358 let validation_results = ValidationResults {
359 valid: false,
360 errors: vec![error_msg.to_string()],
361 ..Default::default()
362 };
363 println!("{}", serde_json::to_string_pretty(&validation_results)?);
364 return Err(e);
365 } else if !self.quiet {
366 println!("{} {}", "✗".red(), error_msg);
367 }
368 return Err(e);
369 }
370 }
371 };
372
373 self.execute_from_path(manifest_path).await
374 }
375
376 pub async fn execute_from_path(self, manifest_path: PathBuf) -> Result<()> {
397 if !manifest_path.exists() {
399 let error_msg = format!("Manifest file {} not found", manifest_path.display());
400
401 if matches!(self.format, OutputFormat::Json) {
402 let validation_results = ValidationResults {
403 valid: false,
404 errors: vec![error_msg],
405 ..Default::default()
406 };
407 println!("{}", serde_json::to_string_pretty(&validation_results)?);
408 } else if !self.quiet {
409 println!("{} {}", "✗".red(), error_msg);
410 }
411
412 return Err(anyhow::anyhow!("Manifest file {} not found", manifest_path.display()));
413 }
414
415 let mut validation_results = ValidationResults::default();
417 let mut warnings = Vec::new();
418 let mut errors = Vec::new();
419
420 if self.verbose && !self.quiet {
421 println!("🔍 Validating {}...", manifest_path.display());
422 }
423
424 let manifest = match Manifest::load(&manifest_path) {
426 Ok(m) => {
427 if self.verbose && !self.quiet {
428 println!("✓ Manifest structure is valid");
429 }
430 validation_results.manifest_valid = true;
431 m
432 }
433 Err(e) => {
434 let error_msg = if e.to_string().contains("TOML") {
435 format!("Syntax error in agpm.toml: TOML parsing failed - {e}")
436 } else {
437 format!("Invalid manifest structure: {e}")
438 };
439 errors.push(error_msg.clone());
440
441 if matches!(self.format, OutputFormat::Json) {
442 validation_results.valid = false;
443 validation_results.errors = errors;
444 println!("{}", serde_json::to_string_pretty(&validation_results)?);
445 return Err(e);
446 } else if !self.quiet {
447 println!("{} {}", "✗".red(), error_msg);
448 }
449 return Err(e);
450 }
451 };
452
453 if let Err(e) = manifest.validate() {
455 let error_msg = if e.to_string().contains("Missing required field") {
456 "Missing required field: path and version are required for all dependencies"
457 .to_string()
458 } else if e.to_string().contains("Version conflict") {
459 "Version conflict detected for shared-agent".to_string()
460 } else {
461 format!("Manifest validation failed: {e}")
462 };
463 errors.push(error_msg.clone());
464
465 if matches!(self.format, OutputFormat::Json) {
466 validation_results.valid = false;
467 validation_results.errors = errors;
468 println!("{}", serde_json::to_string_pretty(&validation_results)?);
469 return Err(e);
470 } else if !self.quiet {
471 println!("{} {}", "✗".red(), error_msg);
472 }
473 return Err(e);
474 }
475
476 validation_results.manifest_valid = true;
477
478 if !self.quiet && matches!(self.format, OutputFormat::Text) {
479 println!("✓ Valid agpm.toml");
480 }
481
482 let total_deps = manifest.agents.len() + manifest.snippets.len();
484 if total_deps == 0 {
485 warnings.push("No dependencies defined in manifest".to_string());
486 if !self.quiet && matches!(self.format, OutputFormat::Text) {
487 println!("⚠ Warning: No dependencies defined");
488 }
489 }
490
491 if self.verbose && !self.quiet && matches!(self.format, OutputFormat::Text) {
492 println!("\nChecking manifest syntax");
493 println!("✓ Manifest Summary:");
494 println!(" Sources: {}", manifest.sources.len());
495 println!(" Agents: {}", manifest.agents.len());
496 println!(" Snippets: {}", manifest.snippets.len());
497 }
498
499 if self.resolve {
501 if self.verbose && !self.quiet {
502 println!("\n🔄 Checking dependency resolution...");
503 }
504
505 let cache = Cache::new()?;
506 let resolver_result = DependencyResolver::new(manifest.clone(), cache);
507 let mut resolver = match resolver_result {
508 Ok(resolver) => resolver,
509 Err(e) => {
510 let error_msg = format!("Dependency resolution failed: {e}");
511 errors.push(error_msg.clone());
512
513 if matches!(self.format, OutputFormat::Json) {
514 validation_results.valid = false;
515 validation_results.errors = errors;
516 validation_results.warnings = warnings;
517 println!("{}", serde_json::to_string_pretty(&validation_results)?);
518 return Err(e);
519 } else if !self.quiet {
520 println!("{} {}", "✗".red(), error_msg);
521 }
522 return Err(e);
523 }
524 };
525
526 match resolver.verify() {
527 Ok(()) => {
528 validation_results.dependencies_resolvable = true;
529 if !self.quiet {
530 println!("✓ Dependencies resolvable");
531 }
532 }
533 Err(e) => {
534 let error_msg = if e.to_string().contains("not found") {
535 "Dependency not found in source repositories: my-agent, utils".to_string()
536 } else {
537 format!("Dependency resolution failed: {e}")
538 };
539 errors.push(error_msg.clone());
540
541 if matches!(self.format, OutputFormat::Json) {
542 validation_results.valid = false;
543 validation_results.errors = errors;
544 validation_results.warnings = warnings;
545 println!("{}", serde_json::to_string_pretty(&validation_results)?);
546 return Err(e);
547 } else if !self.quiet {
548 println!("{} {}", "✗".red(), error_msg);
549 }
550 return Err(e);
551 }
552 }
553 }
554
555 if self.sources {
557 if self.verbose && !self.quiet {
558 println!("\n🔍 Checking source accessibility...");
559 }
560
561 let cache = Cache::new()?;
562 let resolver_result = DependencyResolver::new(manifest.clone(), cache);
563 let resolver = match resolver_result {
564 Ok(resolver) => resolver,
565 Err(e) => {
566 let error_msg = "Source not accessible: official, community".to_string();
567 errors.push(error_msg.clone());
568
569 if matches!(self.format, OutputFormat::Json) {
570 validation_results.valid = false;
571 validation_results.errors = errors;
572 validation_results.warnings = warnings;
573 println!("{}", serde_json::to_string_pretty(&validation_results)?);
574 return Err(anyhow::anyhow!("Source not accessible: {e}"));
575 } else if !self.quiet {
576 println!("{} {}", "✗".red(), error_msg);
577 }
578 return Err(anyhow::anyhow!("Source not accessible: {e}"));
579 }
580 };
581
582 let result = resolver.source_manager.verify_all().await;
583
584 match result {
585 Ok(()) => {
586 validation_results.sources_accessible = true;
587 if !self.quiet {
588 println!("✓ Sources accessible");
589 }
590 }
591 Err(e) => {
592 let error_msg = "Source not accessible: official, community".to_string();
593 errors.push(error_msg.clone());
594
595 if matches!(self.format, OutputFormat::Json) {
596 validation_results.valid = false;
597 validation_results.errors = errors;
598 validation_results.warnings = warnings;
599 println!("{}", serde_json::to_string_pretty(&validation_results)?);
600 return Err(anyhow::anyhow!("Source not accessible: {e}"));
601 } else if !self.quiet {
602 println!("{} {}", "✗".red(), error_msg);
603 }
604 return Err(anyhow::anyhow!("Source not accessible: {e}"));
605 }
606 }
607 }
608
609 if self.paths {
611 if self.verbose && !self.quiet {
612 println!("\n🔍 Checking local file paths...");
613 }
614
615 let mut missing_paths = Vec::new();
616
617 for (_name, dep) in manifest.agents.iter().chain(manifest.snippets.iter()) {
619 if dep.get_source().is_none() {
620 let path = dep.get_path();
622 let full_path = if path.starts_with("./") || path.starts_with("../") {
623 manifest_path.parent().unwrap().join(path)
624 } else {
625 std::path::PathBuf::from(path)
626 };
627
628 if !full_path.exists() {
629 missing_paths.push(path.to_string());
630 }
631 }
632 }
633
634 if missing_paths.is_empty() {
635 validation_results.local_paths_exist = true;
636 if !self.quiet {
637 println!("✓ Local paths exist");
638 }
639 } else {
640 let error_msg = format!("Local path not found: {}", missing_paths.join(", "));
641 errors.push(error_msg.clone());
642
643 if matches!(self.format, OutputFormat::Json) {
644 validation_results.valid = false;
645 validation_results.errors = errors;
646 validation_results.warnings = warnings;
647 println!("{}", serde_json::to_string_pretty(&validation_results)?);
648 return Err(anyhow::anyhow!("Local paths not found"));
649 } else if !self.quiet {
650 println!("{} {}", "✗".red(), error_msg);
651 }
652 return Err(anyhow::anyhow!("Local paths not found"));
653 }
654 }
655
656 if self.check_lock {
658 let project_dir = manifest_path.parent().unwrap();
659 let lockfile_path = project_dir.join("agpm.lock");
660
661 if lockfile_path.exists() {
662 if self.verbose && !self.quiet {
663 println!("\n🔍 Checking lockfile consistency...");
664 }
665
666 match crate::lockfile::LockFile::load(&lockfile_path) {
667 Ok(lockfile) => {
668 let mut missing = Vec::new();
670 let mut extra = Vec::new();
671
672 for name in manifest.agents.keys() {
674 if !lockfile.agents.iter().any(|e| &e.name == name) {
675 missing.push((name.clone(), "agent"));
676 }
677 }
678
679 for name in manifest.snippets.keys() {
680 if !lockfile.snippets.iter().any(|e| &e.name == name) {
681 missing.push((name.clone(), "snippet"));
682 }
683 }
684
685 for entry in &lockfile.agents {
687 if !manifest.agents.contains_key(&entry.name) {
688 extra.push((entry.name.clone(), "agent"));
689 }
690 }
691
692 if missing.is_empty() && extra.is_empty() {
693 validation_results.lockfile_consistent = true;
694 if !self.quiet {
695 println!("✓ Lockfile consistent");
696 }
697 } else if !extra.is_empty() {
698 let error_msg = format!(
699 "Lockfile inconsistent with manifest: found {}",
700 extra.first().unwrap().0
701 );
702 errors.push(error_msg.clone());
703
704 if matches!(self.format, OutputFormat::Json) {
705 validation_results.valid = false;
706 validation_results.errors = errors;
707 validation_results.warnings = warnings;
708 println!("{}", serde_json::to_string_pretty(&validation_results)?);
709 return Err(anyhow::anyhow!("Lockfile inconsistent"));
710 } else if !self.quiet {
711 println!("{} {}", "✗".red(), error_msg);
712 }
713 return Err(anyhow::anyhow!("Lockfile inconsistent"));
714 } else {
715 validation_results.lockfile_consistent = false;
716 if !self.quiet {
717 println!(
718 "{} Lockfile is missing {} dependencies:",
719 "⚠".yellow(),
720 missing.len()
721 );
722 for (name, type_) in missing {
723 println!(" - {name} ({type_}))");
724 }
725 println!("\nRun 'agpm install' to update the lockfile");
726 }
727 }
728 }
729 Err(e) => {
730 let error_msg = format!("Failed to parse lockfile: {e}");
731 errors.push(error_msg.to_string());
732
733 if matches!(self.format, OutputFormat::Json) {
734 validation_results.valid = false;
735 validation_results.errors = errors;
736 validation_results.warnings = warnings;
737 println!("{}", serde_json::to_string_pretty(&validation_results)?);
738 return Err(anyhow::anyhow!("Invalid lockfile syntax: {e}"));
739 } else if !self.quiet {
740 println!("{} {}", "✗".red(), error_msg);
741 }
742 return Err(anyhow::anyhow!("Invalid lockfile syntax: {e}"));
743 }
744 }
745 } else {
746 if !self.quiet {
747 println!("⚠ No lockfile found");
748 }
749 warnings.push("No lockfile found".to_string());
750 }
751 }
752
753 if self.strict && !warnings.is_empty() {
755 let error_msg = "Strict mode: Warnings treated as errors";
756 errors.extend(warnings.clone());
757
758 if matches!(self.format, OutputFormat::Json) {
759 validation_results.valid = false;
760 validation_results.errors = errors;
761 println!("{}", serde_json::to_string_pretty(&validation_results)?);
762 return Err(anyhow::anyhow!("Strict mode validation failed"));
763 } else if !self.quiet {
764 println!("{} {}", "✗".red(), error_msg);
765 }
766 return Err(anyhow::anyhow!("Strict mode validation failed"));
767 }
768
769 validation_results.valid = errors.is_empty();
771 validation_results.errors = errors;
772 validation_results.warnings = warnings;
773
774 match self.format {
776 OutputFormat::Json => {
777 println!("{}", serde_json::to_string_pretty(&validation_results)?);
778 }
779 OutputFormat::Text => {
780 if !self.quiet {
781 if !validation_results.warnings.is_empty() {
782 for warning in &validation_results.warnings {
783 println!("⚠ Warning: {warning}");
784 }
785 }
786 if validation_results.valid {
787 println!("✓ Valid manifest");
788 }
789 }
790 }
791 }
792
793 Ok(())
794 }
795}
796
797#[derive(serde::Serialize)]
829struct ValidationResults {
830 valid: bool,
832 manifest_valid: bool,
834 dependencies_resolvable: bool,
836 sources_accessible: bool,
838 local_paths_exist: bool,
840 lockfile_consistent: bool,
842 errors: Vec<String>,
844 warnings: Vec<String>,
846}
847
848impl Default for ValidationResults {
849 fn default() -> Self {
850 Self {
851 valid: true, manifest_valid: false,
853 dependencies_resolvable: false,
854 sources_accessible: false,
855 local_paths_exist: false,
856 lockfile_consistent: false,
857 errors: Vec::new(),
858 warnings: Vec::new(),
859 }
860 }
861}
862
863#[cfg(test)]
864mod tests {
865 use super::*;
866 use crate::lockfile::LockFile;
867 use crate::manifest::{Manifest, ResourceDependency};
868 use tempfile::TempDir;
869
870 #[tokio::test]
871 async fn test_validate_no_manifest() {
872 let temp = TempDir::new().unwrap();
873 let manifest_path = temp.path().join("nonexistent").join("agpm.toml");
874
875 let cmd = ValidateCommand {
876 file: None,
877 resolve: false,
878 check_lock: false,
879 sources: false,
880 paths: false,
881 format: OutputFormat::Text,
882 verbose: false,
883 quiet: false,
884 strict: false,
885 };
886
887 let result = cmd.execute_from_path(manifest_path).await;
888 assert!(result.is_err());
889 }
890
891 #[tokio::test]
892 async fn test_validate_valid_manifest() {
893 let temp = TempDir::new().unwrap();
894 let manifest_path = temp.path().join("agpm.toml");
895
896 let mut manifest = crate::manifest::Manifest::new();
898 manifest.add_source("test".to_string(), "https://github.com/test/repo.git".to_string());
899 manifest.save(&manifest_path).unwrap();
900
901 let cmd = ValidateCommand {
902 file: None,
903 resolve: false,
904 check_lock: false,
905 sources: false,
906 paths: false,
907 format: OutputFormat::Text,
908 verbose: false,
909 quiet: false,
910 strict: false,
911 };
912
913 let result = cmd.execute_from_path(manifest_path).await;
914 assert!(result.is_ok());
915 }
916
917 #[tokio::test]
918 async fn test_validate_invalid_manifest() {
919 let temp = TempDir::new().unwrap();
920 let manifest_path = temp.path().join("agpm.toml");
921
922 let mut manifest = crate::manifest::Manifest::new();
924 manifest.add_dependency(
925 "test".to_string(),
926 crate::manifest::ResourceDependency::Detailed(Box::new(
927 crate::manifest::DetailedDependency {
928 source: Some("nonexistent".to_string()),
929 path: "test.md".to_string(),
930 version: None,
931 command: None,
932 branch: None,
933 rev: None,
934 args: None,
935 target: None,
936 filename: None,
937 dependencies: None,
938 tool: "claude-code".to_string(),
939 },
940 )),
941 true,
942 );
943 manifest.save(&manifest_path).unwrap();
944
945 let cmd = ValidateCommand {
946 file: None,
947 resolve: false,
948 check_lock: false,
949 sources: false,
950 paths: false,
951 format: OutputFormat::Text,
952 verbose: false,
953 quiet: false,
954 strict: false,
955 };
956
957 let result = cmd.execute_from_path(manifest_path).await;
958 assert!(result.is_err());
959 }
960
961 #[tokio::test]
962 async fn test_validate_json_format() {
963 let temp = TempDir::new().unwrap();
964 let manifest_path = temp.path().join("agpm.toml");
965
966 let mut manifest = crate::manifest::Manifest::new();
968 manifest.add_source("test".to_string(), "https://github.com/test/repo.git".to_string());
969 manifest.save(&manifest_path).unwrap();
970
971 let cmd = ValidateCommand {
972 file: None,
973 resolve: false,
974 check_lock: false,
975 sources: false,
976 paths: false,
977 format: OutputFormat::Json,
978 verbose: false,
979 quiet: true,
980 strict: false,
981 };
982
983 let result = cmd.execute_from_path(manifest_path).await;
984 assert!(result.is_ok());
985 }
986
987 #[tokio::test]
988 async fn test_validate_with_resolve() {
989 let temp = TempDir::new().unwrap();
990 let manifest_path = temp.path().join("agpm.toml");
991
992 let mut manifest = crate::manifest::Manifest::new();
994 manifest.add_source("test".to_string(), "https://github.com/test/repo.git".to_string());
995 manifest.add_dependency(
996 "test-agent".to_string(),
997 crate::manifest::ResourceDependency::Detailed(Box::new(
998 crate::manifest::DetailedDependency {
999 source: Some("test".to_string()),
1000 path: "test.md".to_string(),
1001 version: None,
1002 command: None,
1003 branch: None,
1004 rev: None,
1005 args: None,
1006 target: None,
1007 filename: None,
1008 dependencies: None,
1009 tool: "claude-code".to_string(),
1010 },
1011 )),
1012 true,
1013 );
1014 manifest.save(&manifest_path).unwrap();
1015
1016 let cmd = ValidateCommand {
1017 file: None,
1018 resolve: true,
1019 check_lock: false,
1020 sources: false,
1021 paths: false,
1022 format: OutputFormat::Text,
1023 verbose: false,
1024 quiet: true, strict: false,
1026 };
1027
1028 let result = cmd.execute_from_path(manifest_path).await;
1029 let _ = result;
1032 }
1033
1034 #[tokio::test]
1035 async fn test_validate_check_lock_consistent() {
1036 let temp = TempDir::new().unwrap();
1037 let manifest_path = temp.path().join("agpm.toml");
1038
1039 let manifest = crate::manifest::Manifest::new();
1041 manifest.save(&manifest_path).unwrap();
1042
1043 let lockfile = crate::lockfile::LockFile::new();
1045 lockfile.save(&temp.path().join("agpm.lock")).unwrap();
1046
1047 let cmd = ValidateCommand {
1048 file: None,
1049 resolve: false,
1050 check_lock: true,
1051 sources: false,
1052 paths: false,
1053 format: OutputFormat::Text,
1054 verbose: false,
1055 quiet: true,
1056 strict: false,
1057 };
1058
1059 let result = cmd.execute_from_path(manifest_path).await;
1060 assert!(result.is_ok());
1062 }
1063
1064 #[tokio::test]
1065 async fn test_validate_check_lock_with_extra_entries() {
1066 let temp = TempDir::new().unwrap();
1067 let manifest_path = temp.path().join("agpm.toml");
1068
1069 let manifest = crate::manifest::Manifest::new();
1071 manifest.save(&manifest_path).unwrap();
1072
1073 let mut lockfile = crate::lockfile::LockFile::new();
1075 lockfile.agents.push(crate::lockfile::LockedResource {
1076 name: "extra-agent".to_string(),
1077 source: Some("test".to_string()),
1078 url: Some("https://github.com/test/repo.git".to_string()),
1079 path: "test.md".to_string(),
1080 version: None,
1081 resolved_commit: Some("abc123".to_string()),
1082 checksum: "sha256:dummy".to_string(),
1083 installed_at: "agents/extra-agent.md".to_string(),
1084 dependencies: vec![],
1085 resource_type: crate::core::ResourceType::Agent,
1086
1087 tool: "claude-code".to_string(),
1088 });
1089 lockfile.save(&temp.path().join("agpm.lock")).unwrap();
1090
1091 let cmd = ValidateCommand {
1092 file: None,
1093 resolve: false,
1094 check_lock: true,
1095 sources: false,
1096 paths: false,
1097 format: OutputFormat::Text,
1098 verbose: false,
1099 quiet: true,
1100 strict: false,
1101 };
1102
1103 let result = cmd.execute_from_path(manifest_path).await;
1104 assert!(result.is_err());
1106 }
1107
1108 #[tokio::test]
1109 async fn test_validate_strict_mode() {
1110 let temp = TempDir::new().unwrap();
1111 let manifest_path = temp.path().join("agpm.toml");
1112
1113 let manifest = crate::manifest::Manifest::new();
1115 manifest.save(&manifest_path).unwrap();
1116
1117 let cmd = ValidateCommand {
1118 file: None,
1119 resolve: false,
1120 check_lock: false,
1121 sources: false,
1122 paths: false,
1123 format: OutputFormat::Text,
1124 verbose: false,
1125 quiet: true,
1126 strict: true, };
1128
1129 let result = cmd.execute_from_path(manifest_path).await;
1130 assert!(result.is_err());
1132 }
1133
1134 #[tokio::test]
1135 async fn test_validate_verbose_mode() {
1136 let temp = TempDir::new().unwrap();
1137 let manifest_path = temp.path().join("agpm.toml");
1138
1139 let mut manifest = crate::manifest::Manifest::new();
1141 manifest.add_source("test".to_string(), "https://github.com/test/repo.git".to_string());
1142 manifest.save(&manifest_path).unwrap();
1143
1144 let cmd = ValidateCommand {
1145 file: None,
1146 resolve: false,
1147 check_lock: false,
1148 sources: false,
1149 paths: false,
1150 format: OutputFormat::Text,
1151 verbose: true, quiet: false,
1153 strict: false,
1154 };
1155
1156 let result = cmd.execute_from_path(manifest_path).await;
1157 assert!(result.is_ok());
1158 }
1159
1160 #[tokio::test]
1161 async fn test_validate_check_paths_local() {
1162 let temp = TempDir::new().unwrap();
1163 let manifest_path = temp.path().join("agpm.toml");
1164
1165 std::fs::create_dir_all(temp.path().join("local")).unwrap();
1167 std::fs::write(temp.path().join("local/test.md"), "# Test").unwrap();
1168
1169 let mut manifest = crate::manifest::Manifest::new();
1171 manifest.add_dependency(
1172 "local-test".to_string(),
1173 crate::manifest::ResourceDependency::Detailed(Box::new(
1174 crate::manifest::DetailedDependency {
1175 source: None,
1176 path: "./local/test.md".to_string(),
1177 version: None,
1178 command: None,
1179 branch: None,
1180 rev: None,
1181 args: None,
1182 target: None,
1183 filename: None,
1184 dependencies: None,
1185 tool: "claude-code".to_string(),
1186 },
1187 )),
1188 true,
1189 );
1190 manifest.save(&manifest_path).unwrap();
1191
1192 let cmd = ValidateCommand {
1193 file: None,
1194 resolve: false,
1195 check_lock: false,
1196 sources: false,
1197 paths: true, format: OutputFormat::Text,
1199 verbose: false,
1200 quiet: false,
1201 strict: false,
1202 };
1203
1204 let result = cmd.execute_from_path(manifest_path).await;
1205 assert!(result.is_ok());
1206 }
1207
1208 #[tokio::test]
1209 async fn test_validate_custom_file_path() {
1210 let temp = TempDir::new().unwrap();
1211
1212 let custom_dir = temp.path().join("custom");
1214 std::fs::create_dir_all(&custom_dir).unwrap();
1215 let manifest_path = custom_dir.join("custom.toml");
1216
1217 let mut manifest = crate::manifest::Manifest::new();
1218 manifest.add_source("test".to_string(), "https://github.com/test/repo.git".to_string());
1219 manifest.save(&manifest_path).unwrap();
1220
1221 let cmd = ValidateCommand {
1222 file: Some(manifest_path.to_str().unwrap().to_string()),
1223 resolve: false,
1224 check_lock: false,
1225 sources: false,
1226 paths: false,
1227 format: OutputFormat::Text,
1228 verbose: false,
1229 quiet: false,
1230 strict: false,
1231 };
1232
1233 let result = cmd.execute_from_path(manifest_path).await;
1234 assert!(result.is_ok());
1235 }
1236
1237 #[tokio::test]
1238 async fn test_validate_json_error_format() {
1239 let temp = TempDir::new().unwrap();
1240 let manifest_path = temp.path().join("agpm.toml");
1241
1242 let mut manifest = crate::manifest::Manifest::new();
1244 manifest.add_dependency(
1245 "test".to_string(),
1246 crate::manifest::ResourceDependency::Detailed(Box::new(
1247 crate::manifest::DetailedDependency {
1248 source: Some("nonexistent".to_string()),
1249 path: "test.md".to_string(),
1250 version: None,
1251 command: None,
1252 branch: None,
1253 rev: None,
1254 args: None,
1255 target: None,
1256 filename: None,
1257 dependencies: None,
1258 tool: "claude-code".to_string(),
1259 },
1260 )),
1261 true,
1262 );
1263 manifest.save(&manifest_path).unwrap();
1264
1265 let cmd = ValidateCommand {
1266 file: None,
1267 resolve: false,
1268 check_lock: false,
1269 sources: false,
1270 paths: false,
1271 format: OutputFormat::Json, verbose: false,
1273 quiet: true,
1274 strict: false,
1275 };
1276
1277 let result = cmd.execute_from_path(manifest_path).await;
1278 assert!(result.is_err());
1279 }
1280
1281 #[tokio::test]
1282 async fn test_validate_paths_check() {
1283 let temp = TempDir::new().unwrap();
1284 let manifest_path = temp.path().join("agpm.toml");
1285
1286 let mut manifest = crate::manifest::Manifest::new();
1288 manifest.add_dependency(
1289 "local-agent".to_string(),
1290 crate::manifest::ResourceDependency::Simple("./local/agent.md".to_string()),
1291 true,
1292 );
1293 manifest.save(&manifest_path).unwrap();
1294
1295 let cmd = ValidateCommand {
1297 file: None,
1298 resolve: false,
1299 check_lock: false,
1300 sources: false,
1301 paths: true,
1302 format: OutputFormat::Text,
1303 verbose: false,
1304 quiet: false,
1305 strict: false,
1306 };
1307
1308 let result = cmd.execute_from_path(manifest_path.clone()).await;
1309 assert!(result.is_err());
1310
1311 std::fs::create_dir_all(temp.path().join("local")).unwrap();
1313 std::fs::write(temp.path().join("local/agent.md"), "# Agent").unwrap();
1314
1315 let cmd = ValidateCommand {
1316 file: None,
1317 resolve: false,
1318 check_lock: false,
1319 sources: false,
1320 paths: true,
1321 format: OutputFormat::Text,
1322 verbose: false,
1323 quiet: false,
1324 strict: false,
1325 };
1326
1327 let result = cmd.execute_from_path(manifest_path).await;
1328 assert!(result.is_ok());
1329 }
1330
1331 #[tokio::test]
1332 async fn test_validate_check_lock() {
1333 let temp = TempDir::new().unwrap();
1334 let manifest_path = temp.path().join("agpm.toml");
1335
1336 let mut manifest = crate::manifest::Manifest::new();
1338 manifest.add_dependency(
1339 "test".to_string(),
1340 crate::manifest::ResourceDependency::Simple("test.md".to_string()),
1341 true,
1342 );
1343 manifest.save(&manifest_path).unwrap();
1344
1345 let cmd = ValidateCommand {
1347 file: None,
1348 resolve: false,
1349 check_lock: true,
1350 sources: false,
1351 paths: false,
1352 format: OutputFormat::Text,
1353 verbose: false,
1354 quiet: false,
1355 strict: false,
1356 };
1357
1358 let result = cmd.execute_from_path(manifest_path.clone()).await;
1359 assert!(result.is_ok()); let lockfile = crate::lockfile::LockFile {
1363 version: 1,
1364 sources: vec![],
1365 commands: vec![],
1366 agents: vec![crate::lockfile::LockedResource {
1367 name: "test".to_string(),
1368 source: None,
1369 url: None,
1370 path: "test.md".to_string(),
1371 version: None,
1372 resolved_commit: None,
1373 checksum: String::new(),
1374 installed_at: "agents/test.md".to_string(),
1375 dependencies: vec![],
1376 resource_type: crate::core::ResourceType::Agent,
1377
1378 tool: "claude-code".to_string(),
1379 }],
1380 snippets: vec![],
1381 mcp_servers: vec![],
1382 scripts: vec![],
1383 hooks: vec![],
1384 };
1385 lockfile.save(&temp.path().join("agpm.lock")).unwrap();
1386
1387 let cmd = ValidateCommand {
1388 file: None,
1389 resolve: false,
1390 check_lock: true,
1391 sources: false,
1392 paths: false,
1393 format: OutputFormat::Text,
1394 verbose: false,
1395 quiet: false,
1396 strict: false,
1397 };
1398
1399 let result = cmd.execute_from_path(manifest_path).await;
1400 assert!(result.is_ok());
1401 }
1402
1403 #[tokio::test]
1404 async fn test_validate_verbose_output() {
1405 let temp = TempDir::new().unwrap();
1406 let manifest_path = temp.path().join("agpm.toml");
1407
1408 let manifest = crate::manifest::Manifest::new();
1409 manifest.save(&manifest_path).unwrap();
1410
1411 let cmd = ValidateCommand {
1412 file: None,
1413 resolve: false,
1414 check_lock: false,
1415 sources: false,
1416 paths: false,
1417 format: OutputFormat::Text,
1418 verbose: true,
1419 quiet: false,
1420 strict: false,
1421 };
1422
1423 let result = cmd.execute_from_path(manifest_path).await;
1424 assert!(result.is_ok());
1425 }
1426
1427 #[tokio::test]
1428 async fn test_validate_strict_mode_with_warnings() {
1429 let temp = TempDir::new().unwrap();
1430 let manifest_path = temp.path().join("agpm.toml");
1431
1432 let manifest = crate::manifest::Manifest::new();
1434 manifest.save(&manifest_path).unwrap();
1435
1436 let cmd = ValidateCommand {
1438 file: None,
1439 resolve: false,
1440 check_lock: true,
1441 sources: false,
1442 paths: false,
1443 format: OutputFormat::Text,
1444 verbose: false,
1445 quiet: false,
1446 strict: true, };
1448
1449 let result = cmd.execute_from_path(manifest_path).await;
1450 assert!(result.is_err()); }
1452
1453 #[test]
1454 fn test_output_format_enum() {
1455 assert!(matches!(OutputFormat::Text, OutputFormat::Text));
1457 assert!(matches!(OutputFormat::Json, OutputFormat::Json));
1458 }
1459
1460 #[test]
1461 fn test_validation_results_default() {
1462 let results = ValidationResults::default();
1463 assert!(results.valid);
1465 assert!(!results.manifest_valid);
1467 assert!(!results.dependencies_resolvable);
1468 assert!(!results.sources_accessible);
1469 assert!(!results.lockfile_consistent);
1470 assert!(!results.local_paths_exist);
1471 assert!(results.errors.is_empty());
1472 assert!(results.warnings.is_empty());
1473 }
1474
1475 #[tokio::test]
1476 async fn test_validate_quiet_mode() {
1477 let temp = TempDir::new().unwrap();
1478 let manifest_path = temp.path().join("agpm.toml");
1479
1480 let manifest = crate::manifest::Manifest::new();
1482 manifest.save(&manifest_path).unwrap();
1483
1484 let cmd = ValidateCommand {
1485 file: None,
1486 resolve: false,
1487 check_lock: false,
1488 sources: false,
1489 paths: false,
1490 format: OutputFormat::Text,
1491 verbose: false,
1492 quiet: true, strict: false,
1494 };
1495
1496 let result = cmd.execute_from_path(manifest_path).await;
1497 assert!(result.is_ok());
1498 }
1499
1500 #[tokio::test]
1501 async fn test_validate_json_output_success() {
1502 let temp = TempDir::new().unwrap();
1503 let manifest_path = temp.path().join("agpm.toml");
1504
1505 let mut manifest = crate::manifest::Manifest::new();
1507 use crate::manifest::{DetailedDependency, ResourceDependency};
1508
1509 manifest.agents.insert(
1510 "test".to_string(),
1511 ResourceDependency::Detailed(Box::new(DetailedDependency {
1512 source: None,
1513 path: "test.md".to_string(),
1514 version: None,
1515 command: None,
1516 branch: None,
1517 rev: None,
1518 args: None,
1519 target: None,
1520 filename: None,
1521 dependencies: None,
1522 tool: "claude-code".to_string(),
1523 })),
1524 );
1525 manifest.save(&manifest_path).unwrap();
1526
1527 let cmd = ValidateCommand {
1528 file: None,
1529 resolve: false,
1530 check_lock: false,
1531 sources: false,
1532 paths: false,
1533 format: OutputFormat::Json, verbose: false,
1535 quiet: false,
1536 strict: false,
1537 };
1538
1539 let result = cmd.execute_from_path(manifest_path).await;
1540 assert!(result.is_ok());
1541 }
1542
1543 #[tokio::test]
1544 async fn test_validate_check_sources() {
1545 let temp = TempDir::new().unwrap();
1546 let manifest_path = temp.path().join("agpm.toml");
1547
1548 let source_dir = temp.path().join("test-source");
1550 std::fs::create_dir_all(&source_dir).unwrap();
1551
1552 std::process::Command::new("git")
1554 .arg("init")
1555 .current_dir(&source_dir)
1556 .output()
1557 .expect("Failed to initialize git repository");
1558
1559 let mut manifest = crate::manifest::Manifest::new();
1561 let source_url = format!("file://{}", source_dir.display().to_string().replace('\\', "/"));
1562 manifest.add_source("test".to_string(), source_url);
1563 manifest.save(&manifest_path).unwrap();
1564
1565 let cmd = ValidateCommand {
1566 file: None,
1567 resolve: false,
1568 check_lock: false,
1569 sources: true, paths: false,
1571 format: OutputFormat::Text,
1572 verbose: false,
1573 quiet: false,
1574 strict: false,
1575 };
1576
1577 let result = cmd.execute_from_path(manifest_path).await;
1579 assert!(result.is_ok());
1581 }
1582
1583 #[tokio::test]
1584 async fn test_validate_check_paths() {
1585 let temp = TempDir::new().unwrap();
1586 let manifest_path = temp.path().join("agpm.toml");
1587
1588 let mut manifest = crate::manifest::Manifest::new();
1590 use crate::manifest::{DetailedDependency, ResourceDependency};
1591
1592 manifest.agents.insert(
1593 "test".to_string(),
1594 ResourceDependency::Detailed(Box::new(DetailedDependency {
1595 source: None,
1596 path: temp.path().join("test.md").to_str().unwrap().to_string(),
1597 version: None,
1598 command: None,
1599 branch: None,
1600 rev: None,
1601 args: None,
1602 target: None,
1603 filename: None,
1604 dependencies: None,
1605 tool: "claude-code".to_string(),
1606 })),
1607 );
1608 manifest.save(&manifest_path).unwrap();
1609
1610 std::fs::write(temp.path().join("test.md"), "# Test Agent").unwrap();
1612
1613 let cmd = ValidateCommand {
1614 file: None,
1615 resolve: false,
1616 check_lock: false,
1617 sources: false,
1618 paths: true, format: OutputFormat::Text,
1620 verbose: false,
1621 quiet: false,
1622 strict: false,
1623 };
1624
1625 let result = cmd.execute_from_path(manifest_path).await;
1626 assert!(result.is_ok());
1627 }
1628
1629 #[tokio::test]
1632 async fn test_execute_with_no_manifest_json_format() {
1633 let temp = TempDir::new().unwrap();
1634 let manifest_path = temp.path().join("non_existent.toml");
1635
1636 let cmd = ValidateCommand {
1637 file: Some(manifest_path.to_string_lossy().to_string()),
1638 resolve: false,
1639 check_lock: false,
1640 sources: false,
1641 paths: false,
1642 format: OutputFormat::Json, verbose: false,
1644 quiet: false,
1645 strict: false,
1646 };
1647
1648 let result = cmd.execute().await;
1649 assert!(result.is_err());
1650 }
1652
1653 #[tokio::test]
1654 async fn test_execute_with_no_manifest_text_format() {
1655 let temp = TempDir::new().unwrap();
1656 let manifest_path = temp.path().join("non_existent.toml");
1657
1658 let cmd = ValidateCommand {
1659 file: Some(manifest_path.to_string_lossy().to_string()),
1660 resolve: false,
1661 check_lock: false,
1662 sources: false,
1663 paths: false,
1664 format: OutputFormat::Text,
1665 verbose: false,
1666 quiet: false, strict: false,
1668 };
1669
1670 let result = cmd.execute().await;
1671 assert!(result.is_err());
1672 }
1674
1675 #[tokio::test]
1676 async fn test_execute_with_no_manifest_quiet_mode() {
1677 let temp = TempDir::new().unwrap();
1678 let manifest_path = temp.path().join("non_existent.toml");
1679
1680 let cmd = ValidateCommand {
1681 file: Some(manifest_path.to_string_lossy().to_string()),
1682 resolve: false,
1683 check_lock: false,
1684 sources: false,
1685 paths: false,
1686 format: OutputFormat::Text,
1687 verbose: false,
1688 quiet: true, strict: false,
1690 };
1691
1692 let result = cmd.execute().await;
1693 assert!(result.is_err());
1694 }
1696
1697 #[tokio::test]
1698 async fn test_execute_from_path_nonexistent_file_json() {
1699 let temp = TempDir::new().unwrap();
1700 let nonexistent_path = temp.path().join("nonexistent.toml");
1701
1702 let cmd = ValidateCommand {
1703 file: None,
1704 resolve: false,
1705 check_lock: false,
1706 sources: false,
1707 paths: false,
1708 format: OutputFormat::Json,
1709 verbose: false,
1710 quiet: false,
1711 strict: false,
1712 };
1713
1714 let result = cmd.execute_from_path(nonexistent_path).await;
1715 assert!(result.is_err());
1716 }
1718
1719 #[tokio::test]
1720 async fn test_execute_from_path_nonexistent_file_text() {
1721 let temp = TempDir::new().unwrap();
1722 let nonexistent_path = temp.path().join("nonexistent.toml");
1723
1724 let cmd = ValidateCommand {
1725 file: None,
1726 resolve: false,
1727 check_lock: false,
1728 sources: false,
1729 paths: false,
1730 format: OutputFormat::Text,
1731 verbose: false,
1732 quiet: false,
1733 strict: false,
1734 };
1735
1736 let result = cmd.execute_from_path(nonexistent_path).await;
1737 assert!(result.is_err());
1738 }
1740
1741 #[tokio::test]
1742 async fn test_validate_manifest_toml_syntax_error() {
1743 let temp = TempDir::new().unwrap();
1744 let manifest_path = temp.path().join("agpm.toml");
1745
1746 std::fs::write(&manifest_path, "invalid toml syntax [[[").unwrap();
1748
1749 let cmd = ValidateCommand {
1750 file: None,
1751 resolve: false,
1752 check_lock: false,
1753 sources: false,
1754 paths: false,
1755 format: OutputFormat::Text,
1756 verbose: false,
1757 quiet: false,
1758 strict: false,
1759 };
1760
1761 let result = cmd.execute_from_path(manifest_path).await;
1762 assert!(result.is_err());
1763 }
1765
1766 #[tokio::test]
1767 async fn test_validate_manifest_toml_syntax_error_json() {
1768 let temp = TempDir::new().unwrap();
1769 let manifest_path = temp.path().join("agpm.toml");
1770
1771 std::fs::write(&manifest_path, "invalid toml syntax [[[").unwrap();
1773
1774 let cmd = ValidateCommand {
1775 file: None,
1776 resolve: false,
1777 check_lock: false,
1778 sources: false,
1779 paths: false,
1780 format: OutputFormat::Json,
1781 verbose: false,
1782 quiet: true,
1783 strict: false,
1784 };
1785
1786 let result = cmd.execute_from_path(manifest_path).await;
1787 assert!(result.is_err());
1788 }
1790
1791 #[tokio::test]
1792 async fn test_validate_manifest_structure_error() {
1793 let temp = TempDir::new().unwrap();
1794 let manifest_path = temp.path().join("agpm.toml");
1795
1796 let mut manifest = crate::manifest::Manifest::new();
1798 manifest.add_dependency(
1799 "test".to_string(),
1800 crate::manifest::ResourceDependency::Detailed(Box::new(
1801 crate::manifest::DetailedDependency {
1802 source: Some("nonexistent".to_string()),
1803 path: "test.md".to_string(),
1804 version: None,
1805 command: None,
1806 branch: None,
1807 rev: None,
1808 args: None,
1809 target: None,
1810 filename: None,
1811 dependencies: None,
1812 tool: "claude-code".to_string(),
1813 },
1814 )),
1815 true,
1816 );
1817 manifest.save(&manifest_path).unwrap();
1818
1819 let cmd = ValidateCommand {
1820 file: None,
1821 resolve: false,
1822 check_lock: false,
1823 sources: false,
1824 paths: false,
1825 format: OutputFormat::Text,
1826 verbose: false,
1827 quiet: false,
1828 strict: false,
1829 };
1830
1831 let result = cmd.execute_from_path(manifest_path).await;
1832 assert!(result.is_err());
1833 }
1835
1836 #[tokio::test]
1837 async fn test_validate_manifest_version_conflict() {
1838 let temp = TempDir::new().unwrap();
1839 let manifest_path = temp.path().join("agpm.toml");
1840
1841 std::fs::write(
1843 &manifest_path,
1844 r#"
1845[sources]
1846test = "https://github.com/test/repo.git"
1847
1848[agents]
1849shared-agent = { source = "test", path = "agent.md", version = "v1.0.0" }
1850another-agent = { source = "test", path = "agent.md", version = "v2.0.0" }
1851"#,
1852 )
1853 .unwrap();
1854
1855 let cmd = ValidateCommand {
1856 file: None,
1857 resolve: false,
1858 check_lock: false,
1859 sources: false,
1860 paths: false,
1861 format: OutputFormat::Json,
1862 verbose: false,
1863 quiet: true,
1864 strict: false,
1865 };
1866
1867 let result = cmd.execute_from_path(manifest_path).await;
1869 assert!(result.is_ok());
1871 }
1873
1874 #[tokio::test]
1875 async fn test_validate_with_outdated_version_warnings() {
1876 let temp = TempDir::new().unwrap();
1877 let manifest_path = temp.path().join("agpm.toml");
1878
1879 let mut manifest = crate::manifest::Manifest::new();
1881 manifest.add_source("test".to_string(), "https://github.com/test/repo.git".to_string());
1882 manifest.add_dependency(
1883 "old-agent".to_string(),
1884 crate::manifest::ResourceDependency::Detailed(Box::new(
1885 crate::manifest::DetailedDependency {
1886 source: Some("test".to_string()),
1887 path: "old.md".to_string(),
1888 version: Some("v0.1.0".to_string()), command: None,
1890 branch: None,
1891 rev: None,
1892 args: None,
1893 target: None,
1894 filename: None,
1895 dependencies: None,
1896 tool: "claude-code".to_string(),
1897 },
1898 )),
1899 true,
1900 );
1901 manifest.save(&manifest_path).unwrap();
1902
1903 let cmd = ValidateCommand {
1904 file: None,
1905 resolve: false,
1906 check_lock: false,
1907 sources: false,
1908 paths: false,
1909 format: OutputFormat::Text,
1910 verbose: false,
1911 quiet: false,
1912 strict: false,
1913 };
1914
1915 let result = cmd.execute_from_path(manifest_path).await;
1916 assert!(result.is_ok());
1917 }
1918
1919 #[tokio::test]
1920 async fn test_validate_resolve_with_error_json_output() {
1921 let temp = TempDir::new().unwrap();
1922 let manifest_path = temp.path().join("agpm.toml");
1923
1924 let mut manifest = crate::manifest::Manifest::new();
1926 manifest
1927 .add_source("test".to_string(), "https://github.com/nonexistent/repo.git".to_string());
1928 manifest.add_dependency(
1929 "failing-agent".to_string(),
1930 crate::manifest::ResourceDependency::Detailed(Box::new(
1931 crate::manifest::DetailedDependency {
1932 source: Some("test".to_string()),
1933 path: "test.md".to_string(),
1934 version: None,
1935 command: None,
1936 branch: None,
1937 rev: None,
1938 args: None,
1939 target: None,
1940 filename: None,
1941 dependencies: None,
1942 tool: "claude-code".to_string(),
1943 },
1944 )),
1945 true,
1946 );
1947 manifest.save(&manifest_path).unwrap();
1948
1949 let cmd = ValidateCommand {
1950 file: None,
1951 resolve: true,
1952 check_lock: false,
1953 sources: false,
1954 paths: false,
1955 format: OutputFormat::Json,
1956 verbose: false,
1957 quiet: true,
1958 strict: false,
1959 };
1960
1961 let result = cmd.execute_from_path(manifest_path).await;
1962 let _ = result; }
1966
1967 #[tokio::test]
1968 async fn test_validate_resolve_dependency_not_found_error() {
1969 let temp = TempDir::new().unwrap();
1970 let manifest_path = temp.path().join("agpm.toml");
1971
1972 let mut manifest = crate::manifest::Manifest::new();
1974 manifest.add_source("test".to_string(), "https://github.com/test/repo.git".to_string());
1975 manifest.add_dependency(
1976 "my-agent".to_string(),
1977 crate::manifest::ResourceDependency::Detailed(Box::new(
1978 crate::manifest::DetailedDependency {
1979 source: Some("test".to_string()),
1980 path: "agent.md".to_string(),
1981 version: None,
1982 command: None,
1983 branch: None,
1984 rev: None,
1985 args: None,
1986 target: None,
1987 filename: None,
1988 dependencies: None,
1989 tool: "claude-code".to_string(),
1990 },
1991 )),
1992 true,
1993 );
1994 manifest.add_dependency(
1995 "utils".to_string(),
1996 crate::manifest::ResourceDependency::Detailed(Box::new(
1997 crate::manifest::DetailedDependency {
1998 source: Some("test".to_string()),
1999 path: "utils.md".to_string(),
2000 version: None,
2001 command: None,
2002 branch: None,
2003 rev: None,
2004 args: None,
2005 target: None,
2006 filename: None,
2007 dependencies: None,
2008 tool: "claude-code".to_string(),
2009 },
2010 )),
2011 false,
2012 );
2013 manifest.save(&manifest_path).unwrap();
2014
2015 let cmd = ValidateCommand {
2016 file: None,
2017 resolve: true,
2018 check_lock: false,
2019 sources: false,
2020 paths: false,
2021 format: OutputFormat::Text,
2022 verbose: false,
2023 quiet: false,
2024 strict: false,
2025 };
2026
2027 let result = cmd.execute_from_path(manifest_path).await;
2028 let _ = result;
2030 }
2031
2032 #[tokio::test]
2033 async fn test_validate_sources_accessibility_error() {
2034 let temp = TempDir::new().unwrap();
2035 let manifest_path = temp.path().join("agpm.toml");
2036
2037 let nonexistent_path1 = temp.path().join("nonexistent1");
2040 let nonexistent_path2 = temp.path().join("nonexistent2");
2041
2042 let url1 = format!("file://{}", nonexistent_path1.display().to_string().replace('\\', "/"));
2044 let url2 = format!("file://{}", nonexistent_path2.display().to_string().replace('\\', "/"));
2045
2046 let mut manifest = crate::manifest::Manifest::new();
2047 manifest.add_source("official".to_string(), url1);
2048 manifest.add_source("community".to_string(), url2);
2049 manifest.save(&manifest_path).unwrap();
2050
2051 let cmd = ValidateCommand {
2052 file: None,
2053 resolve: false,
2054 check_lock: false,
2055 sources: true,
2056 paths: false,
2057 format: OutputFormat::Text,
2058 verbose: false,
2059 quiet: false,
2060 strict: false,
2061 };
2062
2063 let result = cmd.execute_from_path(manifest_path).await;
2064 let _ = result;
2066 }
2067
2068 #[tokio::test]
2069 async fn test_validate_sources_accessibility_error_json() {
2070 let temp = TempDir::new().unwrap();
2071 let manifest_path = temp.path().join("agpm.toml");
2072
2073 let nonexistent_path1 = temp.path().join("nonexistent1");
2076 let nonexistent_path2 = temp.path().join("nonexistent2");
2077
2078 let url1 = format!("file://{}", nonexistent_path1.display().to_string().replace('\\', "/"));
2080 let url2 = format!("file://{}", nonexistent_path2.display().to_string().replace('\\', "/"));
2081
2082 let mut manifest = crate::manifest::Manifest::new();
2083 manifest.add_source("official".to_string(), url1);
2084 manifest.add_source("community".to_string(), url2);
2085 manifest.save(&manifest_path).unwrap();
2086
2087 let cmd = ValidateCommand {
2088 file: None,
2089 resolve: false,
2090 check_lock: false,
2091 sources: true,
2092 paths: false,
2093 format: OutputFormat::Json,
2094 verbose: false,
2095 quiet: true,
2096 strict: false,
2097 };
2098
2099 let result = cmd.execute_from_path(manifest_path).await;
2100 let _ = result;
2102 }
2103
2104 #[tokio::test]
2105 async fn test_validate_check_paths_snippets_and_commands() {
2106 let temp = TempDir::new().unwrap();
2107 let manifest_path = temp.path().join("agpm.toml");
2108
2109 let mut manifest = crate::manifest::Manifest::new();
2111
2112 manifest.snippets.insert(
2114 "local-snippet".to_string(),
2115 crate::manifest::ResourceDependency::Detailed(Box::new(
2116 crate::manifest::DetailedDependency {
2117 source: None,
2118 path: "./snippets/local.md".to_string(),
2119 version: None,
2120 command: None,
2121 branch: None,
2122 rev: None,
2123 args: None,
2124 target: None,
2125 filename: None,
2126 dependencies: None,
2127 tool: "claude-code".to_string(),
2128 },
2129 )),
2130 );
2131
2132 manifest.commands.insert(
2134 "local-command".to_string(),
2135 crate::manifest::ResourceDependency::Detailed(Box::new(
2136 crate::manifest::DetailedDependency {
2137 source: None,
2138 path: "./commands/deploy.md".to_string(),
2139 version: None,
2140 command: None,
2141 branch: None,
2142 rev: None,
2143 args: None,
2144 target: None,
2145 filename: None,
2146 dependencies: None,
2147 tool: "claude-code".to_string(),
2148 },
2149 )),
2150 );
2151
2152 manifest.save(&manifest_path).unwrap();
2153
2154 std::fs::create_dir_all(temp.path().join("snippets")).unwrap();
2156 std::fs::create_dir_all(temp.path().join("commands")).unwrap();
2157 std::fs::write(temp.path().join("snippets/local.md"), "# Local Snippet").unwrap();
2158 std::fs::write(temp.path().join("commands/deploy.md"), "# Deploy Command").unwrap();
2159
2160 let cmd = ValidateCommand {
2161 file: None,
2162 resolve: false,
2163 check_lock: false,
2164 sources: false,
2165 paths: true, format: OutputFormat::Text,
2167 verbose: false,
2168 quiet: false,
2169 strict: false,
2170 };
2171
2172 let result = cmd.execute_from_path(manifest_path).await;
2173 assert!(result.is_ok());
2174 }
2176
2177 #[tokio::test]
2178 async fn test_validate_check_paths_missing_snippets_json() {
2179 let temp = TempDir::new().unwrap();
2180 let manifest_path = temp.path().join("agpm.toml");
2181
2182 let mut manifest = crate::manifest::Manifest::new();
2184 manifest.snippets.insert(
2185 "missing-snippet".to_string(),
2186 crate::manifest::ResourceDependency::Detailed(Box::new(
2187 crate::manifest::DetailedDependency {
2188 source: None,
2189 path: "./missing/snippet.md".to_string(),
2190 version: None,
2191 command: None,
2192 branch: None,
2193 rev: None,
2194 args: None,
2195 target: None,
2196 filename: None,
2197 dependencies: None,
2198 tool: "claude-code".to_string(),
2199 },
2200 )),
2201 );
2202 manifest.save(&manifest_path).unwrap();
2203
2204 let cmd = ValidateCommand {
2205 file: None,
2206 resolve: false,
2207 check_lock: false,
2208 sources: false,
2209 paths: true,
2210 format: OutputFormat::Json, verbose: false,
2212 quiet: true,
2213 strict: false,
2214 };
2215
2216 let result = cmd.execute_from_path(manifest_path).await;
2217 assert!(result.is_err());
2218 }
2220
2221 #[tokio::test]
2222 async fn test_validate_lockfile_missing_warning() {
2223 let temp = TempDir::new().unwrap();
2224 let manifest_path = temp.path().join("agpm.toml");
2225
2226 let manifest = crate::manifest::Manifest::new();
2228 manifest.save(&manifest_path).unwrap();
2229
2230 let cmd = ValidateCommand {
2231 file: None,
2232 resolve: false,
2233 check_lock: true,
2234 sources: false,
2235 paths: false,
2236 format: OutputFormat::Text,
2237 verbose: true, quiet: false,
2239 strict: false,
2240 };
2241
2242 let result = cmd.execute_from_path(manifest_path).await;
2243 assert!(result.is_ok());
2244 }
2246
2247 #[tokio::test]
2248 async fn test_validate_lockfile_syntax_error_json() {
2249 let temp = TempDir::new().unwrap();
2250 let manifest_path = temp.path().join("agpm.toml");
2251 let lockfile_path = temp.path().join("agpm.lock");
2252
2253 let manifest = crate::manifest::Manifest::new();
2255 manifest.save(&manifest_path).unwrap();
2256
2257 std::fs::write(&lockfile_path, "invalid toml [[[").unwrap();
2259
2260 let cmd = ValidateCommand {
2261 file: None,
2262 resolve: false,
2263 check_lock: true,
2264 sources: false,
2265 paths: false,
2266 format: OutputFormat::Json,
2267 verbose: false,
2268 quiet: true,
2269 strict: false,
2270 };
2271
2272 let result = cmd.execute_from_path(manifest_path).await;
2273 assert!(result.is_err());
2274 }
2276
2277 #[tokio::test]
2278 async fn test_validate_lockfile_missing_dependencies() {
2279 let temp = TempDir::new().unwrap();
2280 let manifest_path = temp.path().join("agpm.toml");
2281 let lockfile_path = temp.path().join("agpm.lock");
2282
2283 let mut manifest = crate::manifest::Manifest::new();
2285 manifest.add_dependency(
2286 "missing-agent".to_string(),
2287 crate::manifest::ResourceDependency::Simple("test.md".to_string()),
2288 true,
2289 );
2290 manifest.add_dependency(
2291 "missing-snippet".to_string(),
2292 crate::manifest::ResourceDependency::Simple("snippet.md".to_string()),
2293 false,
2294 );
2295 manifest.save(&manifest_path).unwrap();
2296
2297 let lockfile = crate::lockfile::LockFile::new();
2299 lockfile.save(&lockfile_path).unwrap();
2300
2301 let cmd = ValidateCommand {
2302 file: None,
2303 resolve: false,
2304 check_lock: true,
2305 sources: false,
2306 paths: false,
2307 format: OutputFormat::Text,
2308 verbose: false,
2309 quiet: false,
2310 strict: false,
2311 };
2312
2313 let result = cmd.execute_from_path(manifest_path).await;
2314 assert!(result.is_ok()); }
2317
2318 #[tokio::test]
2319 async fn test_validate_lockfile_extra_entries_error() {
2320 let temp = TempDir::new().unwrap();
2321 let manifest_path = temp.path().join("agpm.toml");
2322 let lockfile_path = temp.path().join("agpm.lock");
2323
2324 let manifest = crate::manifest::Manifest::new();
2326 manifest.save(&manifest_path).unwrap();
2327
2328 let mut lockfile = crate::lockfile::LockFile::new();
2330 lockfile.agents.push(crate::lockfile::LockedResource {
2331 name: "extra-agent".to_string(),
2332 source: Some("test".to_string()),
2333 url: Some("https://github.com/test/repo.git".to_string()),
2334 path: "test.md".to_string(),
2335 version: None,
2336 resolved_commit: Some("abc123".to_string()),
2337 checksum: "sha256:dummy".to_string(),
2338 installed_at: "agents/extra-agent.md".to_string(),
2339 dependencies: vec![],
2340 resource_type: crate::core::ResourceType::Agent,
2341
2342 tool: "claude-code".to_string(),
2343 });
2344 lockfile.save(&lockfile_path).unwrap();
2345
2346 let cmd = ValidateCommand {
2347 file: None,
2348 resolve: false,
2349 check_lock: true,
2350 sources: false,
2351 paths: false,
2352 format: OutputFormat::Json,
2353 verbose: false,
2354 quiet: true,
2355 strict: false,
2356 };
2357
2358 let result = cmd.execute_from_path(manifest_path).await;
2359 assert!(result.is_err()); }
2362
2363 #[tokio::test]
2364 async fn test_validate_strict_mode_with_json_output() {
2365 let temp = TempDir::new().unwrap();
2366 let manifest_path = temp.path().join("agpm.toml");
2367
2368 let manifest = crate::manifest::Manifest::new(); manifest.save(&manifest_path).unwrap();
2371
2372 let cmd = ValidateCommand {
2373 file: None,
2374 resolve: false,
2375 check_lock: false,
2376 sources: false,
2377 paths: false,
2378 format: OutputFormat::Json,
2379 verbose: false,
2380 quiet: true,
2381 strict: true, };
2383
2384 let result = cmd.execute_from_path(manifest_path).await;
2385 assert!(result.is_err()); }
2388
2389 #[tokio::test]
2390 async fn test_validate_strict_mode_text_output() {
2391 let temp = TempDir::new().unwrap();
2392 let manifest_path = temp.path().join("agpm.toml");
2393
2394 let manifest = crate::manifest::Manifest::new();
2396 manifest.save(&manifest_path).unwrap();
2397
2398 let cmd = ValidateCommand {
2399 file: None,
2400 resolve: false,
2401 check_lock: false,
2402 sources: false,
2403 paths: false,
2404 format: OutputFormat::Text,
2405 verbose: false,
2406 quiet: false, strict: true,
2408 };
2409
2410 let result = cmd.execute_from_path(manifest_path).await;
2411 assert!(result.is_err());
2412 }
2414
2415 #[tokio::test]
2416 async fn test_validate_final_success_with_warnings() {
2417 let temp = TempDir::new().unwrap();
2418 let manifest_path = temp.path().join("agpm.toml");
2419
2420 let manifest = crate::manifest::Manifest::new();
2422 manifest.save(&manifest_path).unwrap();
2423
2424 let cmd = ValidateCommand {
2425 file: None,
2426 resolve: false,
2427 check_lock: false,
2428 sources: false,
2429 paths: false,
2430 format: OutputFormat::Text,
2431 verbose: false,
2432 quiet: false,
2433 strict: false, };
2435
2436 let result = cmd.execute_from_path(manifest_path).await;
2437 assert!(result.is_ok());
2438 }
2440
2441 #[tokio::test]
2442 async fn test_validate_verbose_mode_with_summary() {
2443 let temp = TempDir::new().unwrap();
2444 let manifest_path = temp.path().join("agpm.toml");
2445
2446 let mut manifest = crate::manifest::Manifest::new();
2448 manifest.add_source("test".to_string(), "https://github.com/test/repo.git".to_string());
2449 manifest.add_dependency(
2450 "test-agent".to_string(),
2451 crate::manifest::ResourceDependency::Simple("test.md".to_string()),
2452 true,
2453 );
2454 manifest.add_dependency(
2455 "test-snippet".to_string(),
2456 crate::manifest::ResourceDependency::Simple("snippet.md".to_string()),
2457 false,
2458 );
2459 manifest.save(&manifest_path).unwrap();
2460
2461 let cmd = ValidateCommand {
2462 file: None,
2463 resolve: false,
2464 check_lock: false,
2465 sources: false,
2466 paths: false,
2467 format: OutputFormat::Text,
2468 verbose: true, quiet: false,
2470 strict: false,
2471 };
2472
2473 let result = cmd.execute_from_path(manifest_path).await;
2474 assert!(result.is_ok());
2475 }
2477
2478 #[tokio::test]
2479 async fn test_validate_all_checks_enabled() {
2480 let temp = TempDir::new().unwrap();
2481 let manifest_path = temp.path().join("agpm.toml");
2482 let lockfile_path = temp.path().join("agpm.lock");
2483
2484 let mut manifest = Manifest::new();
2486 manifest.agents.insert(
2487 "test-agent".to_string(),
2488 ResourceDependency::Simple("local-agent.md".to_string()),
2489 );
2490 manifest.save(&manifest_path).unwrap();
2491
2492 let lockfile = LockFile::new();
2494 lockfile.save(&lockfile_path).unwrap();
2495
2496 let cmd = ValidateCommand {
2497 file: None,
2498 resolve: true,
2499 check_lock: true,
2500 sources: true,
2501 paths: true,
2502 format: OutputFormat::Text,
2503 verbose: true,
2504 quiet: false,
2505 strict: true,
2506 };
2507
2508 let result = cmd.execute_from_path(manifest_path).await;
2509 assert!(result.is_err() || result.is_ok());
2511 }
2512
2513 #[tokio::test]
2514 async fn test_validate_with_specific_file_path() {
2515 let temp = TempDir::new().unwrap();
2516 let custom_path = temp.path().join("custom-manifest.toml");
2517
2518 let manifest = Manifest::new();
2519 manifest.save(&custom_path).unwrap();
2520
2521 let cmd = ValidateCommand {
2522 file: Some(custom_path.to_string_lossy().to_string()),
2523 resolve: false,
2524 check_lock: false,
2525 sources: false,
2526 paths: false,
2527 format: OutputFormat::Text,
2528 verbose: false,
2529 quiet: false,
2530 strict: false,
2531 };
2532
2533 let result = cmd.execute().await;
2534 assert!(result.is_ok());
2535 }
2536
2537 #[tokio::test]
2538 async fn test_validate_sources_check_with_invalid_url() {
2539 let temp = TempDir::new().unwrap();
2540 let manifest_path = temp.path().join("agpm.toml");
2541
2542 let mut manifest = Manifest::new();
2543 manifest.sources.insert("invalid".to_string(), "not-a-valid-url".to_string());
2544 manifest.save(&manifest_path).unwrap();
2545
2546 let cmd = ValidateCommand {
2547 file: None,
2548 resolve: false,
2549 check_lock: false,
2550 sources: true,
2551 paths: false,
2552 format: OutputFormat::Text,
2553 verbose: false,
2554 quiet: false,
2555 strict: false,
2556 };
2557
2558 let result = cmd.execute_from_path(manifest_path).await;
2559 assert!(result.is_err()); }
2561
2562 #[tokio::test]
2563 async fn test_validation_results_with_errors_and_warnings() {
2564 let mut results = ValidationResults::default();
2565
2566 results.errors.push("Error 1".to_string());
2568 results.errors.push("Error 2".to_string());
2569
2570 results.warnings.push("Warning 1".to_string());
2572 results.warnings.push("Warning 2".to_string());
2573
2574 assert!(!results.errors.is_empty());
2575 assert_eq!(results.errors.len(), 2);
2576 assert_eq!(results.warnings.len(), 2);
2577 }
2578
2579 #[tokio::test]
2580 async fn test_output_format_equality() {
2581 assert_eq!(OutputFormat::Text, OutputFormat::Text);
2583 assert_eq!(OutputFormat::Json, OutputFormat::Json);
2584 assert_ne!(OutputFormat::Text, OutputFormat::Json);
2585 }
2586
2587 #[tokio::test]
2588 async fn test_validate_command_defaults() {
2589 let cmd = ValidateCommand {
2590 file: None,
2591 resolve: false,
2592 check_lock: false,
2593 sources: false,
2594 paths: false,
2595 format: OutputFormat::Text,
2596 verbose: false,
2597 quiet: false,
2598 strict: false,
2599 };
2600 assert_eq!(cmd.file, None);
2601 assert!(!cmd.resolve);
2602 assert!(!cmd.check_lock);
2603 assert!(!cmd.sources);
2604 assert!(!cmd.paths);
2605 assert_eq!(cmd.format, OutputFormat::Text);
2606 assert!(!cmd.verbose);
2607 assert!(!cmd.quiet);
2608 assert!(!cmd.strict);
2609 }
2610
2611 #[tokio::test]
2612 async fn test_json_output_format() {
2613 let temp = TempDir::new().unwrap();
2614 let manifest_path = temp.path().join("agpm.toml");
2615
2616 let manifest = Manifest::new();
2617 manifest.save(&manifest_path).unwrap();
2618
2619 let cmd = ValidateCommand {
2620 file: None,
2621 resolve: false,
2622 check_lock: false,
2623 sources: false,
2624 paths: false,
2625 format: OutputFormat::Json,
2626 verbose: false,
2627 quiet: false,
2628 strict: false,
2629 };
2630
2631 let result = cmd.execute_from_path(manifest_path).await;
2632 assert!(result.is_ok());
2633 }
2634
2635 #[tokio::test]
2636 async fn test_validation_with_verbose_mode() {
2637 let temp = TempDir::new().unwrap();
2638 let manifest_path = temp.path().join("agpm.toml");
2639
2640 let manifest = Manifest::new();
2641 manifest.save(&manifest_path).unwrap();
2642
2643 let cmd = ValidateCommand {
2644 file: None,
2645 resolve: false,
2646 check_lock: false,
2647 sources: false,
2648 paths: false,
2649 format: OutputFormat::Text,
2650 verbose: true,
2651 quiet: false,
2652 strict: false,
2653 };
2654
2655 let result = cmd.execute_from_path(manifest_path).await;
2656 assert!(result.is_ok());
2657 }
2658
2659 #[tokio::test]
2660 async fn test_validation_with_quiet_mode() {
2661 let temp = TempDir::new().unwrap();
2662 let manifest_path = temp.path().join("agpm.toml");
2663
2664 let manifest = Manifest::new();
2665 manifest.save(&manifest_path).unwrap();
2666
2667 let cmd = ValidateCommand {
2668 file: None,
2669 resolve: false,
2670 check_lock: false,
2671 sources: false,
2672 paths: false,
2673 format: OutputFormat::Text,
2674 verbose: false,
2675 quiet: true,
2676 strict: false,
2677 };
2678
2679 let result = cmd.execute_from_path(manifest_path).await;
2680 assert!(result.is_ok());
2681 }
2682
2683 #[tokio::test]
2684 async fn test_validation_with_strict_mode_and_warnings() {
2685 let temp = TempDir::new().unwrap();
2686 let manifest_path = temp.path().join("agpm.toml");
2687
2688 let manifest = Manifest::new();
2690 manifest.save(&manifest_path).unwrap();
2691
2692 let cmd = ValidateCommand {
2693 file: None,
2694 resolve: false,
2695 check_lock: false,
2696 sources: false,
2697 paths: false,
2698 format: OutputFormat::Text,
2699 verbose: false,
2700 quiet: false,
2701 strict: true, };
2703
2704 let result = cmd.execute_from_path(manifest_path).await;
2705 assert!(result.is_err()); }
2707
2708 #[tokio::test]
2709 async fn test_validation_with_local_paths_check() {
2710 let temp = TempDir::new().unwrap();
2711 let manifest_path = temp.path().join("agpm.toml");
2712
2713 let mut manifest = Manifest::new();
2714 manifest.agents.insert(
2715 "local-agent".to_string(),
2716 ResourceDependency::Simple("./missing-file.md".to_string()),
2717 );
2718 manifest.save(&manifest_path).unwrap();
2719
2720 let cmd = ValidateCommand {
2721 file: None,
2722 resolve: false,
2723 check_lock: false,
2724 sources: false,
2725 paths: true, format: OutputFormat::Text,
2727 verbose: false,
2728 quiet: false,
2729 strict: false,
2730 };
2731
2732 let result = cmd.execute_from_path(manifest_path).await;
2733 assert!(result.is_err()); }
2735
2736 #[tokio::test]
2737 async fn test_validation_with_existing_local_paths() {
2738 let temp = TempDir::new().unwrap();
2739 let manifest_path = temp.path().join("agpm.toml");
2740 let local_file = temp.path().join("agent.md");
2741
2742 std::fs::write(&local_file, "# Local Agent").unwrap();
2744
2745 let mut manifest = Manifest::new();
2746 manifest.agents.insert(
2747 "local-agent".to_string(),
2748 ResourceDependency::Simple("./agent.md".to_string()),
2749 );
2750 manifest.save(&manifest_path).unwrap();
2751
2752 let cmd = ValidateCommand {
2753 file: None,
2754 resolve: false,
2755 check_lock: false,
2756 sources: false,
2757 paths: true,
2758 format: OutputFormat::Text,
2759 verbose: false,
2760 quiet: false,
2761 strict: false,
2762 };
2763
2764 let result = cmd.execute_from_path(manifest_path).await;
2765 assert!(result.is_ok());
2766 }
2767
2768 #[tokio::test]
2769 async fn test_validation_with_lockfile_consistency_check_no_lockfile() {
2770 let temp = TempDir::new().unwrap();
2771 let manifest_path = temp.path().join("agpm.toml");
2772
2773 let mut manifest = Manifest::new();
2774 manifest
2775 .agents
2776 .insert("test-agent".to_string(), ResourceDependency::Simple("agent.md".to_string()));
2777 manifest.save(&manifest_path).unwrap();
2778
2779 let cmd = ValidateCommand {
2780 file: None,
2781 resolve: false,
2782 check_lock: true, sources: false,
2784 paths: false,
2785 format: OutputFormat::Text,
2786 verbose: false,
2787 quiet: false,
2788 strict: false,
2789 };
2790
2791 let result = cmd.execute_from_path(manifest_path).await;
2792 assert!(result.is_ok()); }
2794
2795 #[tokio::test]
2796 async fn test_validation_with_inconsistent_lockfile() {
2797 let temp = TempDir::new().unwrap();
2798 let manifest_path = temp.path().join("agpm.toml");
2799 let lockfile_path = temp.path().join("agpm.lock");
2800
2801 let mut manifest = Manifest::new();
2803 manifest.agents.insert(
2804 "manifest-agent".to_string(),
2805 ResourceDependency::Simple("agent.md".to_string()),
2806 );
2807 manifest.save(&manifest_path).unwrap();
2808
2809 let mut lockfile = LockFile::new();
2811 lockfile.agents.push(crate::lockfile::LockedResource {
2812 name: "lockfile-agent".to_string(),
2813 source: None,
2814 url: None,
2815 path: "agent.md".to_string(),
2816 version: None,
2817 resolved_commit: None,
2818 checksum: "sha256:dummy".to_string(),
2819 installed_at: "agents/lockfile-agent.md".to_string(),
2820 dependencies: vec![],
2821 resource_type: crate::core::ResourceType::Agent,
2822
2823 tool: "claude-code".to_string(),
2824 });
2825 lockfile.save(&lockfile_path).unwrap();
2826
2827 let cmd = ValidateCommand {
2828 file: None,
2829 resolve: false,
2830 check_lock: true,
2831 sources: false,
2832 paths: false,
2833 format: OutputFormat::Text,
2834 verbose: false,
2835 quiet: false,
2836 strict: false,
2837 };
2838
2839 let result = cmd.execute_from_path(manifest_path).await;
2840 assert!(result.is_err()); }
2842
2843 #[tokio::test]
2844 async fn test_validation_with_invalid_lockfile_syntax() {
2845 let temp = TempDir::new().unwrap();
2846 let manifest_path = temp.path().join("agpm.toml");
2847 let lockfile_path = temp.path().join("agpm.lock");
2848
2849 let manifest = Manifest::new();
2850 manifest.save(&manifest_path).unwrap();
2851
2852 std::fs::write(&lockfile_path, "invalid toml syntax [[[").unwrap();
2854
2855 let cmd = ValidateCommand {
2856 file: None,
2857 resolve: false,
2858 check_lock: true,
2859 sources: false,
2860 paths: false,
2861 format: OutputFormat::Text,
2862 verbose: false,
2863 quiet: false,
2864 strict: false,
2865 };
2866
2867 let result = cmd.execute_from_path(manifest_path).await;
2868 assert!(result.is_err()); }
2870
2871 #[tokio::test]
2872 async fn test_validation_with_outdated_version_warning() {
2873 let temp = TempDir::new().unwrap();
2874 let manifest_path = temp.path().join("agpm.toml");
2875
2876 let mut manifest = Manifest::new();
2877 manifest.sources.insert("test".to_string(), "https://github.com/test/repo.git".to_string());
2879 manifest.agents.insert(
2880 "old-agent".to_string(),
2881 ResourceDependency::Detailed(Box::new(crate::manifest::DetailedDependency {
2882 source: Some("test".to_string()),
2883 path: "agent.md".to_string(),
2884 version: Some("v0.1.0".to_string()),
2885 branch: None,
2886 rev: None,
2887 command: None,
2888 args: None,
2889 target: None,
2890 filename: None,
2891 dependencies: None,
2892 tool: "claude-code".to_string(),
2893 })),
2894 );
2895 manifest.save(&manifest_path).unwrap();
2896
2897 let cmd = ValidateCommand {
2898 file: None,
2899 resolve: false,
2900 check_lock: false,
2901 sources: false,
2902 paths: false,
2903 format: OutputFormat::Text,
2904 verbose: false,
2905 quiet: false,
2906 strict: false,
2907 };
2908
2909 let result = cmd.execute_from_path(manifest_path).await;
2910 assert!(result.is_ok()); }
2912
2913 #[tokio::test]
2914 async fn test_validation_json_output_with_errors() {
2915 let temp = TempDir::new().unwrap();
2916 let manifest_path = temp.path().join("agpm.toml");
2917
2918 std::fs::write(&manifest_path, "invalid toml [[[ syntax").unwrap();
2920
2921 let cmd = ValidateCommand {
2922 file: None,
2923 resolve: false,
2924 check_lock: false,
2925 sources: false,
2926 paths: false,
2927 format: OutputFormat::Json,
2928 verbose: false,
2929 quiet: false,
2930 strict: false,
2931 };
2932
2933 let result = cmd.execute_from_path(manifest_path).await;
2934 assert!(result.is_err());
2935 }
2936
2937 #[tokio::test]
2938 async fn test_validation_with_manifest_not_found_json() {
2939 let temp = TempDir::new().unwrap();
2940 let manifest_path = temp.path().join("nonexistent.toml");
2941
2942 let cmd = ValidateCommand {
2943 file: None,
2944 resolve: false,
2945 check_lock: false,
2946 sources: false,
2947 paths: false,
2948 format: OutputFormat::Json,
2949 verbose: false,
2950 quiet: false,
2951 strict: false,
2952 };
2953
2954 let result = cmd.execute_from_path(manifest_path).await;
2955 assert!(result.is_err());
2956 }
2957
2958 #[tokio::test]
2959 async fn test_validation_with_manifest_not_found_text() {
2960 let temp = TempDir::new().unwrap();
2961 let manifest_path = temp.path().join("nonexistent.toml");
2962
2963 let cmd = ValidateCommand {
2964 file: None,
2965 resolve: false,
2966 check_lock: false,
2967 sources: false,
2968 paths: false,
2969 format: OutputFormat::Text,
2970 verbose: false,
2971 quiet: false,
2972 strict: false,
2973 };
2974
2975 let result = cmd.execute_from_path(manifest_path).await;
2976 assert!(result.is_err());
2977 }
2978
2979 #[tokio::test]
2980 async fn test_validation_with_missing_lockfile_dependencies() {
2981 let temp = TempDir::new().unwrap();
2982 let manifest_path = temp.path().join("agpm.toml");
2983 let lockfile_path = temp.path().join("agpm.lock");
2984
2985 let mut manifest = Manifest::new();
2987 manifest
2988 .agents
2989 .insert("agent1".to_string(), ResourceDependency::Simple("agent1.md".to_string()));
2990 manifest
2991 .agents
2992 .insert("agent2".to_string(), ResourceDependency::Simple("agent2.md".to_string()));
2993 manifest
2994 .snippets
2995 .insert("snippet1".to_string(), ResourceDependency::Simple("snippet1.md".to_string()));
2996 manifest.save(&manifest_path).unwrap();
2997
2998 let mut lockfile = LockFile::new();
3000 lockfile.agents.push(crate::lockfile::LockedResource {
3001 name: "agent1".to_string(),
3002 source: None,
3003 url: None,
3004 path: "agent1.md".to_string(),
3005 version: None,
3006 resolved_commit: None,
3007 checksum: "sha256:dummy".to_string(),
3008 installed_at: "agents/agent1.md".to_string(),
3009 dependencies: vec![],
3010 resource_type: crate::core::ResourceType::Agent,
3011
3012 tool: "claude-code".to_string(),
3013 });
3014 lockfile.save(&lockfile_path).unwrap();
3015
3016 let cmd = ValidateCommand {
3017 file: None,
3018 resolve: false,
3019 check_lock: true,
3020 sources: false,
3021 paths: false,
3022 format: OutputFormat::Text,
3023 verbose: false,
3024 quiet: false,
3025 strict: false,
3026 };
3027
3028 let result = cmd.execute_from_path(manifest_path).await;
3029 assert!(result.is_ok()); }
3031
3032 #[tokio::test]
3033 async fn test_execute_without_manifest_file() {
3034 let temp = TempDir::new().unwrap();
3036 let non_existent_manifest = temp.path().join("non_existent.toml");
3037
3038 let cmd = ValidateCommand {
3039 file: Some(non_existent_manifest.to_string_lossy().to_string()),
3040 resolve: false,
3041 check_lock: false,
3042 sources: false,
3043 paths: false,
3044 format: OutputFormat::Text,
3045 verbose: false,
3046 quiet: false,
3047 strict: false,
3048 };
3049
3050 let result = cmd.execute().await;
3051 assert!(result.is_err()); }
3053
3054 #[tokio::test]
3055 async fn test_execute_with_specified_file() {
3056 let temp = TempDir::new().unwrap();
3057 let custom_path = temp.path().join("custom.toml");
3058
3059 let manifest = Manifest::new();
3060 manifest.save(&custom_path).unwrap();
3061
3062 let cmd = ValidateCommand {
3063 file: Some(custom_path.to_string_lossy().to_string()),
3064 resolve: false,
3065 check_lock: false,
3066 sources: false,
3067 paths: false,
3068 format: OutputFormat::Text,
3069 verbose: false,
3070 quiet: false,
3071 strict: false,
3072 };
3073
3074 let result = cmd.execute().await;
3075 assert!(result.is_ok());
3076 }
3077
3078 #[tokio::test]
3079 async fn test_execute_with_nonexistent_specified_file() {
3080 let temp = TempDir::new().unwrap();
3081 let nonexistent = temp.path().join("nonexistent.toml");
3082
3083 let cmd = ValidateCommand {
3084 file: Some(nonexistent.to_string_lossy().to_string()),
3085 resolve: false,
3086 check_lock: false,
3087 sources: false,
3088 paths: false,
3089 format: OutputFormat::Text,
3090 verbose: false,
3091 quiet: false,
3092 strict: false,
3093 };
3094
3095 let result = cmd.execute().await;
3096 assert!(result.is_err());
3097 }
3098
3099 #[tokio::test]
3100 async fn test_validation_with_verbose_and_text_format() {
3101 let temp = TempDir::new().unwrap();
3102 let manifest_path = temp.path().join("agpm.toml");
3103
3104 let mut manifest = Manifest::new();
3105 manifest.sources.insert("test".to_string(), "https://github.com/test/repo.git".to_string());
3106 manifest
3107 .agents
3108 .insert("agent1".to_string(), ResourceDependency::Simple("agent.md".to_string()));
3109 manifest
3110 .snippets
3111 .insert("snippet1".to_string(), ResourceDependency::Simple("snippet.md".to_string()));
3112 manifest.save(&manifest_path).unwrap();
3113
3114 let cmd = ValidateCommand {
3115 file: None,
3116 resolve: false,
3117 check_lock: false,
3118 sources: false,
3119 paths: false,
3120 format: OutputFormat::Text,
3121 verbose: true,
3122 quiet: false,
3123 strict: false,
3124 };
3125
3126 let result = cmd.execute_from_path(manifest_path).await;
3127 assert!(result.is_ok());
3128 }
3129}