agpm-cli 0.4.14

AGent Package Manager - A Git-based package manager for coding agents
Documentation
//! Integration tests for the `agpm tree` command.

use assert_cmd::Command;
use predicates::prelude::*;
use std::env;
use tempfile::TempDir;
use tokio::fs;

use crate::common::ManifestBuilder;

async fn create_test_manifest(dir: &std::path::Path) {
    let manifest = ManifestBuilder::new()
        .add_source("community", "https://github.com/example/community.git")
        .add_standard_agent("code-reviewer", "community", "agents/reviewer.md")
        .add_local_agent("local-agent", "../local/agent.md")
        .add_standard_snippet("utils", "community", "snippets/utils.md")
        .build();

    fs::write(dir.join("agpm.toml"), manifest).await.unwrap();
}

async fn create_test_lockfile(dir: &std::path::Path) {
    let lockfile_content = r#"
version = 1

[[sources]]
name = "community"
url = "https://github.com/example/community.git"
fetched_at = "2024-01-01T00:00:00Z"

[[agents]]
name = "code-reviewer"
source = "community"
url = "https://github.com/example/community.git"
path = "agents/reviewer.md"
version = "v1.0.0"
resolved_commit = "abc123def456"
checksum = "sha256:abc123"
installed_at = ".claude/agents/agpm/code-reviewer.md"
dependencies = ["agent:agents/rust-helper", "snippet:snippets/utils"]
resource_type = "agent"

[[agents]]
name = "rust-helper"
source = "community"
url = "https://github.com/example/community.git"
path = "agents/rust-helper.md"
version = "v1.0.0"
resolved_commit = "abc123def456"
checksum = "sha256:def456"
installed_at = ".claude/agents/agpm/rust-helper.md"
dependencies = []
resource_type = "agent"

[[agents]]
name = "local-agent"
path = "../local/agent.md"
checksum = "sha256:local123"
installed_at = ".claude/agents/agpm/local-agent.md"
dependencies = []
resource_type = "agent"

[[snippets]]
name = "utils"
source = "community"
url = "https://github.com/example/community.git"
path = "snippets/utils.md"
version = "v1.0.0"
resolved_commit = "abc123def456"
checksum = "sha256:ghi789"
installed_at = ".agpm/snippets/utils.md"
dependencies = []
resource_type = "snippet"
"#;

    fs::write(dir.join("agpm.lock"), lockfile_content).await.unwrap();
}

#[tokio::test]
async fn test_tree_no_lockfile() {
    let temp = TempDir::new().unwrap();
    create_test_manifest(temp.path()).await;

    let mut cmd = Command::new(env!("CARGO_BIN_EXE_agpm"));
    cmd.current_dir(temp.path()).arg("tree");

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("No lockfile found"))
        .stdout(predicate::str::contains("agpm install"));
}

#[tokio::test]
async fn test_tree_basic() {
    let temp = TempDir::new().unwrap();
    create_test_manifest(temp.path()).await;
    create_test_lockfile(temp.path()).await;

    let mut cmd = Command::new(env!("CARGO_BIN_EXE_agpm"));
    cmd.current_dir(temp.path()).arg("tree");

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("code-reviewer"))
        .stdout(predicate::str::contains("rust-helper"))
        .stdout(predicate::str::contains("utils"))
        .stdout(predicate::str::contains("local-agent"));
}

#[tokio::test]
async fn test_tree_with_depth() {
    let temp = TempDir::new().unwrap();
    create_test_manifest(temp.path()).await;
    create_test_lockfile(temp.path()).await;

    let mut cmd = Command::new(env!("CARGO_BIN_EXE_agpm"));
    cmd.current_dir(temp.path()).arg("tree").arg("--depth").arg("1");

    cmd.assert().success();
}

