use anyhow::Result;
use crate::common::{ManifestBuilder, TestProject};
#[tokio::test]
async fn test_transitive_pattern_dependency_expands() -> Result<()> {
agpm_cli::test_utils::init_test_logging(None);
let project = TestProject::new().await?;
let repo = project.create_source_repo("community").await?;
repo.add_resource("commands", "cmd-one", "# Command One\n\nFirst command.").await?;
repo.add_resource("commands", "cmd-two", "# Command Two\n\nSecond command.").await?;
repo.add_resource(
"snippets",
"helper-one",
r#"---
dependencies:
commands:
- path: ../commands/cmd-one.md
---
# Helper One
First helper with transitive dependency on cmd-one.
"#,
)
.await?;
repo.add_resource(
"snippets",
"helper-two",
r#"---
dependencies:
commands:
- path: ../commands/cmd-two.md
---
# Helper Two
Second helper with transitive dependency on cmd-two.
"#,
)
.await?;
repo.add_resource(
"agents",
"parent",
r#"---
dependencies:
snippets:
- path: ../snippets/helper-*.md
---
# Parent Agent
Has a glob pattern in transitive dependencies that matches multiple snippets.
Each snippet has its own transitive dependencies.
"#,
)
.await?;
repo.commit_all("Add resources")?;
repo.tag_version("v1.0.0")?;
let source_url = repo.bare_file_url(project.sources_path()).await?;
let manifest = ManifestBuilder::new()
.add_source("community", &source_url)
.add_standard_agent("parent", "community", "agents/parent.md")
.build();
project.write_manifest(&manifest).await?;
project.run_agpm(&["install"])?.assert_success();
let parent_path = project.project_path().join(".claude/agents/agpm/parent.md");
assert!(tokio::fs::metadata(&parent_path).await.is_ok(), "Parent agent should be installed");
let helper_one_path = project.project_path().join(".claude/snippets/agpm/helper-one.md");
let helper_two_path = project.project_path().join(".claude/snippets/agpm/helper-two.md");
assert!(
tokio::fs::metadata(&helper_one_path).await.is_ok(),
"Helper-one should be installed (matched by pattern)"
);
assert!(
tokio::fs::metadata(&helper_two_path).await.is_ok(),
"Helper-two should be installed (matched by pattern)"
);
let cmd_one_path = project.project_path().join(".claude/commands/agpm/cmd-one.md");
let cmd_two_path = project.project_path().join(".claude/commands/agpm/cmd-two.md");
assert!(
tokio::fs::metadata(&cmd_one_path).await.is_ok(),
"cmd-one should be installed (transitive dep of helper-one)"
);
assert!(
tokio::fs::metadata(&cmd_two_path).await.is_ok(),
"cmd-two should be installed (transitive dep of helper-two)"
);
let lockfile_content = project.read_lockfile().await?;
assert!(
lockfile_content.contains(r#"name = "agents/parent""#),
"Lockfile should contain parent with canonical name"
);
assert!(
lockfile_content.contains(r#"name = "snippets/helper-one""#),
"Lockfile should contain helper-one"
);
assert!(
lockfile_content.contains(r#"name = "snippets/helper-two""#),
"Lockfile should contain helper-two"
);
assert!(
lockfile_content.contains(r#"name = "commands/cmd-one""#),
"Lockfile should contain cmd-one"
);
assert!(
lockfile_content.contains(r#"name = "commands/cmd-two""#),
"Lockfile should contain cmd-two"
);
Ok(())
}
#[tokio::test]
async fn test_manifest_pattern_has_transitive_deps_resolved() -> Result<()> {
agpm_cli::test_utils::init_test_logging(None);
let project = TestProject::new().await?;
let repo = project.create_source_repo("community").await?;
repo.add_resource("commands", "cmd-a", "# Command A\n\nFirst command.").await?;
repo.add_resource("commands", "cmd-b", "# Command B\n\nSecond command.").await?;
repo.add_resource(
"snippets",
"util-one",
r#"---
dependencies:
commands:
- path: ../commands/cmd-a.md
---
# Util One
First utility with transitive dependency on cmd-a.
"#,
)
.await?;
repo.add_resource(
"snippets",
"util-two",
r#"---
dependencies:
commands:
- path: ../commands/cmd-b.md
---
# Util Two
Second utility with transitive dependency on cmd-b.
"#,
)
.await?;
repo.commit_all("Add resources")?;
repo.tag_version("v1.0.0")?;
let source_url = repo.bare_file_url(project.sources_path()).await?;
let manifest = ManifestBuilder::new()
.add_source("community", &source_url)
.add_snippet("util-pattern", |d| {
d.source("community").path("snippets/util-*.md").version("v1.0.0")
})
.build();
project.write_manifest(&manifest).await?;
project.run_agpm(&["install"])?.assert_success();
let util_one_path = project.project_path().join(".agpm/snippets/util-one.md");
let util_two_path = project.project_path().join(".agpm/snippets/util-two.md");
assert!(
tokio::fs::metadata(&util_one_path).await.is_ok(),
"util-one should be installed (matched by manifest pattern)"
);
assert!(
tokio::fs::metadata(&util_two_path).await.is_ok(),
"util-two should be installed (matched by manifest pattern)"
);
let cmd_a_path = project.project_path().join(".claude/commands/agpm/cmd-a.md");
let cmd_b_path = project.project_path().join(".claude/commands/agpm/cmd-b.md");
assert!(
tokio::fs::metadata(&cmd_a_path).await.is_ok(),
"cmd-a should be installed (transitive dep of util-one)"
);
assert!(
tokio::fs::metadata(&cmd_b_path).await.is_ok(),
"cmd-b should be installed (transitive dep of util-two)"
);
let lockfile_content = project.read_lockfile().await?;
assert!(
lockfile_content.contains(r#"name = "snippets/util-one""#),
"Lockfile should contain util-one"
);
assert!(
lockfile_content.contains(r#"name = "snippets/util-two""#),
"Lockfile should contain util-two"
);
assert!(
lockfile_content.contains(r#"name = "commands/cmd-a""#),
"Lockfile should contain cmd-a"
);
assert!(
lockfile_content.contains(r#"name = "commands/cmd-b""#),
"Lockfile should contain cmd-b"
);
Ok(())
}