agpm-cli 0.4.14

AGent Package Manager - A Git-based package manager for coding agents
Documentation
//! Additional integration tests for CLI modules.
//!
//! This module provides comprehensive testing for the CLI interface, including
//! argument parsing, configuration building, and command execution paths.
//!
//! # Test Categories
//!
//! - **Argument Parsing**: Tests that CLI flags and options are parsed correctly
//! - **Configuration Building**: Tests that CLI arguments are converted to [`CliConfig`](crate::cli::CliConfig)
//! - **Command Execution**: Integration tests that verify commands execute successfully
//! - **Environment Handling**: Tests for environment variable application and restoration
//!
//! # Test Safety
//!
//! Tests that modify the working directory or environment variables use:
//! - Temporary directories for file operations
//! - Explicit environment variable restoration
//!
//! This ensures tests don't interfere with each other or the development environment.

#[cfg(test)]
mod cli_tests {
    use crate::cli::Cli;
    use anyhow::Result;
    use clap::Parser;

    #[test]
    fn test_cli_parsing() -> Result<()> {
        // Test basic command parsing
        let cli = Cli::try_parse_from(["agpm", "--help"]);
        assert!(cli.is_err()); // --help causes a special error

        let _cli = Cli::try_parse_from(["agpm", "list"])?;
        Ok(())
    }

    #[test]
    fn test_cli_verbose_flag() -> Result<()> {
        let cli = Cli::try_parse_from(["agpm", "--verbose", "list"])?;
        assert!(cli.verbose);
        Ok(())
    }

    #[test]
    fn test_cli_quiet_flag() -> Result<()> {
        let cli = Cli::try_parse_from(["agpm", "--quiet", "list"])?;
        assert!(cli.quiet);
        Ok(())
    }

    #[test]
    fn test_cli_no_progress_flag() -> Result<()> {
        let cli = Cli::try_parse_from(["agpm", "--no-progress", "list"])?;
        assert!(cli.no_progress);
        Ok(())
    }

    #[test]
    fn test_cli_config_option() -> Result<()> {
        let cli = Cli::try_parse_from(["agpm", "--config", "/path/to/config", "list"])?;
        assert_eq!(cli.config, Some("/path/to/config".to_string()));
        Ok(())
    }

    #[test]
    fn test_cli_all_commands() -> Result<()> {
        // Test that all commands can be parsed
        let commands = vec![
            vec!["agpm", "init"],
            vec!["agpm", "add", "source", "test", "https://github.com/test/repo.git"],
            vec!["agpm", "install"],
            vec!["agpm", "update"],
            vec!["agpm", "list"],
            vec!["agpm", "validate"],
            vec!["agpm", "cache", "info"],
            vec!["agpm", "config", "show"],
        ];

        for cmd in commands {
            Cli::try_parse_from(cmd)?;
        }
        Ok(())
    }

    #[tokio::test]
    async fn test_cli_execute_with_flags() -> Result<()> {
        use crate::cli::CliConfig;
        use tempfile::TempDir;

        // This test verifies that CLI commands execute successfully with various flags
        // We test using config injection to avoid modifying global environment variables

        let temp_dir = TempDir::new()?;
        let temp_path = temp_dir.path().canonicalize()?;

        // Create the manifest
        let manifest_path = temp_path.join("agpm.toml");
        std::fs::write(&manifest_path, "[sources]\n")?;

        // Verify the file exists
        assert!(manifest_path.exists(), "Manifest file was not created");

        // Test that verbose flag creates correct config and executes successfully
        // Use config path command which doesn't need a manifest in current dir
        let cli = Cli::try_parse_from(["agpm", "--verbose", "config", "path"])?;
        assert!(cli.verbose);
        let config = cli.build_config();
        assert_eq!(config.log_level, Some("debug".to_string()));
        assert!(!config.no_progress);

        // Use a test config that doesn't modify environment
        let test_config = CliConfig::new(); // Empty config for testing
        cli.execute_with_config(test_config).await?;

        // Test that quiet flag creates correct config
        let cli = Cli::try_parse_from(["agpm", "--quiet", "config", "path"])?;
        assert!(cli.quiet);
        let config = cli.build_config();
        assert_eq!(config.log_level, None);

        let test_config = CliConfig::new();
        cli.execute_with_config(test_config).await?;

        // Test that no-progress flag creates correct config
        let cli = Cli::try_parse_from(["agpm", "--no-progress", "config", "path"])?;
        assert!(cli.no_progress);
        let config = cli.build_config();
        assert!(config.no_progress);
        assert_eq!(config.log_level, Some("info".to_string()));

        let test_config = CliConfig::new();
        cli.execute_with_config(test_config).await?;

        // Test combined flags
        let cli = Cli::try_parse_from(["agpm", "--verbose", "--no-progress", "config", "path"])?;
        assert!(cli.verbose);
        assert!(cli.no_progress);
        let config = cli.build_config();
        assert_eq!(config.log_level, Some("debug".to_string()));
        assert!(config.no_progress);

        let test_config = CliConfig::new();
        cli.execute_with_config(test_config).await?;
        Ok(())
    }