#[tokio::test]
async fn test_tree_json_format() {
    let temp = TempDir::new().unwrap();
    create_test_manifest(temp.path()).await;
    create_test_lockfile(temp.path()).await;

    let mut cmd = Command::new(env!("CARGO_BIN_EXE_agpm"));
    cmd.current_dir(temp.path()).arg("tree").arg("--format").arg("json");

    cmd.assert()
        .success()
        .stdout(predicate::str::contains(r#""project":"#))
        .stdout(predicate::str::contains(r#""roots":"#));
}

#[tokio::test]
async fn test_tree_text_format() {
    let temp = TempDir::new().unwrap();
    create_test_manifest(temp.path()).await;
    create_test_lockfile(temp.path()).await;

    let mut cmd = Command::new(env!("CARGO_BIN_EXE_agpm"));
    cmd.current_dir(temp.path()).arg("tree").arg("--format").arg("text");

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("code-reviewer"))
        .stdout(predicate::str::contains("agent/"));
}

#[tokio::test]
async fn test_tree_invalid_format() {
    let temp = TempDir::new().unwrap();
    create_test_manifest(temp.path()).await;
    create_test_lockfile(temp.path()).await;

    let mut cmd = Command::new(env!("CARGO_BIN_EXE_agpm"));
    cmd.current_dir(temp.path()).arg("tree").arg("--format").arg("invalid");

    cmd.assert().failure().stderr(predicate::str::contains("Invalid format"));
}

#[tokio::test]
async fn test_tree_zero_depth() {
    let temp = TempDir::new().unwrap();
    create_test_manifest(temp.path()).await;
    create_test_lockfile(temp.path()).await;

    let mut cmd = Command::new(env!("CARGO_BIN_EXE_agpm"));
    cmd.current_dir(temp.path()).arg("tree").arg("--depth").arg("0");

    cmd.assert().failure().stderr(predicate::str::contains("must be at least 1"));
}

#[tokio::test]
async fn test_tree_filter_agents() {
    let temp = TempDir::new().unwrap();
    create_test_manifest(temp.path()).await;
    create_test_lockfile(temp.path()).await;

    let mut cmd = Command::new(env!("CARGO_BIN_EXE_agpm"));
    cmd.current_dir(temp.path()).arg("tree").arg("--agents");

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("agent/"))
        .stdout(predicate::str::contains("code-reviewer"));
}

#[tokio::test]
async fn test_tree_filter_snippets() {
    let temp = TempDir::new().unwrap();
    create_test_manifest(temp.path()).await;
    create_test_lockfile(temp.path()).await;

    let mut cmd = Command::new(env!("CARGO_BIN_EXE_agpm"));
    cmd.current_dir(temp.path()).arg("tree").arg("--snippets");

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("snippet/"))
        .stdout(predicate::str::contains("utils"));
}

#[tokio::test]
async fn test_tree_specific_package() {
    let temp = TempDir::new().unwrap();
    create_test_manifest(temp.path()).await;
    create_test_lockfile(temp.path()).await;

    let mut cmd = Command::new(env!("CARGO_BIN_EXE_agpm"));
    cmd.current_dir(temp.path()).arg("tree").arg("--package").arg("code-reviewer");

    cmd.assert().success().stdout(predicate::str::contains("code-reviewer"));
}

#[tokio::test]
async fn test_tree_package_not_found() {
    let temp = TempDir::new().unwrap();
    create_test_manifest(temp.path()).await;
    create_test_lockfile(temp.path()).await;

    let mut cmd = Command::new(env!("CARGO_BIN_EXE_agpm"));
    cmd.current_dir(temp.path()).arg("tree").arg("--package").arg("nonexistent");

    cmd.assert().failure().stderr(predicate::str::contains("not found"));
}

#[tokio::test]
async fn test_tree_with_transitive_deps() {
    let temp = TempDir::new().unwrap();
    create_test_manifest(temp.path()).await;
    create_test_lockfile(temp.path()).await;

    let mut cmd = Command::new(env!("CARGO_BIN_EXE_agpm"));
    cmd.current_dir(temp.path()).arg("tree");

    // Should show code-reviewer with its dependencies
    cmd.assert()
        .success()
        .stdout(predicate::str::contains("code-reviewer"))
        .stdout(predicate::str::contains("rust-helper"))
        .stdout(predicate::str::contains("utils"));
}

#[tokio::test]
async fn test_tree_no_manifest() {
    let temp = TempDir::new().unwrap();

    let mut cmd = Command::new(env!("CARGO_BIN_EXE_agpm"));
    cmd.current_dir(temp.path()).arg("tree");

    cmd.assert().failure().stderr(predicate::str::contains("Manifest file agpm.toml not found"));
}

#[tokio::test]
async fn test_tree_with_duplicates_flag() {
    let temp = TempDir::new().unwrap();
    create_test_manifest(temp.path()).await;
    create_test_lockfile(temp.path()).await;

    let mut cmd = Command::new(env!("CARGO_BIN_EXE_agpm"));
    cmd.current_dir(temp.path()).arg("tree").arg("--duplicates");

    cmd.assert().success();
}

#[tokio::test]
async fn test_tree_no_dedupe() {
    let temp = TempDir::new().unwrap();
    create_test_manifest(temp.path()).await;
    create_test_lockfile(temp.path()).await;

    let mut cmd = Command::new(env!("CARGO_BIN_EXE_agpm"));
    cmd.current_dir(temp.path()).arg("tree").arg("--no-dedupe");

    cmd.assert().success();
}