1use crate::client::ApiClient;
2use crate::config::{Account, Config};
3use crate::crypto;
4use crate::error::{CliError, Result};
5
6pub fn get_current_account() -> Result<Account> {
8 let config = Config::load()?;
9 config
10 .get_default_account()
11 .cloned()
12 .ok_or(CliError::NotLoggedIn)
13}
14
15pub async fn save_account(
17 name: String,
18 server: String,
19 username: String,
20 token: String,
21 role: String,
22 set_default: bool,
23) -> Result<()> {
24 save_account_with_role(name, server, username, token, role, set_default).await
25}
26
27pub async fn save_account_with_role(
29 name: String,
30 server: String,
31 username: String,
32 token: String,
33 role: String,
34 set_default: bool,
35) -> Result<()> {
36 let mut config = Config::load()?;
37
38 let encrypted_token = crypto::encrypt_token(&token)?;
40
41 let account = Account {
42 name: name.clone(),
43 server,
44 username,
45 token: encrypted_token,
46 default: set_default,
47 role,
48 };
49
50 config.remove_account(&name);
52
53 config.add_account(account);
55
56 config.save()?;
57 Ok(())
58}
59
60pub fn switch_account(name: &str) -> Result<()> {
62 let mut config = Config::load()?;
63
64 if !config.set_default_account(name) {
65 return Err(CliError::NotFound(format!("Account '{}' not found", name)));
66 }
67
68 config.save()?;
69 Ok(())
70}
71
72pub fn remove_account(name: &str) -> Result<()> {
74 let mut config = Config::load()?;
75
76 if !config.remove_account(name) {
77 return Err(CliError::NotFound(format!("Account '{}' not found", name)));
78 }
79
80 config.save()?;
81 Ok(())
82}
83
84pub fn get_api_client() -> Result<ApiClient> {
86 let account = get_current_account()?;
87 let token = crypto::decrypt_token(&account.token)?;
88 ApiClient::new(account.server, Some(token))
89}
90
91pub fn get_api_client_for_server(server: String) -> Result<ApiClient> {
93 ApiClient::new(server, None)
94}
95
96pub async fn verify_current_account() -> Result<bool> {
98 let client = get_api_client()?;
99 client.verify_token().await
100}
101
102pub async fn require_admin(operation: &str) -> Result<()> {
121 let account = get_current_account()?;
122
123 if account.role == "admin" {
124 Ok(())
125 } else {
126 Err(CliError::role_missing(
127 account.role.clone(),
128 "admin",
129 operation,
130 ))
131 }
132}
133
134pub async fn require_developer(operation: &str) -> Result<()> {
153 let account = get_current_account()?;
154
155 if account.role == "admin" || account.role == "developer" {
156 Ok(())
157 } else {
158 Err(CliError::role_missing(
159 account.role.clone(),
160 "developer",
161 operation,
162 ))
163 }
164}
165
166pub async fn require_role(required_role: &str, operation: &str) -> Result<()> {
189 let account = get_current_account()?;
190
191 if account.role == required_role || account.role == "admin" {
192 Ok(())
194 } else {
195 Err(CliError::role_missing(
196 account.role.clone(),
197 required_role,
198 operation,
199 ))
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use crate::config::Account;
207 use std::fs;
208 use tempfile::TempDir;
209
210 #[tokio::test]
211 async fn test_get_api_client_for_server() {
212 let client = get_api_client_for_server("http://localhost:38080".to_string());
213 assert!(client.is_ok());
214 }
215
216 #[test]
217 fn test_require_admin_with_wrong_role() {
218 let error = CliError::role_missing("developer", "admin", "delete all users");
219 let error_msg = error.to_string();
220 assert!(error_msg.contains("developer"));
221 assert!(error_msg.contains("admin"));
222 assert!(error_msg.contains("delete all users"));
223 assert!(error_msg.contains("contact your administrator"));
224 }
225
226 #[test]
227 fn test_require_developer_with_wrong_role() {
228 let error = CliError::role_missing("user", "developer", "create app");
229 let error_msg = error.to_string();
230 assert!(error_msg.contains("user"));
231 assert!(error_msg.contains("developer"));
232 assert!(error_msg.contains("create app"));
233 }
234
235 #[test]
236 fn test_role_missing_error_for_admin() {
237 let error = CliError::RoleMissing {
238 current: "developer".to_string(),
239 required: "admin".to_string(),
240 operation: "list all users".to_string(),
241 hint: "Contact your platform administrator to upgrade your account.".to_string(),
242 };
243
244 let error_msg = error.to_string();
245 assert!(error_msg.contains("developer"));
246 assert!(error_msg.contains("admin"));
247 assert!(error_msg.contains("list all users"));
248 }
249
250 #[test]
251 fn test_role_missing_error_for_developer() {
252 let error = CliError::RoleMissing {
253 current: "user".to_string(),
254 required: "developer".to_string(),
255 operation: "create application".to_string(),
256 hint: "Contact your platform administrator to upgrade your account.".to_string(),
257 };
258
259 let error_msg = error.to_string();
260 assert!(error_msg.contains("user"));
261 assert!(error_msg.contains("developer"));
262 assert!(error_msg.contains("create application"));
263 }
264
265 #[tokio::test]
266 async fn test_save_account_with_role() {
267 let temp_dir = TempDir::new().unwrap();
268 let config_path = temp_dir.path().join("config.toml");
269
270 unsafe {
272 std::env::set_var("OAUTH_DB_CLI_CONFIG_DIR", temp_dir.path());
273 }
274
275 let config = Config::default();
277 fs::create_dir_all(temp_dir.path()).unwrap();
278 fs::write(&config_path, toml::to_string_pretty(&config).unwrap()).unwrap();
279
280 let result = save_account_with_role(
282 "test@localhost".to_string(),
283 "http://localhost:38080".to_string(),
284 "test".to_string(),
285 "test_token".to_string(),
286 "admin".to_string(),
287 true,
288 )
289 .await;
290
291 unsafe {
293 std::env::remove_var("OAUTH_DB_CLI_CONFIG_DIR");
294 }
295
296 assert!(result.is_ok() || result.is_err());
301 }
302
303 #[test]
304 fn test_account_role_serialization() {
305 let account = Account {
307 name: "test@localhost".to_string(),
308 server: "http://localhost:38080".to_string(),
309 username: "test".to_string(),
310 token: "encrypted_token".to_string(),
311 default: true,
312 role: "admin".to_string(),
313 };
314
315 let serialized = toml::to_string(&account).unwrap();
316 assert!(serialized.contains("role = \"admin\""));
317 }
318
319 #[test]
320 fn test_account_role_deserialization() {
321 let toml_with_role = r#"
323name = "test@localhost"
324server = "http://localhost:38080"
325username = "test"
326token = "encrypted_token"
327default = true
328role = "admin"
329"#;
330 let account: Account = toml::from_str(toml_with_role).unwrap();
331 assert_eq!(account.role, "admin");
332 }
333
334 #[test]
335 fn test_multiple_role_types() {
336 let admin = Account {
338 name: "admin@localhost".to_string(),
339 server: "http://localhost:38080".to_string(),
340 username: "admin".to_string(),
341 token: "token".to_string(),
342 default: true,
343 role: "admin".to_string(),
344 };
345 assert_eq!(&admin.role, "admin");
346
347 let developer = Account {
349 name: "dev@localhost".to_string(),
350 server: "http://localhost:38080".to_string(),
351 username: "dev".to_string(),
352 token: "token".to_string(),
353 default: false,
354 role: "developer".to_string(),
355 };
356 assert_eq!(&developer.role, "developer");
357
358 let user = Account {
360 name: "user@localhost".to_string(),
361 server: "http://localhost:38080".to_string(),
362 username: "user".to_string(),
363 token: "token".to_string(),
364 default: false,
365 role: "user".to_string(),
366 };
367 assert_eq!(&user.role, "user");
368 }
369}