use agpm_cli::utils::normalize_path_for_storage;
use tokio::fs;
use crate::common::{ManifestBuilder, TestProject};
use crate::fixtures::{LockfileFixture, ManifestFixture};
#[tokio::test]
async fn test_network_timeout() {
let project = TestProject::new().await.unwrap();
let manifest = ManifestBuilder::new()
.add_source("official", "file:///non/existent/path/to/repo")
.add_standard_agent("my-agent", "official", "agents/my-agent.md")
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(!output.success, "Expected command to fail but it succeeded");
assert!(
output.stderr.contains("Failed to clone")
|| output.stderr.contains("Git operation failed")
|| output.stderr.contains("Local repository path does not exist")
|| output.stderr.contains("does not exist"),
"Expected error about clone failure, got: {}",
output.stderr
);
}
#[tokio::test]
async fn test_disk_space_error() {
let project = TestProject::new().await.unwrap();
let source_repo = project.create_source_repo("official").await.unwrap();
source_repo
.add_resource("agents", "large-agent", "# Large Agent\n\nA test agent")
.await
.unwrap();
source_repo.commit_all("Add large agent").unwrap();
source_repo.tag_version("v1.0.0").unwrap();
let source_url = source_repo.bare_file_url(project.sources_path()).unwrap();
let manifest = ManifestBuilder::new()
.add_source("official", &source_url)
.add_standard_agent("large-agent", "official", "agents/large-agent.md")
.build();
project.write_manifest(&manifest).await.unwrap();
let invalid_cache_file = project.project_path().join("invalid_cache_file.txt");
fs::write(&invalid_cache_file, "This is a file, not a directory").await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
output.assert_success(); }
#[tokio::test]
async fn test_corrupted_git_repo() {
let project = TestProject::new().await.unwrap();
let fake_repo_dir = project.sources_path().join("official");
fs::create_dir_all(&fake_repo_dir).await.unwrap();
fs::create_dir_all(fake_repo_dir.join(".git")).await.unwrap();
fs::write(fake_repo_dir.join(".git/config"), "corrupted config").await.unwrap();
let fake_repo_url = format!("file://{}", normalize_path_for_storage(&fake_repo_dir));
let manifest = ManifestBuilder::new()
.add_source("official", &fake_repo_url)
.add_standard_agent("test-agent", "official", "agents/test.md")
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(!output.success, "Expected command to fail but it succeeded");
assert!(
output.stderr.contains("Corrupted repository")
|| output.stderr.contains("Invalid git repository")
|| output.stderr.contains("Git error")
|| output.stderr.contains("Failed to clone"),
"Expected git error, got: {}",
output.stderr
);
}
#[tokio::test]
async fn test_authentication_failure() {
let project = TestProject::new().await.unwrap();
let manifest = ManifestBuilder::new()
.add_source("private", "file:///restricted/private/repo")
.add_standard_agent("secret-agent", "private", "agents/secret.md")
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(!output.success, "Expected command to fail but it succeeded");
assert!(
output.stderr.contains("Failed to clone")
|| output.stderr.contains("Repository not found")
|| output.stderr.contains("does not exist"),
"Expected authentication/access error, got: {}",
output.stderr
);
}
#[tokio::test]
async fn test_malformed_markdown() {
let project = TestProject::new().await.unwrap();
let manifest_content = r#"
[agents]
broken-agent = { path = "./agents/broken.md" }
"#;
project.write_manifest(manifest_content).await.unwrap();
let malformed_content = r"---
type: agent
name: broken-agent
invalid yaml: [ unclosed
---
# Broken Agent
";
project.create_local_resource("agents/broken.md", malformed_content).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(
output.success,
"Expected command to succeed with warning but it failed: {}",
output.stderr
);
assert!(
output.stderr.contains("Warning: Unable to parse YAML frontmatter")
|| output.stderr.contains("Warning: Unable to parse TOML frontmatter"),
"Expected warning about invalid frontmatter, got: {}",
output.stderr
);
let installed_path = project.project_path().join(".claude/agents/broken.md");
assert!(
installed_path.exists(),
"File should be installed despite invalid frontmatter at: {:?}",
installed_path
);
}
#[cfg(unix)]
#[tokio::test]
async fn test_permission_conflicts() {
use std::os::unix::fs::PermissionsExt;
let project = TestProject::new().await.unwrap();
let source_repo = project.create_source_repo("official").await.unwrap();
source_repo.add_resource("agents", "my-agent", "# My Agent\n\nA test agent").await.unwrap();
source_repo.commit_all("Add my agent").unwrap();
source_repo.tag_version("v1.0.0").unwrap();
let source_url = source_repo.bare_file_url(project.sources_path()).unwrap();
let manifest = ManifestBuilder::new()
.add_source("official", &source_url)
.add_standard_agent("my-agent", "official", "agents/my-agent.md")
.build();
project.write_manifest(&manifest).await.unwrap();
let claude_dir = project.project_path().join(".claude");
fs::create_dir_all(&claude_dir).await.unwrap();
let agents_dir = claude_dir.join("agents");
fs::create_dir_all(&agents_dir).await.unwrap();
let mut perms = fs::metadata(&agents_dir).await.unwrap().permissions();
perms.set_mode(0o444); fs::set_permissions(&agents_dir, perms).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(!output.success, "Expected command to fail but it succeeded");
assert!(
output.stderr.contains("Failed to install") && output.stderr.contains("resource"),
"Expected permission error, got: {}",
output.stderr
);
if agents_dir.exists() {
let mut perms = fs::metadata(&agents_dir).await.unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&agents_dir, perms).await.unwrap();
}
}
#[tokio::test]
async fn test_invalid_version_specs() {
let project = TestProject::new().await.unwrap();
let source_repo = project.create_source_repo("official").await.unwrap();
source_repo.add_resource("agents", "invalid", "# Test Agent").await.unwrap();
source_repo.add_resource("agents", "malformed", "# Test Agent").await.unwrap();
source_repo.commit_all("Add test agents").unwrap();
source_repo.tag_version("v0.1.0").unwrap();
source_repo.tag_version("v1.0.0").unwrap();
let source_url = source_repo.bare_file_url(project.sources_path()).unwrap();
let manifest = ManifestBuilder::new()
.add_source("official", &source_url)
.add_agent("invalid-version", |d| {
d.source("official").path("agents/invalid.md").version("not-a-version")
})
.add_agent("malformed-constraint", |d| {
d.source("official").path("agents/malformed.md").version(">=1.0.0 <invalid")
})
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(!output.success, "Expected command to fail but it succeeded");
assert!(
output.stderr.contains("Invalid version")
|| output.stderr.contains("Version constraint error")
|| output.stderr.contains("No matching version")
|| output.stderr.contains("Failed to resolve")
|| output.stderr.contains("Failed to checkout reference")
|| output.stderr.contains("Git operation failed"),
"Expected version error, got: {}",
output.stderr
);
}
#[tokio::test]
async fn test_system_limits() {
let project = TestProject::new().await.unwrap();
let mut builder = ManifestBuilder::new()
.add_source("official", "https://github.com/example-org/agpm-official.git");
for i in 0..1000 {
builder = builder.add_agent(&format!("agent_{i}"), |d| {
d.source("official").path(&format!("agents/agent_{i}.md")).version("v1.0.0")
});
}
let manifest = builder.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["validate"]).unwrap();
output.assert_success(); assert!(
output.stdout.contains("✓")
|| output.stdout.contains("✅")
|| output.stdout.contains("Validation complete"),
"Expected validation success indicator, got: {}",
output.stdout
);
}
#[tokio::test]
async fn test_interrupted_operation() {
let project = TestProject::new().await.unwrap();
let manifest_content = r#"
[agents]
local-agent = { path = "./agents/local.md" }
[snippets]
local-snippet = { path = "./snippets/local.md" }
"#;
project.write_manifest(manifest_content).await.unwrap();
project.create_local_resource("agents/local.md", "# Local Agent").await.unwrap();
project.create_local_resource("snippets/local.md", "# Local Snippet").await.unwrap();
let partial_lockfile = r#"
# Auto-generated lockfile - DO NOT EDIT
version = 1
[[sources]]
name = "official"
url = "https://github.com/example-org/agpm-official.git"
"#;
fs::write(project.project_path().join("agpm.lock"), partial_lockfile).await.unwrap();
let output = project.run_agpm(&["validate", "--check-lock"]).unwrap();
assert!(!output.success, "Expected command to fail but it succeeded");
assert!(
output.stderr.contains("Incomplete lockfile")
|| output.stderr.contains("Corrupted lockfile")
|| output.stderr.contains("Missing required fields")
|| output.stderr.contains("Invalid lockfile syntax"),
"Expected lockfile error, got: {}",
output.stderr
);
}
#[tokio::test]
async fn test_invalid_urls() {
let project = TestProject::new().await.unwrap();
let manifest_content = r#"
[sources]
invalid_url = "not-a-url"
wrong_protocol = "ftp://example.com/repo.git"
malformed_path = "/path/without/git/repo"
[agents]
test-agent = { source = "invalid_url", path = "agents/test.md", version = "v1.0.0" }
"#;
project.write_manifest(manifest_content).await.unwrap();
let output = project.run_agpm(&["validate", "--resolve"]).unwrap();
assert!(!output.success, "Expected command to fail but it succeeded");
assert!(
output.stderr.contains("Invalid URL")
|| output.stderr.contains("Malformed URL")
|| output.stderr.contains("Failed to clone")
|| output.stderr.contains("Manifest validation failed"),
"Expected URL validation error, got: {}",
output.stderr
);
}
#[tokio::test]
async fn test_large_file_handling() {
let project = TestProject::new().await.unwrap();
let large_content =
format!("---\ntype: agent\nname: my-agent\n---\n\n{}", "# Large Agent\n\n".repeat(50000));
let source_repo = project.create_source_repo("official").await.unwrap();
source_repo.add_resource("agents", "my-agent", &large_content).await.unwrap();
source_repo.commit_all("Add large agent").unwrap();
source_repo.tag_version("v1.0.0").unwrap();
let source_url = source_repo.bare_file_url(project.sources_path()).unwrap();
let manifest = ManifestBuilder::new()
.add_source("official", &source_url)
.add_standard_agent("my-agent", "official", "agents/my-agent.md")
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
output.assert_success().assert_stdout_contains("Installed");
}
#[tokio::test]
async fn test_filesystem_corruption() {
let project = TestProject::new().await.unwrap();
let manifest_content = r#"
[agents]
local-agent = { path = "./agents/local.md" }
[snippets]
local-snippet = { path = "./snippets/local.md" }
"#;
project.write_manifest(manifest_content).await.unwrap();
project.create_local_resource("agents/local.md", "# Local Agent").await.unwrap();
project.create_local_resource("snippets/local.md", "# Local Snippet").await.unwrap();
let corrupted_lockfile = "version = 1\n\0\0\0corrupted\0data\n";
fs::write(project.project_path().join("agpm.lock"), corrupted_lockfile).await.unwrap();
let output = project.run_agpm(&["validate", "--check-lock"]).unwrap();
assert!(!output.success, "Expected command to fail but it succeeded");
assert!(
output.stderr.contains("Corrupted")
|| output.stderr.contains("Invalid character")
|| output.stderr.contains("TOML")
|| output.stderr.contains("Invalid lockfile syntax"),
"Expected filesystem corruption error, got: {}",
output.stderr
);
}
#[tokio::test]
async fn test_missing_lockfile_dependencies() {
let project = TestProject::new().await.unwrap();
let manifest_content = r#"
[agents]
local-agent = { path = "./agents/local.md" }
helper = { path = "./agents/helper.md" }
[snippets]
utils = { path = "./snippets/utils.md" }
"#;
project.write_manifest(manifest_content).await.unwrap();
project.create_local_resource("agents/local.md", "# Local Agent").await.unwrap();
project.create_local_resource("agents/helper.md", "# Helper").await.unwrap();
project.create_local_resource("snippets/utils.md", "# Utils").await.unwrap();
let incomplete_lockfile = r#"
# Auto-generated lockfile - DO NOT EDIT
version = 1
[[sources]]
name = "official"
url = "https://github.com/example-org/agpm-official.git"
commit = "abc123456789abcdef123456789abcdef12345678"
[[agents]]
name = "local-agent"
path = "./agents/local.md"
checksum = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
installed_at = "agents/local-agent.md"
# Missing 'helper' and 'utils' from manifest
"#;
fs::write(project.project_path().join("agpm.lock"), incomplete_lockfile).await.unwrap();
let output = project.run_agpm(&["validate", "--check-lock"]).unwrap();
assert!(!output.success, "Expected command to fail but it succeeded");
assert!(
output.stderr.contains("Missing dependencies")
|| (output.stderr.contains("lockfile") && output.stderr.contains("mismatch"))
|| output.stderr.contains("helper")
|| output.stderr.contains("utils")
|| output.stderr.contains("Invalid lockfile syntax"),
"Expected missing dependencies error, got: {}",
output.stderr
);
}
#[tokio::test]
async fn test_git_command_missing() {
let project = TestProject::new().await.unwrap();
let manifest = ManifestBuilder::new()
.add_source("official", "https://github.com/example-org/agpm-official.git")
.add_standard_agent("test-agent", "official", "agents/test.md")
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm_with_env(&["install"], &[("PATH", "/nonexistent")]).unwrap();
assert!(!output.success, "Command should fail when git is not available");
assert!(
output.stderr.contains("git")
|| output.stderr.contains("Git")
|| output.stderr.contains("not found")
|| output.stderr.contains("No such file")
|| output.stderr.contains("file access")
|| output.stderr.contains("File system error"),
"Expected error related to missing git or file access, got: {}",
output.stderr
);
}
#[tokio::test]
async fn test_invalid_config_files() {
let project = TestProject::new().await.unwrap();
let invalid_toml = r#"
this is not valid toml at all
[unclosed section
key = "value without closing quote
"#;
project.write_manifest(invalid_toml).await.unwrap();
let output = project.run_agpm(&["validate"]).unwrap();
assert!(!output.success, "Expected command to fail but it succeeded");
assert!(
output.stderr.contains("TOML parsing")
|| output.stderr.contains("Syntax error")
|| output.stderr.contains("Parse error")
|| output.stderr.contains("Invalid"),
"Expected TOML parsing error, got: {}",
output.stderr
);
}
#[tokio::test]
async fn test_partial_installation_recovery() {
let project = TestProject::new().await.unwrap();
let source_repo = project.create_source_repo("official").await.unwrap();
source_repo.add_resource("agents", "my-agent", "# My Agent\n\nA test agent").await.unwrap();
source_repo.add_resource("snippets", "utils", "# Utils\n\nA test snippet").await.unwrap();
source_repo.commit_all("Add test resources").unwrap();
source_repo.tag_version("v1.0.0").unwrap();
let source_url = source_repo.bare_file_url(project.sources_path()).unwrap();
let manifest = ManifestBuilder::new()
.add_source("official", &source_url)
.add_standard_agent("my-agent", "official", "agents/my-agent.md")
.add_standard_snippet("utils", "official", "snippets/utils.md")
.build();
project.write_manifest(&manifest).await.unwrap();
project.create_local_resource("agents/my-agent.md", "# Partial agent").await.unwrap();
let lockfile_content = LockfileFixture::basic().content;
fs::write(project.project_path().join("agpm.lock"), lockfile_content).await.unwrap();
let output = project.run_agpm(&["validate", "--check-lock"]).unwrap();
assert!(!output.success, "Expected command to fail but it succeeded"); assert!(
output.stderr.contains("Lockfile inconsistent") || output.stderr.contains("helper"), "Expected lockfile inconsistency error, got: {}",
output.stderr
);
}
#[tokio::test]
async fn test_concurrent_lockfile_modification() {
let project = TestProject::new().await.unwrap();
let source_repo = project.create_source_repo("official").await.unwrap();
source_repo.add_resource("agents", "my-agent", "# My Agent\n\nA test agent").await.unwrap();
source_repo.commit_all("Add my agent").unwrap();
source_repo.tag_version("v1.0.0").unwrap();
let source_url = source_repo.bare_file_url(project.sources_path()).unwrap();
let manifest = ManifestBuilder::new()
.add_source("official", &source_url)
.add_standard_agent("my-agent", "official", "agents/my-agent.md")
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
output.assert_success().assert_stdout_contains("Installed"); }
#[tokio::test]
async fn test_helpful_error_messages() {
let project = TestProject::new().await.unwrap();
let manifest_content = ManifestFixture::missing_fields().content;
project.write_manifest(&manifest_content).await.unwrap();
let output = project.run_agpm(&["validate"]).unwrap();
assert!(!output.success, "Expected command to fail but it succeeded");
assert!(output.stderr.contains("error:"), "Expected error indicator in stderr"); assert!(
output.stderr.contains("Missing required field")
|| output.stderr.contains("Invalid manifest")
|| output.stderr.contains("Manifest validation failed"),
"Expected clear error description, got: {}",
output.stderr
); assert!(
output.stderr.contains("path") || output.stderr.contains("Suggestion:"),
"Expected specific field or suggestion, got: {}",
output.stderr
); }