    #[test]
    fn test_cli_config_builder() -> Result<()> {
        // Test verbose flag sets debug log level
        let cli = Cli::try_parse_from(["agpm", "--verbose", "list"])?;
        let config = cli.build_config();
        assert_eq!(config.log_level, Some("debug".to_string()));
        assert!(!config.no_progress);

        // Test quiet flag sets no log level
        let cli = Cli::try_parse_from(["agpm", "--quiet", "list"])?;
        let config = cli.build_config();
        assert_eq!(config.log_level, None);

        // Test default sets info log level
        let cli = Cli::try_parse_from(["agpm", "list"])?;
        let config = cli.build_config();
        assert_eq!(config.log_level, Some("info".to_string()));

        // Test no-progress flag
        let cli = Cli::try_parse_from(["agpm", "--no-progress", "list"])?;
        let config = cli.build_config();
        assert!(config.no_progress);

        // Test config path
        let cli = Cli::try_parse_from(["agpm", "--config", "/custom/path", "list"])?;
        let config = cli.build_config();
        assert_eq!(config.config_path, Some("/custom/path".to_string()));
        Ok(())
    }

    #[tokio::test]
    async fn test_cli_execute_all_commands() -> Result<()> {
        use crate::cli::CliConfig;
        use tempfile::TempDir;

        // In coverage/CI environments, current dir might not exist, so set a safe one first

        let temp_dir = TempDir::new()?;
        let temp_path = temp_dir.path().canonicalize()?;

        // Create the manifest in temp directory
        let manifest_path = temp_path.join("agpm.toml");
        std::fs::write(&manifest_path, "[sources]\n")?;

        // Verify the file exists
        assert!(manifest_path.exists(), "Manifest file was not created");

        // Use a test config that doesn't modify environment for all commands
        let test_config = CliConfig::new();

        // Test each command execution path - use manifest-path parameter for all
        let cli = Cli::try_parse_from([
            "agpm",
            "--manifest-path",
            manifest_path.to_str().unwrap(),
            "list",
        ])?;
        cli.execute_with_config(test_config.clone()).await?;

        // Verify the manifest still exists
        assert!(manifest_path.exists(), "Manifest disappeared after list");

        let cli = Cli::try_parse_from([
            "agpm",
            "--manifest-path",
            manifest_path.to_str().unwrap(),
            "validate",
        ])?;
        cli.execute_with_config(test_config.clone()).await?;

        let cli = Cli::try_parse_from([
            "agpm",
            "--manifest-path",
            manifest_path.to_str().unwrap(),
            "cache",
            "info",
        ])?;
        cli.execute_with_config(test_config.clone()).await?;

        // Skip config commands that modify global state
        // These would create side effects that affect other tests

        // Test install
        let cli = Cli::try_parse_from([
            "agpm",
            "--manifest-path",
            manifest_path.to_str().unwrap(),
            "install",
        ])?;
        cli.execute_with_config(test_config.clone()).await?;

        // Test update
        let cli = Cli::try_parse_from([
            "agpm",
            "--manifest-path",
            manifest_path.to_str().unwrap(),
            "update",
        ])?;
        cli.execute_with_config(test_config.clone()).await?;

        // Test add source
        let cli = Cli::try_parse_from([
            "agpm",
            "--manifest-path",
            manifest_path.to_str().unwrap(),
            "add",
            "source",
            "test",
            "https://github.com/test/repo.git",
        ])?;
        cli.execute_with_config(test_config.clone()).await?;
        Ok(())
    }

