use crate::common::TestProject;
use agpm_cli::utils::platform::normalize_path_for_storage;
use anyhow::Result;
use tokio::fs;
#[tokio::test]
async fn test_install_disabled_tools_excluded() -> Result<()> {
let project = TestProject::new().await?;
let source_repo = project.create_source_repo("test").await?;
fs::create_dir_all(source_repo.path.join("agents")).await?;
fs::create_dir_all(source_repo.path.join("agent")).await?;
fs::write(
source_repo.path.join("agents/claude-agent.md"),
"# Claude Code Agent\nThis is a claude-code agent.",
)
.await?;
fs::write(
source_repo.path.join("agent/opencode-agent.md"),
"# OpenCode Agent\nThis is an opencode agent.",
)
.await?;
source_repo.git.add_all()?;
source_repo.git.commit("Initial commit")?;
source_repo.git.tag("v1.0.0")?;
let manifest_content = format!(
r#"
[sources]
test = "{}"
[tools.claude-code]
path = ".claude"
resources = {{ agents = {{ path = "agents/agpm", flatten = true }} }}
[tools.opencode]
enabled = false
path = ".opencode"
resources = {{ agents = {{ path = "agent/agpm", flatten = true }} }}
[agents]
claude-agent = {{ source = "test", path = "agents/claude-agent.md", version = "v1.0.0" }}
opencode-agent = {{ source = "test", path = "agent/opencode-agent.md", version = "v1.0.0", tool = "opencode" }}
"#,
normalize_path_for_storage(&source_repo.path)
);
fs::write(project.project_path().join("agpm.toml"), manifest_content).await?;
let output = project.run_agpm(&["install"])?;
assert!(output.success);
let claude_agent_path =
project.project_path().join(".claude").join("agents").join("agpm").join("claude-agent.md");
assert!(claude_agent_path.exists(), "Claude-code agent should be installed");
let opencode_agent_path = project
.project_path()
.join(".opencode")
.join("agent")
.join("agpm")
.join("opencode-agent.md");
assert!(!opencode_agent_path.exists(), "OpenCode agent should NOT be installed");
let lockfile_content = fs::read_to_string(project.project_path().join("agpm.lock")).await?;
assert!(lockfile_content.contains("claude-agent"));
assert!(!lockfile_content.contains("opencode-agent"));
Ok(())
}
#[tokio::test]
async fn test_install_enabled_tools_included() -> Result<()> {
let project = TestProject::new().await?;
let source_repo = project.create_source_repo("test").await?;
fs::create_dir_all(source_repo.path.join("agents")).await?;
fs::create_dir_all(source_repo.path.join("agent")).await?;
fs::write(
source_repo.path.join("agents/claude-agent.md"),
"# Claude Code Agent\nThis is a claude-code agent.",
)
.await?;
fs::write(
source_repo.path.join("agent/opencode-agent.md"),
"# OpenCode Agent\nThis is an opencode agent.",
)
.await?;
source_repo.git.add_all()?;
source_repo.git.commit("Initial commit")?;
source_repo.git.tag("v1.0.0")?;
let manifest_content = format!(
r#"
[sources]
test = "{}"
[tools.claude-code]
path = ".claude"
resources = {{ agents = {{ path = "agents/agpm", flatten = true }} }}
[tools.opencode]
enabled = true
path = ".opencode"
resources = {{ agents = {{ path = "agent/agpm", flatten = true }} }}
[agents]
claude-agent = {{ source = "test", path = "agents/claude-agent.md", version = "v1.0.0" }}
opencode-agent = {{ source = "test", path = "agent/opencode-agent.md", version = "v1.0.0", tool = "opencode" }}
"#,
normalize_path_for_storage(&source_repo.path)
);
fs::write(project.project_path().join("agpm.toml"), manifest_content).await?;
let output = project.run_agpm(&["install"])?;
assert!(output.success);
let claude_agent_path =
project.project_path().join(".claude").join("agents").join("agpm").join("claude-agent.md");
assert!(claude_agent_path.exists(), "Claude-code agent should be installed");
let opencode_agent_path = project
.project_path()
.join(".opencode")
.join("agent")
.join("agpm")
.join("opencode-agent.md");
assert!(opencode_agent_path.exists(), "OpenCode agent should be installed");
let lockfile_content = fs::read_to_string(project.project_path().join("agpm.lock")).await?;
assert!(lockfile_content.contains("claude-agent"));
assert!(lockfile_content.contains("opencode-agent"));
Ok(())
}
#[tokio::test]
async fn test_install_default_tool_enabled() -> Result<()> {
let project = TestProject::new().await?;
let source_repo = project.create_source_repo("test").await?;
fs::create_dir_all(source_repo.path.join("agent")).await?;
fs::write(
source_repo.path.join("agent/opencode-agent.md"),
"# OpenCode Agent\nThis is an opencode agent.",
)
.await?;
source_repo.git.add_all()?;
source_repo.git.commit("Initial commit")?;
source_repo.git.tag("v1.0.0")?;
let manifest_content = format!(
r#"
[sources]
test = "{}"
[tools.opencode]
path = ".opencode"
resources = {{ agents = {{ path = "agent/agpm", flatten = true }} }}
[agents]
opencode-agent = {{ source = "test", path = "agent/opencode-agent.md", version = "v1.0.0", tool = "opencode" }}
"#,
normalize_path_for_storage(&source_repo.path)
);
fs::write(project.project_path().join("agpm.toml"), manifest_content).await?;
let output = project.run_agpm(&["install"])?;
assert!(output.success);
let opencode_agent_path = project
.project_path()
.join(".opencode")
.join("agent")
.join("agpm")
.join("opencode-agent.md");
assert!(opencode_agent_path.exists(), "OpenCode agent should be installed by default");
let lockfile_content = fs::read_to_string(project.project_path().join("agpm.lock")).await?;
assert!(lockfile_content.contains("opencode-agent"));
Ok(())
}
#[tokio::test]
async fn test_update_and_install_respect_tool_enable_disable() -> Result<()> {
let project = TestProject::new().await?;
let source_repo = project.create_source_repo("test").await?;
fs::create_dir_all(source_repo.path.join("agents")).await?;
fs::create_dir_all(source_repo.path.join("agent")).await?;
fs::write(
source_repo.path.join("agents/claude-agent.md"),
"# Claude Code Agent v1\nThis is version 1.",
)
.await?;
fs::write(
source_repo.path.join("agent/opencode-agent.md"),
"# OpenCode Agent v1\nThis is version 1.",
)
.await?;
source_repo.git.add_all()?;
source_repo.git.commit("Initial commit")?;
source_repo.git.tag("v1.0.0")?;
let manifest_content = format!(
r#"
[sources]
test = "{}"
[tools.claude-code]
path = ".claude"
resources = {{ agents = {{ path = "agents/agpm", flatten = true }} }}
[tools.opencode]
enabled = false
path = ".opencode"
resources = {{ agents = {{ path = "agent/agpm", flatten = true }} }}
[agents]
claude-agent = {{ source = "test", path = "agents/claude-agent.md", version = "v1.0.0" }}
opencode-agent = {{ source = "test", path = "agent/opencode-agent.md", version = "v1.0.0", tool = "opencode" }}
"#,
normalize_path_for_storage(&source_repo.path)
);
fs::write(project.project_path().join("agpm.toml"), manifest_content).await?;
let manifest_content_debug =
fs::read_to_string(project.project_path().join("agpm.toml")).await?;
println!("Manifest content:\n{}", manifest_content_debug);
let output = project.run_agpm(&["install"])?;
assert!(output.success);
assert!(project.project_path().join(".claude/agents/agpm/claude-agent.md").exists());
assert!(!project.project_path().join(".opencode/agent/agpm/opencode-agent.md").exists());
fs::write(
source_repo.path.join("agents/claude-agent.md"),
"# Claude Code Agent v2\nThis is version 2.",
)
.await?;
fs::write(
source_repo.path.join("agent/opencode-agent.md"),
"# OpenCode Agent v2\nThis is version 2.",
)
.await?;
source_repo.git.add_all()?;
source_repo.git.commit("Update to v2")?;
source_repo.git.tag("v2.0.0")?;
let updated_manifest_content = format!(
r#"
[sources]
test = "{}"
[tools.claude-code]
path = ".claude"
resources = {{ agents = {{ path = "agents/agpm", flatten = true }} }}
[tools.opencode]
enabled = false
path = ".opencode"
resources = {{ agents = {{ path = "agent/agpm", flatten = true }} }}
[agents]
claude-agent = {{ source = "test", path = "agents/claude-agent.md", version = "v2.0.0" }}
opencode-agent = {{ source = "test", path = "agent/opencode-agent.md", version = "v2.0.0", tool = "opencode" }}
"#,
normalize_path_for_storage(&source_repo.path)
);
fs::write(project.project_path().join("agpm.toml"), updated_manifest_content).await?;
let output = project.run_agpm(&["update"])?;
assert!(output.success);
assert!(
!project.project_path().join(".opencode/agent/agpm/opencode-agent.md").exists(),
"OpenCode agent should still not be installed when tool is disabled"
);
let enabled_manifest_content = format!(
r#"
[sources]
test = "{}"
[tools.claude-code]
path = ".claude"
resources = {{ agents = {{ path = "agents/agpm", flatten = true }} }}
[tools.opencode]
enabled = true
path = ".opencode"
resources = {{ agents = {{ path = "agent/agpm", flatten = true }} }}
[agents]
claude-agent = {{ source = "test", path = "agents/claude-agent.md", version = "v2.0.0" }}
opencode-agent = {{ source = "test", path = "agent/opencode-agent.md", version = "v2.0.0", tool = "opencode" }}
"#,
normalize_path_for_storage(&source_repo.path)
);
fs::write(project.project_path().join("agpm.toml"), enabled_manifest_content).await?;
let output = project.run_agpm(&["install"])?;
assert!(output.success);
assert!(
project.project_path().join(".opencode/agent/agpm/opencode-agent.md").exists(),
"OpenCode agent should be installed when tool is enabled"
);
let opencode_agent_content =
fs::read_to_string(project.project_path().join(".opencode/agent/agpm/opencode-agent.md"))
.await?;
assert!(opencode_agent_content.contains("version 2"));
Ok(())
}
#[tokio::test]
async fn test_opencode_defaults() -> Result<()> {
let project = TestProject::new().await?;
let source_repo = project.create_source_repo("test").await?;
fs::create_dir_all(source_repo.path.join("agent/subdir")).await?;
fs::create_dir_all(source_repo.path.join("command/commands")).await?;
fs::write(
source_repo.path.join("agent/subdir/my-agent.md"),
"# My OpenCode Agent\nThis is in a subdirectory.",
)
.await?;
fs::write(
source_repo.path.join("command/commands/my-command.md"),
"# My OpenCode Command\nThis is in a subdirectory.",
)
.await?;
source_repo.git.add_all()?;
source_repo.git.commit("Initial commit")?;
source_repo.git.tag("v1.0.0")?;
let manifest_content = format!(
r#"
[sources]
test = "{}"
[agents]
my-agent = {{ source = "test", path = "agent/subdir/my-agent.md", version = "v1.0.0", tool = "opencode" }}
[commands]
my-command = {{ source = "test", path = "command/commands/my-command.md", version = "v1.0.0", tool = "opencode" }}
"#,
normalize_path_for_storage(&source_repo.path)
);
fs::write(project.project_path().join("agpm.toml"), manifest_content).await?;
let output = project.run_agpm(&["install"])?;
assert!(output.success);
println!("Install stdout: {}", output.stdout);
println!("Install stderr: {}", output.stderr);
let opencode_dir = project.project_path().join(".opencode");
if opencode_dir.exists() {
println!("OpenCode directory exists");
let mut entries = fs::read_dir(&opencode_dir).await?;
while let Some(entry) = entries.next_entry().await? {
println!("Found: {}", entry.path().display());
if entry.path().is_dir() {
let mut sub_entries = fs::read_dir(&entry.path()).await?;
while let Some(sub_entry) = sub_entries.next_entry().await? {
println!(" Sub-entry: {}", sub_entry.path().display());
}
}
}
} else {
println!("OpenCode directory does not exist");
}
let nested_agent_path = project
.project_path()
.join(".opencode")
.join("agent")
.join("agpm")
.join("subdir")
.join("my-agent.md");
let nested_command_path = project
.project_path()
.join(".opencode")
.join("command")
.join("commands")
.join("my-command.md");
if nested_agent_path.exists() {
println!("Agent found in nested directory - flattening is not working");
let content = fs::read_to_string(&nested_agent_path).await?;
println!("Agent content: {}", content);
}
if nested_command_path.exists() {
println!("Command found in nested directory - flattening is not working");
let content = fs::read_to_string(&nested_command_path).await?;
println!("Command content: {}", content);
}
let opencode_agent_path =
project.project_path().join(".opencode").join("agent").join("agpm").join("my-agent.md");
assert!(opencode_agent_path.exists(), "OpenCode agent should be installed by default");
let opencode_command_path =
project.project_path().join(".opencode").join("command").join("agpm").join("my-command.md");
assert!(opencode_command_path.exists(), "OpenCode command should be installed by default");
let nested_agent_path = project
.project_path()
.join(".opencode")
.join("agent")
.join("agpm")
.join("subdir")
.join("my-agent.md");
let nested_command_path = project
.project_path()
.join(".opencode")
.join("command")
.join("commands")
.join("my-command.md");
assert!(
!nested_agent_path.exists(),
"Agent should be flattened, not preserve subdirectory structure"
);
assert!(
!nested_command_path.exists(),
"Command should be flattened, not preserve subdirectory structure"
);
let agent_content = fs::read_to_string(&opencode_agent_path).await?;
assert!(agent_content.contains("My OpenCode Agent"));
let command_content = fs::read_to_string(&opencode_command_path).await?;
assert!(command_content.contains("My OpenCode Command"));
let lockfile_content = fs::read_to_string(project.project_path().join("agpm.lock")).await?;
assert!(lockfile_content.contains("my-agent"));
assert!(lockfile_content.contains("my-command"));
Ok(())
}
#[tokio::test]
async fn test_opencode_explicit_config_requires_explicit_flatten() -> Result<()> {
let project = TestProject::new().await?;
let source_repo = project.create_source_repo("test").await?;
fs::create_dir_all(source_repo.path.join("agent/subdir")).await?;
fs::create_dir_all(source_repo.path.join("command/commands")).await?;
fs::write(
source_repo.path.join("agent/subdir/my-agent.md"),
"# My OpenCode Agent\nThis is in a subdirectory.",
)
.await?;
fs::write(
source_repo.path.join("command/commands/my-command.md"),
"# My OpenCode Command\nThis is in a subdirectory.",
)
.await?;
source_repo.git.add_all()?;
source_repo.git.commit("Initial commit")?;
source_repo.git.tag("v1.0.0")?;
let manifest_content = format!(
r#"
[sources]
test = "{}"
[tools.opencode]
path = ".opencode"
resources = {{ agents = {{ path = "agent/agpm", flatten = true }}, commands = {{ path = "command/agpm", flatten = true }}, mcp-servers = {{ merge-target = ".opencode/opencode.json" }} }}
[agents]
my-agent = {{ source = "test", path = "agent/subdir/my-agent.md", version = "v1.0.0", tool = "opencode" }}
[commands]
my-command = {{ source = "test", path = "command/commands/my-command.md", version = "v1.0.0", tool = "opencode" }}
"#,
normalize_path_for_storage(&source_repo.path)
);
fs::write(project.project_path().join("agpm.toml"), manifest_content).await?;
let output = project.run_agpm(&["install"])?;
assert!(output.success);
let opencode_agent_path =
project.project_path().join(".opencode").join("agent").join("agpm").join("my-agent.md");
assert!(opencode_agent_path.exists(), "OpenCode agent should be installed and flattened");
let opencode_command_path =
project.project_path().join(".opencode").join("command").join("agpm").join("my-command.md");
assert!(opencode_command_path.exists(), "OpenCode command should be installed and flattened");
let nested_agent_path = project
.project_path()
.join(".opencode")
.join("agent")
.join("agpm")
.join("subdir")
.join("my-agent.md");
let nested_command_path = project
.project_path()
.join(".opencode")
.join("command")
.join("commands")
.join("my-command.md");
assert!(!nested_agent_path.exists(), "Agent should be flattened when explicitly configured");
assert!(
!nested_command_path.exists(),
"Command should be flattened when explicitly configured"
);
let agent_content = fs::read_to_string(&opencode_agent_path).await?;
assert!(agent_content.contains("My OpenCode Agent"));
let command_content = fs::read_to_string(&opencode_command_path).await?;
assert!(command_content.contains("My OpenCode Command"));
Ok(())
}