foundry_mcp/cli/commands/
uninstall.rs1use crate::cli::args::UninstallArgs;
4use crate::core::installation;
5use crate::types::responses::{InstallationStatus, UninstallResponse};
6use crate::utils::formatting::format_uninstall_output;
7use anyhow::{Context, Result};
8
9pub async fn execute(args: UninstallArgs) -> Result<String> {
10 validate_target(&args.target)?;
12
13 let result = match args.target.as_str() {
15 "claude-code" => installation::uninstall_from_claude_code()
16 .await
17 .context("Failed to uninstall from Claude Code")?,
18 "cursor" => installation::uninstall_from_cursor(args.remove_config)
19 .await
20 .context("Failed to uninstall from Cursor")?,
21
22 _ => {
23 return Err(anyhow::anyhow!(
24 "Unsupported uninstallation target: {}. Supported targets: claude-code, cursor",
25 args.target
26 ));
27 }
28 };
29
30 let response_data = UninstallResponse {
32 target: args.target.clone(),
33 config_path: result.config_path.clone(),
34 uninstallation_status: if result.success {
35 InstallationStatus::Success
36 } else {
37 InstallationStatus::Partial
38 },
39 actions_taken: result.actions_taken.clone(),
40 files_removed: result.files_removed.clone(),
41 };
42
43 if args.json {
45 Ok(serde_json::to_string_pretty(&response_data)?)
47 } else {
48 Ok(format_uninstall_output(
50 &args.target,
51 &result.config_path,
52 result.success,
53 &result.actions_taken,
54 &result.files_removed,
55 ))
56 }
57}
58
59fn validate_target(target: &str) -> Result<()> {
61 match target {
62 "claude-code" | "cursor" => Ok(()),
63 _ => Err(anyhow::anyhow!(
64 "Unsupported uninstallation target: {}. Supported targets: claude-code, cursor",
65 target
66 )),
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use crate::core::installation::create_uninstallation_result;
74
75 #[test]
76 fn test_validate_target_valid() {
77 let valid_targets = vec!["claude-code", "cursor"];
78
79 for target in valid_targets {
80 assert!(
81 validate_target(target).is_ok(),
82 "Target '{}' should be valid",
83 target
84 );
85 }
86 }
87
88 #[test]
89 fn test_validate_target_invalid() {
90 let invalid_targets = vec!["", "vscode", "claude-desktop"];
91
92 for target in invalid_targets {
93 assert!(
94 validate_target(target).is_err(),
95 "Target '{}' should be invalid",
96 target
97 );
98 }
99 }
100
101 #[test]
102 fn test_execute_invalid_target() {
103 let args = UninstallArgs {
104 target: "invalid-target".to_string(),
105 remove_config: false,
106 json: false,
107 };
108
109 let validation_result = validate_target(&args.target);
111 assert!(validation_result.is_err());
112 assert!(
113 validation_result
114 .unwrap_err()
115 .to_string()
116 .contains("Unsupported uninstallation target")
117 );
118 }
119
120 #[test]
121 fn test_uninstall_args_creation() {
122 let args = UninstallArgs {
123 target: "claude-code".to_string(),
124 remove_config: true,
125 json: false,
126 };
127
128 assert_eq!(args.target, "claude-code");
129 assert!(args.remove_config);
130 }
131
132 #[test]
133 fn test_uninstall_args_default_values() {
134 let args = UninstallArgs {
135 target: "cursor".to_string(),
136 remove_config: false,
137 json: false,
138 };
139
140 assert_eq!(args.target, "cursor");
141 assert!(!args.remove_config);
142 }
143
144 #[test]
145 fn test_create_uninstallation_result() {
146 let result = create_uninstallation_result(
147 true,
148 "/path/to/config.json".to_string(),
149 vec!["Action 1".to_string(), "Action 2".to_string()],
150 vec!["file1.json".to_string(), "file2.json".to_string()],
151 );
152
153 assert!(result.success);
154 assert_eq!(result.config_path, "/path/to/config.json");
155 assert_eq!(result.actions_taken.len(), 2);
156 assert_eq!(result.files_removed.len(), 2);
157 assert_eq!(result.actions_taken[0], "Action 1");
158 assert_eq!(result.files_removed[0], "file1.json");
159 }
160
161 #[test]
162 fn test_uninstall_args_with_config_removal() {
163 let args = UninstallArgs {
164 target: "cursor".to_string(),
165 remove_config: true,
166 json: false,
167 };
168
169 assert_eq!(args.target, "cursor");
170 assert!(args.remove_config);
171 }
172}