foundry-mcp 0.7.1

A comprehensive CLI tool and MCP server for deterministic project management and AI coding assistant integration
Documentation
//! Implementation of the uninstall command

use crate::cli::args::UninstallArgs;
use crate::core::installation;
use crate::types::responses::{InstallationStatus, UninstallResponse};
use crate::utils::formatting::format_uninstall_output;
use anyhow::{Context, Result};

pub async fn execute(args: UninstallArgs) -> Result<String> {
    // Validate uninstallation target
    validate_target(&args.target)?;

    // Perform uninstallation based on target
    let result = match args.target.as_str() {
        "claude-code" => installation::uninstall_from_claude_code()
            .await
            .context("Failed to uninstall from Claude Code")?,
        "cursor" => installation::uninstall_from_cursor(args.remove_config)
            .await
            .context("Failed to uninstall from Cursor")?,

        _ => {
            return Err(anyhow::anyhow!(
                "Unsupported uninstallation target: {}. Supported targets: claude-code, cursor",
                args.target
            ));
        }
    };

    // Build response data for both JSON and human-readable output
    let response_data = UninstallResponse {
        target: args.target.clone(),
        config_path: result.config_path.clone(),
        uninstallation_status: if result.success {
            InstallationStatus::Success
        } else {
            InstallationStatus::Partial
        },
        actions_taken: result.actions_taken.clone(),
        files_removed: result.files_removed.clone(),
    };

    // Return formatted output based on --json flag
    if args.json {
        // Return JSON format - only include the data for clean CLI output
        Ok(serde_json::to_string_pretty(&response_data)?)
    } else {
        // Return human-readable format
        Ok(format_uninstall_output(
            &args.target,
            &result.config_path,
            result.success,
            &result.actions_taken,
            &result.files_removed,
        ))
    }
}

/// Validate the uninstallation target
fn validate_target(target: &str) -> Result<()> {
    match target {
        "claude-code" | "cursor" => Ok(()),
        _ => Err(anyhow::anyhow!(
            "Unsupported uninstallation target: {}. Supported targets: claude-code, cursor",
            target
        )),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::core::installation::create_uninstallation_result;

    #[test]
    fn test_validate_target_valid() {
        let valid_targets = vec!["claude-code", "cursor"];

        for target in valid_targets {
            assert!(
                validate_target(target).is_ok(),
                "Target '{}' should be valid",
                target
            );
        }
    }

    #[test]
    fn test_validate_target_invalid() {
        let invalid_targets = vec!["", "vscode", "claude-desktop"];

        for target in invalid_targets {
            assert!(
                validate_target(target).is_err(),
                "Target '{}' should be invalid",
                target
            );
        }
    }

    #[test]
    fn test_execute_invalid_target() {
        let args = UninstallArgs {
            target: "invalid-target".to_string(),
            remove_config: false,
            json: false,
        };

        // Test the validation logic without calling execute()
        let validation_result = validate_target(&args.target);
        assert!(validation_result.is_err());
        assert!(
            validation_result
                .unwrap_err()
                .to_string()
                .contains("Unsupported uninstallation target")
        );
    }

    #[test]
    fn test_uninstall_args_creation() {
        let args = UninstallArgs {
            target: "claude-code".to_string(),
            remove_config: true,
            json: false,
        };

        assert_eq!(args.target, "claude-code");
        assert!(args.remove_config);
    }

    #[test]
    fn test_uninstall_args_default_values() {
        let args = UninstallArgs {
            target: "cursor".to_string(),
            remove_config: false,
            json: false,
        };

        assert_eq!(args.target, "cursor");
        assert!(!args.remove_config);
    }

    #[test]
    fn test_create_uninstallation_result() {
        let result = create_uninstallation_result(
            true,
            "/path/to/config.json".to_string(),
            vec!["Action 1".to_string(), "Action 2".to_string()],
            vec!["file1.json".to_string(), "file2.json".to_string()],
        );

        assert!(result.success);
        assert_eq!(result.config_path, "/path/to/config.json");
        assert_eq!(result.actions_taken.len(), 2);
        assert_eq!(result.files_removed.len(), 2);
        assert_eq!(result.actions_taken[0], "Action 1");
        assert_eq!(result.files_removed[0], "file1.json");
    }

    #[test]
    fn test_uninstall_args_with_config_removal() {
        let args = UninstallArgs {
            target: "cursor".to_string(),
            remove_config: true,
            json: false,
        };

        assert_eq!(args.target, "cursor");
        assert!(args.remove_config);
    }
}