    #[tokio::test]
    async fn test_cli_execute_method() -> Result<()> {
        // Test with config path command which doesn't require a manifest
        let cli = Cli::try_parse_from(["agpm", "config", "path"])?;

        // This tests the execute method path (lines 582-584)
        cli.execute().await?;
        Ok(())
    }

    #[tokio::test]
    async fn test_cli_execute_init_command() -> Result<()> {
        use tempfile::TempDir;

        let temp_dir = TempDir::new()?;
        let temp_path = temp_dir.path().to_path_buf();

        // Test Init command execution (line 692)
        // Parse the CLI to properly create the command with path option
        let cli = Cli::try_parse_from(["agpm", "init", "--path", temp_path.to_str().unwrap()])?;

        cli.execute().await?;

        // Verify the manifest was created
        let manifest_path = temp_path.join("agpm.toml");
        assert!(manifest_path.exists());
        Ok(())
    }

    #[tokio::test]
    async fn test_cli_execute_cache_command() -> Result<()> {
        use tempfile::TempDir;

        let temp_dir = TempDir::new()?;
        let temp_path = temp_dir.path().canonicalize()?;

        // Create a manifest for cache operations
        std::fs::write(temp_path.join("agpm.toml"), "[sources]\n")?;

        // Test Cache command execution (line 698)
        // Parse the CLI to properly create the command with default subcommand
        let cli = Cli::try_parse_from(["agpm", "cache", "info"])?;

        cli.execute().await?;
        Ok(())
    }

    #[tokio::test]
    async fn test_cli_execute_remove_command() -> Result<()> {
        use tempfile::TempDir;

        let temp_dir = TempDir::new()?;
        let temp_path = temp_dir.path().to_path_buf();

        // Change to temp directory

        // Create a manifest with a source to remove
        std::fs::write(
            temp_path.join("agpm.toml"),
            r#"[sources]
test-source = "https://github.com/test/repo.git"

[agents]
[snippets]
[commands]
[mcp-servers]
"#,
        )?;

        // Test Remove command execution - try to remove non-existent source
        let cli = Cli::try_parse_from(["agpm", "remove", "source", "nonexistent"])?;

        let result = cli.execute().await;

        // Should fail because source doesn't exist
        assert!(result.is_err(), "Remove command should fail for non-existent source");
        Ok(())
    }

    #[tokio::test]
    async fn test_cli_execute_config_command() -> Result<()> {
        use tempfile::TempDir;

        let _temp_dir = TempDir::new()?;

        // Test Config command execution (line 699)
        // Config path command doesn't need a manifest
        let cli = Cli::try_parse_from(["agpm", "config", "path"])?;

        cli.execute().await?;
        Ok(())
    }

    #[test]
    fn test_cli_global_flags_work_with_all_commands() -> Result<()> {
        let commands = vec!["init", "install", "update", "list", "validate"];
        let flags = vec!["--verbose", "--quiet", "--no-progress"];

        for cmd in &commands {
            for flag in &flags {
                Cli::try_parse_from(["agpm", flag, cmd])?;
            }
        }
        Ok(())
    }

    // Individual command parsing tests removed - commands don't directly implement Parser trait
    // They are tested through the main CLI interface and integration tests
}

// Update command tests removed - fields are private

#[cfg(test)]
mod cli_execution_tests {
    // List command test removed - fields are private

    // Info command test removed - fields are private

    // Validate and Init command tests removed - fields are private and don't match struct definitions
}