use crate::common::TestProject;
use anyhow::Result;
use tokio::fs;
#[tokio::test]
async fn test_context_checksum_generation() -> 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",
"templated",
r#"---
title: "{{ project.name }}"
version: "{{ config.version }}"
agpm:
templating: true
---
# {{ project.name }} v{{ config.version }}
This is a templated agent.
"#,
)
.await?;
test_repo
.add_resource(
"agents",
"plain",
r#"---
title: Plain Agent
version: "1.0.0"
---
# Plain Agent
This is a plain agent without templating.
"#,
)
.await?;
test_repo.commit_all("Initial version")?;
test_repo.tag_version("v1.0.0")?;
let repo_url = test_repo.bare_file_url(project.sources_path()).await?;
let manifest = format!(
r#"[sources]
test-repo = "{}"
[agents]
templated = {{ source = "test-repo", path = "agents/templated.md", version = "v1.0.0", template_vars = {{ project = {{ name = "MyProject" }}, config = {{ version = "2.0" }} }} }}
plain = {{ source = "test-repo", path = "agents/plain.md", version = "v1.0.0" }}
"#,
repo_url
);
project.write_manifest(&manifest).await?;
let output = project.run_agpm(&["install"])?;
assert!(output.success, "Install should succeed. Stderr: {}", output.stderr);
let lockfile = project.load_lockfile()?;
let templated_agent = lockfile
.agents
.iter()
.find(|a| a.name == "agents/templated")
.expect("Should find templated agent");
let plain_agent =
lockfile.agents.iter().find(|a| a.name == "agents/plain").expect("Should find plain agent");
assert!(
templated_agent.context_checksum.is_some(),
"Templated resource should have context checksum"
);
assert!(
plain_agent.context_checksum.is_none(),
"Plain resource should not have context checksum"
);
if let Some(checksum) = &templated_agent.context_checksum {
assert!(
checksum.starts_with("sha256:"),
"Context checksum should have sha256: prefix: {}",
checksum
);
let hash_part = &checksum[7..]; assert_eq!(hash_part.len(), 64, "SHA-256 hash should be 64 characters: {}", hash_part);
assert!(
hash_part.chars().all(|c| c.is_ascii_hexdigit()),
"SHA-256 hash should be hex digits: {}",
hash_part
);
}
Ok(())
}
#[tokio::test]
async fn test_context_checksum_uniqueness() -> 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(
"snippets",
"configurable",
r#"---
title: "{{ config.title }}"
env: "{{ config.env }}"
agpm:
templating: true
---
# {{ config.title }}
Environment: {{ config.env }}
"#,
)
.await?;
test_repo.commit_all("Initial version")?;
test_repo.tag_version("v1.0.0")?;
let repo_url = test_repo.bare_file_url(project.sources_path()).await?;
let manifest1 = format!(
r#"[sources]
test-repo = "{}"
[snippets]
config1 = {{ source = "test-repo", path = "snippets/configurable.md", version = "v1.0.0", template_vars = {{ config = {{ title = "Development", env = "dev" }} }} }}
"#,
repo_url
);
project.write_manifest(&manifest1).await?;
let output1 = project.run_agpm(&["install"])?;
assert!(output1.success, "First install should succeed");
let lockfile1 = project.load_lockfile()?;
let lockfile_path = project.project_path().join("agpm.lock");
fs::remove_file(&lockfile_path).await?;
let manifest2 = format!(
r#"[sources]
test-repo = "{}"
[snippets]
config2 = {{ source = "test-repo", path = "snippets/configurable.md", version = "v1.0.0", template_vars = {{ config = {{ title = "Production", env = "prod" }} }} }}
"#,
repo_url
);
project.write_manifest(&manifest2).await?;
let output2 = project.run_agpm(&["install"])?;
assert!(output2.success, "Second install should succeed");
let lockfile2 = project.load_lockfile()?;
let config1_snippet = lockfile1
.snippets
.iter()
.find(|s| s.manifest_alias.as_deref() == Some("config1"))
.expect("Should find config1 snippet");
let config2_snippet = lockfile2
.snippets
.iter()
.find(|s| s.manifest_alias.as_deref() == Some("config2"))
.expect("Should find config2 snippet");
let checksum1 = config1_snippet.context_checksum.as_ref();
let checksum2 = config2_snippet.context_checksum.as_ref();
assert!(checksum1.is_some(), "Should find context checksum for config1");
assert!(checksum2.is_some(), "Should find context checksum for config2");
assert_ne!(
checksum1, checksum2,
"Different template variables should produce different context checksums. Config1: {:?}, Config2: {:?}",
checksum1, checksum2
);
Ok(())
}
#[tokio::test]
async fn test_context_checksum_consistency() -> 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",
"consistent",
r#"---
title: "{{ project.title }}"
author: "{{ project.author }}"
agpm:
templating: true
---
# {{ project.title }} by {{ project.author }}
Consistent agent.
"#,
)
.await?;
test_repo.commit_all("Initial version")?;
test_repo.tag_version("v1.0.0")?;
let repo_url = test_repo.bare_file_url(project.sources_path()).await?;
let manifest_template = format!(
r#"[sources]
test-repo = "{}"
[agents]
consistent = {{ source = "test-repo", path = "agents/consistent.md", version = "v1.0.0", template_vars = {{ project = {{ title = "{}", author = "{}" }} }} }}
"#,
repo_url, "{}", "{}"
);
let template_vars = vec![
("MyProject".to_string(), "Alice".to_string()),
("MyProject".to_string(), "Alice".to_string()), ("DifferentProject".to_string(), "Alice".to_string()),
("MyProject".to_string(), "Bob".to_string()),
];
let mut checksums = Vec::new();
for (title, author) in template_vars {
let lockfile_path = project.project_path().join("agpm.lock");
if lockfile_path.exists() {
fs::remove_file(&lockfile_path).await?;
}
let _manifest = manifest_template.replace("{}", &title).replace("{}", &author);
let manifest = format!(
r#"[sources]
test-repo = "{}"
[agents]
consistent = {{ source = "test-repo", path = "agents/consistent.md", version = "v1.0.0", template_vars = {{ project = {{ title = "{}", author = "{}" }} }} }}
"#,
repo_url, title, author
);
project.write_manifest(&manifest).await?;
let output = project.run_agpm(&["install"])?;
assert!(output.success, "Install should succeed for {} by {}", title, author);
let lockfile = project.load_lockfile()?;
let consistent_agent =
lockfile.agents.iter().find(|a| a.name == "agents/consistent").unwrap_or_else(|| {
panic!("Should find consistent agent for {} by {}", title, author)
});
let context_checksum = consistent_agent
.context_checksum
.as_ref()
.unwrap_or_else(|| panic!("Should find context checksum for {} by {}", title, author));
checksums.push(context_checksum.clone());
}
assert_eq!(
checksums[0], checksums[1],
"Same template variables should produce same context checksum: {}",
checksums[0]
);
assert_ne!(checksums[0], checksums[2], "Different titles should produce different checksums");
assert_ne!(checksums[0], checksums[3], "Different authors should produce different checksums");
assert_ne!(
checksums[2], checksums[3],
"Different combinations should produce different checksums"
);
Ok(())
}
#[tokio::test]
async fn test_context_checksum_complex_structures() -> 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(
"commands",
"complex-command",
r#"---
config:
database:
host: "{{ db.host }}"
port: {{ db.port }}
ssl: {{ db.ssl }}
features:
{% for feature in features %}
- {{ feature }}
{% endfor %}
timeouts:
connect: {{ timeouts.connect }}
read: {{ timeouts.read }}
agpm:
templating: true
---
# Complex Command
Database: {{ db.host }}:{{ db.port }}
Features: {{ features | join(sep=", ") }}
Timeouts: connect={{ timeouts.connect }}s, read={{ timeouts.read }}s
"#,
)
.await?;
test_repo.commit_all("Initial version")?;
test_repo.tag_version("v1.0.0")?;
let repo_url = test_repo.bare_file_url(project.sources_path()).await?;
let manifest = format!(
r#"[sources]
test-repo = "{}"
[commands]
complex = {{ source = "test-repo", path = "commands/complex-command.md", version = "v1.0.0", template_vars = {{ db = {{ host = "db.example.com", port = 5432, ssl = true }}, features = ["auth", "logging", "monitoring"], timeouts = {{ connect = 10, read = 30 }} }} }}
"#,
repo_url
);
project.write_manifest(&manifest).await?;
let output = project.run_agpm(&["install"])?;
assert!(output.success, "Install should succeed. Stderr: {}", output.stderr);
let lockfile = project.load_lockfile()?;
if let Some(complex_cmd) = lockfile.commands.iter().find(|c| c.name.contains("complex-command"))
{
if let Some(checksum) = &complex_cmd.context_checksum {
assert!(
checksum.starts_with("sha256:"),
"Context checksum should have proper format: {}",
checksum
);
}
}
let command_path = project.project_path().join(".claude/commands/agpm/complex-command.md");
assert!(command_path.exists(), "Complex command should be installed");
let command_content = fs::read_to_string(&command_path).await?;
assert!(
command_content.contains("db.example.com:5432"),
"Command should contain rendered database info"
);
assert!(
command_content.contains("auth, logging, monitoring"),
"Command should contain rendered features"
);
assert!(
command_content.contains("connect=10s, read=30s"),
"Command should contain rendered timeouts"
);
Ok(())
}