algocline_app/service/
pkg_unlink.rs1use super::alc_toml::validate_package_name;
6use super::resolve::packages_dir;
7use super::AppService;
8
9impl AppService {
10 pub async fn pkg_unlink(&self, name: String) -> Result<String, String> {
16 validate_package_name(&name)?;
17
18 let pkgs = packages_dir()?;
19 let dest = pkgs.join(&name);
20
21 match dest.symlink_metadata() {
23 Ok(m) => {
24 if m.file_type().is_symlink() {
25 std::fs::remove_file(&dest)
26 .map_err(|e| format!("Failed to remove symlink {}: {e}", dest.display()))?;
27 } else {
28 return Err(format!(
29 "Package '{}' is not a symlink. Use pkg_remove to remove installed packages.",
30 name
31 ));
32 }
33 }
34 Err(_) => {
35 return Err(format!("Package '{name}' is not installed"));
36 }
37 }
38
39 Ok(serde_json::json!({ "unlinked": name }).to_string())
40 }
41}
42
43#[cfg(all(test, unix))]
46mod tests {
47 use std::os::unix::fs::symlink;
48
49 use crate::service::test_support::{make_app_service, FakeHome};
50
51 #[tokio::test]
52 async fn pkg_unlink_removes_symlink() {
53 let env = FakeHome::new();
54 let home = &env.home;
55
56 let pkgs = home.join(".algocline").join("packages");
57 std::fs::create_dir_all(&pkgs).unwrap();
58
59 let target = home.join("my_pkg");
60 std::fs::create_dir_all(&target).unwrap();
61 let dest = pkgs.join("my_pkg");
62 symlink(&target, &dest).unwrap();
63
64 let svc = make_app_service().await;
65 let result = svc.pkg_unlink("my_pkg".to_string()).await.unwrap();
66
67 let json: serde_json::Value = serde_json::from_str(&result).unwrap();
68 assert_eq!(json["unlinked"], "my_pkg");
69 assert!(dest.symlink_metadata().is_err());
70 }
71
72 #[tokio::test]
73 async fn pkg_unlink_real_dir_returns_error() {
74 let env = FakeHome::new();
75 let home = &env.home;
76
77 let pkgs = home.join(".algocline").join("packages");
78 let dest = pkgs.join("my_pkg");
79 std::fs::create_dir_all(&dest).unwrap();
80
81 let svc = make_app_service().await;
82 let err = svc.pkg_unlink("my_pkg".to_string()).await.unwrap_err();
83
84 assert!(err.contains("not a symlink"), "got: {err}");
85 }
86
87 #[tokio::test]
88 async fn pkg_unlink_not_installed_returns_error() {
89 let env = FakeHome::new();
90 let home = &env.home;
91
92 let pkgs = home.join(".algocline").join("packages");
93 std::fs::create_dir_all(&pkgs).unwrap();
94
95 let svc = make_app_service().await;
96 let err = svc.pkg_unlink("nonexistent".to_string()).await.unwrap_err();
97
98 assert!(err.contains("not installed"), "got: {err}");
99 }
100
101 #[tokio::test]
102 async fn pkg_unlink_dangling_symlink_removed() {
103 let env = FakeHome::new();
104 let home = &env.home;
105
106 let pkgs = home.join(".algocline").join("packages");
107 std::fs::create_dir_all(&pkgs).unwrap();
108
109 let dest = pkgs.join("dangling_pkg");
110 symlink(home.join("nowhere"), &dest).unwrap();
111 assert!(!dest.exists()); let svc = make_app_service().await;
114 let result = svc.pkg_unlink("dangling_pkg".to_string()).await.unwrap();
115
116 let json: serde_json::Value = serde_json::from_str(&result).unwrap();
117 assert_eq!(json["unlinked"], "dangling_pkg");
118 assert!(dest.symlink_metadata().is_err());
119 }
120}