use tokio::fs;
mod common;
mod fixtures;
use common::TestProject;
use fixtures::{LockfileFixture, ManifestFixture};
#[tokio::test]
async fn test_network_timeout() {
let project = TestProject::new().await.unwrap();
let manifest_content = r#"
[sources]
official = "file:///non/existent/path/to/repo"
[agents]
my-agent = { source = "official", path = "agents/my-agent.md", version = "v1.0.0" }
"#;
project.write_manifest(manifest_content).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_content = format!(
r#"
[sources]
official = "{}"
[agents]
large-agent = {{ source = "official", path = "agents/large-agent.md", version = "v1.0.0" }}
"#,
source_url
);
project.write_manifest(&manifest_content).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 manifest_content = format!(
r#"
[sources]
official = "file://{}"
[agents]
test-agent = {{ source = "official", path = "agents/test.md", version = "v1.0.0" }}
"#,
fake_repo_dir.display().to_string().replace('\\', "/")
);
project.write_manifest(&manifest_content).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_content = r#"
[sources]
private = "file:///restricted/private/repo"
[agents]
secret-agent = { source = "private", path = "agents/secret.md", version = "v1.0.0" }
"#;
project.write_manifest(manifest_content).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-agent.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_content = format!(
r#"
[sources]
official = "{}"
[agents]
my-agent = {{ source = "official", path = "agents/my-agent.md", version = "v1.0.0" }}
"#,
source_url
);
project.write_manifest(&manifest_content).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_content = format!(
r#"
[sources]
official = "{}"
[agents]
invalid-version = {{ source = "official", path = "agents/invalid.md", version = "not-a-version" }}
malformed-constraint = {{ source = "official", path = "agents/malformed.md", version = ">=1.0.0 <invalid" }}
"#,
source_url
);
project.write_manifest(&manifest_content).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_circular_dependency_detection() {
let project = TestProject::new().await.unwrap();
let manifest_content = r#"
[agents]
agent_a = { path = "./agents/a.md" }
agent_b = { path = "./agents/b.md" }
"#;
project.write_manifest(manifest_content).await.unwrap();
project.create_local_resource("agents/a.md", "# Agent A").await.unwrap();
project.create_local_resource("agents/b.md", "# Agent B").await.unwrap();
let output = project.run_agpm(&["validate"]).unwrap();
output.assert_success().assert_stdout_contains("Valid manifest");
let output = project.run_agpm(&["install"]).unwrap();
output.assert_success();
}
#[tokio::test]
async fn test_system_limits() {
let project = TestProject::new().await.unwrap();
let mut manifest_content = String::from(
r#"
[sources]
official = "https://github.com/example-org/agpm-official.git"
[agents]
"#,
);
for i in 0..1000 {
manifest_content.push_str(&format!(
"agent_{i} = {{ source = \"official\", path = \"agents/agent_{i}.md\", version = \"v1.0.0\" }}\n"
));
}
project.write_manifest(&manifest_content).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_content = format!(
r#"
[sources]
official = "{}"
[agents]
my-agent = {{ source = "official", path = "agents/my-agent.md", version = "v1.0.0" }}
"#,
source_url
);
project.write_manifest(&manifest_content).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_content = r#"
[sources]
official = "https://github.com/example-org/agpm-official.git"
[agents]
test-agent = { source = "official", path = "agents/test.md", version = "v1.0.0" }
"#;
project.write_manifest(manifest_content).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_content = format!(
r#"
[sources]
official = "{}"
[agents]
my-agent = {{ source = "official", path = "agents/my-agent.md", version = "v1.0.0" }}
[snippets]
utils = {{ source = "official", path = "snippets/utils.md", version = "v1.0.0" }}
"#,
source_url
);
project.write_manifest(&manifest_content).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_content = format!(
r#"
[sources]
official = "{}"
[agents]
my-agent = {{ source = "official", path = "agents/my-agent.md", version = "v1.0.0" }}
"#,
source_url
);
project.write_manifest(&manifest_content).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
); }