use anyhow::{Context, Result};
use tokio::fs;
use crate::common::{ManifestBuilder, TestProject};
#[tokio::test]
async fn test_template_paths_platform_native() -> Result<()> {
agpm_cli::test_utils::init_test_logging(None);
let project = TestProject::new().await?;
let test_repo = project.create_source_repo("test-repo").await?;
let template_content = r#"---
title: Path Test Agent
agpm:
templating: true
---
# {{ agpm.resource.name }}
This agent is installed at: `{{ agpm.resource.install_path }}`
You can find it at: following location:
{{ agpm.resource.install_path }}
"#;
test_repo.add_resource("agents", "path-test", template_content).await?;
let snippet_content = r#"---
title: Nested Snippet
agpm:
templating: true
---
# Utility Snippet
Install path: {{ agpm.resource.install_path }}
"#;
test_repo.add_resource("snippets", "utils", snippet_content).await?;
test_repo.commit_all("Add test resources")?;
test_repo.tag_version("v1.0.0")?;
let repo_url = test_repo.bare_file_url(project.sources_path()).await?;
let manifest = ManifestBuilder::new()
.add_source("test-repo", &repo_url)
.add_agent("path-test", |d| {
d.source("test-repo").path("agents/path-test.md").version("v1.0.0")
})
.add_snippet("nested-helper", |d| {
d.source("test-repo").path("snippets/utils.md").version("v1.0.0")
})
.build();
project.write_manifest(&manifest).await?;
let output = project.run_agpm(&["install"])?;
assert!(output.success, "Install should succeed. stderr: {}", output.stderr);
let agent_path = project.project_path().join(".claude/agents/agpm/path-test.md");
let agent_content = fs::read_to_string(&agent_path).await?;
let snippet_path = project.project_path().join(".agpm/snippets/utils.md");
let snippet_content = fs::read_to_string(&snippet_path).await?;
#[cfg(windows)]
{
assert!(
agent_content.contains(".claude\\agents\\agpm\\path-test.md"),
"Agent content should contain Windows-style path with backslashes. Content:\n{}",
agent_content
);
assert!(
snippet_content.contains(".agpm\\snippets\\utils.md"),
"Snippet content should contain Windows-style path with backslashes. Content:\n{}",
snippet_content
);
}
#[cfg(not(windows))]
{
assert!(
agent_content.contains(".claude/agents/agpm/path-test.md"),
"Agent content should contain Unix-style path with forward slashes"
);
assert!(
snippet_content.contains(".agpm/snippets/utils.md"),
"Snippet content should contain Unix-style path with forward slashes"
);
}
Ok(())
}
#[tokio::test]
async fn test_project_template_variables() -> Result<()> {
agpm_cli::test_utils::init_test_logging(None);
let project = TestProject::new().await?;
let test_repo = project.create_source_repo("test-repo").await?;
test_repo
.add_resource(
"agents",
"project-agent",
r#"---
title: Project Code Reviewer
agpm:
templating: true
---
# {{ agpm.project.name }} Code Reviewer
I review code for {{ agpm.project.name }} (version {{ agpm.project.version }}).
## Guidelines to Follow
Please refer to our documentation:
- Style Guide: {{ agpm.project.paths.style_guide }}
- Architecture: {{ agpm.project.paths.architecture }}
- Conventions: {{ agpm.project.paths.conventions }}
## Code Standards
When reviewing or generating code, enforce:
- Max line length: {{ agpm.project.standards.max_line_length }} characters
- Indentation: {{ agpm.project.standards.indent_size }} {{ agpm.project.standards.indent_style }}
- Naming: {{ agpm.project.standards.naming_convention }}
## Testing Requirements
{% if agpm.project.custom.require_tests %}
All code changes MUST include tests using {{ agpm.project.custom.test_framework }}.
{% endif %}
{% if agpm.project.custom.require_docstrings %}
All functions require docstrings in {{ agpm.project.custom.docstring_style }} format.
{% endif %}
"#,
)
.await?;
test_repo.commit_all("Add project agent")?;
test_repo.tag_version("v1.0.0")?;
let repo_url = test_repo.bare_file_url(project.sources_path()).await?;
let manifest_content = format!(
r#"[sources]
test-repo = "{}"
[agents]
project-agent = {{ source = "test-repo", path = "agents/project-agent.md", version = "v1.0.0" }}
[project]
# Arbitrary variables - structure is completely flexible
name = "TestProject"
version = "2.1.0"
# Nested sections for organization (optional, just convention)
[project.paths]
style_guide = "docs/STYLE_GUIDE.md"
architecture = "docs/ARCHITECTURE.md"
conventions = "docs/CONVENTIONS.md"
[project.standards]
max_line_length = 100
indent_style = "spaces"
indent_size = 4
naming_convention = "snake_case"
[project.custom]
require_tests = true
test_framework = "pytest"
require_docstrings = true
docstring_style = "google"
"#,
repo_url
);
let manifest_path = project.project_path().join("agpm.toml");
fs::write(&manifest_path, &manifest_content).await?;
let output = project.run_agpm(&["install"])?;
assert!(output.success, "Installation should succeed");
let installed_path = project.project_path().join(".claude/agents/agpm/project-agent.md");
let content =
fs::read_to_string(&installed_path).await.context("Failed to read installed agent file")?;
assert!(
content.contains("# TestProject Code Reviewer"),
"Project name should be substituted in title. Content:\n{}",
content
);
assert!(
content.contains("I review code for TestProject (version 2.1.0)"),
"Project name and version should be substituted. Content:\n{}",
content
);
assert!(
content.contains("Style Guide: docs/STYLE_GUIDE.md"),
"Style guide path should be substituted. Content:\n{}",
content
);
assert!(
content.contains("Architecture: docs/ARCHITECTURE.md"),
"Architecture path should be substituted. Content:\n{}",
content
);
assert!(
content.contains("Conventions: docs/CONVENTIONS.md"),
"Conventions path should be substituted. Content:\n{}",
content
);
assert!(
content.contains("Max line length: 100 characters"),
"Max line length standard should be substituted. Content:\n{}",
content
);
assert!(
content.contains("Indentation: 4 spaces"),
"Indentation standard should be substituted. Content:\n{}",
content
);
assert!(
content.contains("Naming: snake_case"),
"Naming convention should be substituted. Content:\n{}",
content
);
assert!(
content.contains("All code changes MUST include tests using pytest"),
"Testing requirement should be rendered. Content:\n{}",
content
);
assert!(
content.contains("All functions require docstrings in google format"),
"Docstring requirement should be rendered. Content:\n{}",
content
);
assert!(
!content.contains("{{ agpm.project"),
"Template syntax should be replaced. Content:\n{}",
content
);
assert!(!content.contains("{% for"), "Loop syntax should be replaced. Content:\n{}", content);
Ok(())
}