use tokio::fs;
use crate::common::{DirAssert, FileAssert, ManifestBuilder, TestProject};
use crate::fixtures::ManifestFixture;
#[tokio::test]
async fn test_install_creates_lockfile() {
let project = TestProject::new().await.unwrap();
let (_official_repo, official_url) = project.create_standard_v1_repo("official").await.unwrap();
let community_repo = project.create_source_repo("community").await.unwrap();
community_repo
.add_resource("agents", "helper", "# Helper Agent\n\nA helper agent")
.await
.unwrap();
community_repo.commit_all("Add helper").unwrap();
community_repo.tag_version("v1.0.0").unwrap();
let community_url = community_repo.bare_file_url(project.sources_path()).await.unwrap();
let manifest = ManifestBuilder::new()
.add_sources(&[("official", &official_url), ("community", &community_url)])
.add_standard_agent("my-agent", "official", "agents/test-agent.md")
.add_standard_agent("helper", "community", "agents/helper.md")
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install", "--no-cache"]).unwrap();
output.assert_success();
assert!(
output.stdout.contains("Installing")
|| output.stdout.contains("Cloning")
|| output.stdout.contains("Installed"),
"Expected install progress message, got: {}",
output.stdout
);
let lockfile_path = project.project_path().join("agpm.lock");
FileAssert::exists(&lockfile_path).await;
let lockfile_content = fs::read_to_string(&lockfile_path).await.unwrap();
assert!(lockfile_content.contains("version = 1"));
assert!(lockfile_content.contains("[[sources]]"));
assert!(lockfile_content.contains("[[agents]]"));
assert!(lockfile_content.contains("my-agent"));
assert!(lockfile_content.contains("helper"));
let agents_dir = project.project_path().join(".claude/agents/agpm");
assert!(agents_dir.join("test-agent.md").exists());
assert!(agents_dir.join("helper.md").exists());
}
#[tokio::test]
async fn test_install_with_existing_lockfile() {
let project = TestProject::new().await.unwrap();
let official_repo = project.create_source_repo("official").await.unwrap();
official_repo.add_resource("agents", "my-agent", "# My Agent\n\nA test agent").await.unwrap();
official_repo.commit_all("Add my agent").unwrap();
official_repo.tag_version("v1.0.0").unwrap();
let official_url = official_repo.bare_file_url(project.sources_path()).await.unwrap();
let official_sha = official_repo.git.get_commit_hash().unwrap();
let community_repo = project.create_source_repo("community").await.unwrap();
community_repo
.add_resource("agents", "helper", "# Helper Agent\n\nA helper agent")
.await
.unwrap();
community_repo.commit_all("Add helper agent").unwrap();
community_repo.tag_version("v1.0.0").unwrap();
let community_url = community_repo.bare_file_url(project.sources_path()).await.unwrap();
let community_sha = community_repo.git.get_commit_hash().unwrap();
let manifest = ManifestBuilder::new()
.add_sources(&[("official", &official_url), ("community", &community_url)])
.add_standard_agent("my-agent", "official", "agents/my-agent.md")
.add_standard_agent("helper", "community", "agents/helper.md")
.build();
project.write_manifest(&manifest).await.unwrap();
let lockfile_content = format!(
r#"# Auto-generated lockfile - DO NOT EDIT
version = 1
[[sources]]
name = "official"
url = "{}"
commit = "{}"
fetched_at = "2024-01-01T00:00:00Z"
[[sources]]
name = "community"
url = "{}"
commit = "{}"
fetched_at = "2024-01-01T00:00:00Z"
[[agents]]
name = "my-agent"
source = "official"
url = "{}"
path = "agents/my-agent.md"
version = "v1.0.0"
resolved_commit = "{}"
checksum = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
installed_at = ".claude/agents/agpm/my-agent.md"
artifact_type = "claude-code"
[[agents]]
name = "helper"
source = "community"
url = "{}"
path = "agents/helper.md"
version = "v1.0.0"
resolved_commit = "{}"
checksum = "sha256:38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da"
installed_at = ".claude/agents/agpm/helper.md"
artifact_type = "claude-code"
"#,
official_url,
official_sha,
community_url,
community_sha,
official_url,
official_sha, community_url,
community_sha );
fs::write(project.project_path().join("agpm.lock"), lockfile_content).await.unwrap();
let output = project.run_agpm(&["install", "--frozen"]).unwrap();
output.assert_success();
assert!(
output.stdout.contains("Installing")
|| output.stdout.contains("Cloning")
|| output.stdout.contains("Installed"),
"Expected install progress message, got: {}",
output.stdout
);
let agents_dir = project.project_path().join(".claude").join("agents").join("agpm");
DirAssert::exists(&agents_dir).await;
DirAssert::contains_file(&agents_dir, "my-agent.md").await;
DirAssert::contains_file(&agents_dir, "helper.md").await;
}
#[tokio::test]
async fn test_install_without_manifest() {
let project = TestProject::new().await.unwrap();
let output = project.run_agpm(&["install", "--no-cache"]).unwrap();
assert!(!output.success, "Expected command to fail but it succeeded");
assert!(
output.stderr.contains("No agpm.toml found"),
"Expected manifest not found error, got: {}",
output.stderr
);
}
#[tokio::test]
async fn test_install_invalid_manifest_syntax() {
let project = TestProject::new().await.unwrap();
let manifest_content = ManifestFixture::invalid_syntax().content;
project.write_manifest(&manifest_content).await.unwrap();
let output = project.run_agpm(&["install", "--no-cache"]).unwrap();
assert!(!output.success, "Expected command to fail but it succeeded");
assert!(
output.stderr.contains("Invalid manifest file syntax"),
"Expected syntax error, got: {}",
output.stderr
);
}
#[tokio::test]
async fn test_install_missing_manifest_fields() {
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(&["install", "--no-cache"]).unwrap();
assert!(!output.success, "Expected command to fail but it succeeded");
assert!(
output.stderr.contains("Missing required field"),
"Expected missing field error, got: {}",
output.stderr
);
}
#[tokio::test]
async fn test_install_local_dependencies() {
let project = TestProject::new().await.unwrap();
let parent_dir = project.project_path().parent().unwrap();
let local_agents_dir = parent_dir.join("local-agents");
fs::create_dir_all(&local_agents_dir).await.unwrap();
fs::write(local_agents_dir.join("helper.md"), "# Local Agent Helper\n\nThis is a local agent.")
.await
.unwrap();
project
.create_local_resource(
"snippets/local-utils.md",
"# Local Utils\n\nThis is a local snippet.",
)
.await
.unwrap();
let (_official_repo, official_url) = project.create_standard_v1_repo("official").await.unwrap();
let manifest = ManifestBuilder::new()
.add_source("official", &official_url)
.add_standard_agent("my-agent", "official", "agents/test-agent.md")
.add_agent("local-agent", |d| d.path("../local-agents/helper.md").flatten(false))
.add_local_snippet("local-utils", "./snippets/local-utils.md")
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install", "--no-cache"]).unwrap();
output.assert_success();
assert!(
output.stdout.contains("Installing")
|| output.stdout.contains("Cloning")
|| output.stdout.contains("Installed"),
"Expected install progress message, got: {}",
output.stdout
);
let lockfile_content =
fs::read_to_string(project.project_path().join("agpm.lock")).await.unwrap();
assert!(lockfile_content.contains("my-agent")); assert!(lockfile_content.contains("local-agent")); assert!(lockfile_content.contains("local-utils"));
let agents_dir = project.project_path().join(".claude").join("agents").join("agpm");
assert!(agents_dir.join("test-agent.md").exists());
let local_helper_path =
project.project_path().join(".claude/agents/agpm/local-agents/helper.md");
assert!(local_helper_path.exists(), "Local helper should be at {:?}", local_helper_path);
let local_utils_path = project.project_path().join(".agpm/snippets/local-utils.md");
assert!(local_utils_path.exists(), "Local utils should be at {:?}", local_utils_path);
}
#[tokio::test]
async fn test_install_network_failure() {
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", "--no-cache"]).unwrap();
assert!(!output.success, "Expected command to fail but it succeeded");
assert!(
output.stderr.contains("Failed to clone")
|| output.stderr.contains("does not exist")
|| output.stderr.contains("Local repository path does not exist"),
"Expected clone failure error, got: {}",
output.stderr
);
}
#[tokio::test]
async fn test_install_corrupted_lockfile() {
let project = TestProject::new().await.unwrap();
let manifest_content = ManifestFixture::basic().content;
project.write_manifest(&manifest_content).await.unwrap();
fs::write(project.project_path().join("agpm.lock"), "corrupted content").await.unwrap();
let output = project.run_agpm(&["install", "--no-cache"]).unwrap();
assert!(!output.success, "Expected command to fail but it succeeded");
assert!(
output.stderr.contains("Invalid or corrupted lockfile detected")
|| output.stderr.contains("Invalid lockfile syntax")
|| output.stderr.contains("Failed to parse lockfile"),
"Expected lockfile error, got: {}",
output.stderr
);
}