zoom_cli/commands/
users.rs1use crate::api::{ApiError, ZoomClient};
2use crate::output::{self, OutputConfig};
3
4pub async fn list(
5 client: &mut ZoomClient,
6 out: &OutputConfig,
7 status: Option<&str>,
8) -> Result<(), ApiError> {
9 let result = client.list_users(status).await?;
10
11 if out.json {
12 out.print_data(&serde_json::to_string_pretty(&result).expect("serialize"));
13 } else {
14 if result.users.is_empty() {
15 out.print_message("No users found.");
16 return Ok(());
17 }
18 let rows: Vec<Vec<String>> = result
19 .users
20 .iter()
21 .map(|u| {
22 vec![
23 u.id.clone(),
24 u.email.clone(),
25 u.display_name.clone().unwrap_or_default(),
26 u.status.clone().unwrap_or_default(),
27 ]
28 })
29 .collect();
30 out.print_data(&output::table(&["ID", "EMAIL", "NAME", "STATUS"], &rows));
31 if let Some(total) = result.total_records {
32 out.print_message(&format!("{total} user(s) total"));
33 }
34 }
35 Ok(())
36}
37
38pub async fn get(
39 client: &mut ZoomClient,
40 out: &OutputConfig,
41 id_or_email: &str,
42) -> Result<(), ApiError> {
43 let user = client.get_user(id_or_email).await?;
44
45 if out.json {
46 out.print_data(&serde_json::to_string_pretty(&user).expect("serialize"));
47 } else {
48 let name = user
49 .display_name
50 .clone()
51 .or_else(|| match (&user.first_name, &user.last_name) {
52 (Some(f), Some(l)) => Some(format!("{f} {l}")),
53 (Some(f), None) => Some(f.clone()),
54 _ => None,
55 })
56 .unwrap_or_default();
57 out.print_data(&output::kv_block(&[
58 ("id", user.id.clone()),
59 ("email", user.email.clone()),
60 ("name", name),
61 ("status", user.status.clone().unwrap_or_default()),
62 ]));
63 }
64 Ok(())
65}
66
67pub async fn me(client: &mut ZoomClient, out: &OutputConfig) -> Result<(), ApiError> {
68 get(client, out, "me").await
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use crate::api::ZoomClient;
75 use wiremock::matchers::{method, path};
76 use wiremock::{Mock, MockServer, ResponseTemplate};
77
78 fn test_out_json() -> OutputConfig {
79 OutputConfig {
80 json: true,
81 quiet: true,
82 }
83 }
84
85 #[tokio::test]
86 async fn users_list_json_includes_total_records() {
87 let server = MockServer::start().await;
88 Mock::given(method("GET"))
89 .and(path("/v2/users"))
90 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
91 "users": [
92 {"id": "u1", "email": "a@test.com", "display_name": "Alice", "status": "active"}
93 ],
94 "total_records": 1
95 })))
96 .mount(&server)
97 .await;
98
99 let mut client =
100 ZoomClient::new_for_test(format!("{}/v2", server.uri()), server.uri(), "tok".into());
101 list(&mut client, &test_out_json(), None).await.unwrap();
102 }
103
104 #[tokio::test]
105 async fn users_get_returns_not_found_for_unknown_user() {
106 let server = MockServer::start().await;
107 Mock::given(method("GET"))
108 .and(path("/v2/users/nobody@example.com"))
109 .respond_with(ResponseTemplate::new(404).set_body_string("User not found"))
110 .mount(&server)
111 .await;
112
113 let mut client =
114 ZoomClient::new_for_test(format!("{}/v2", server.uri()), server.uri(), "tok".into());
115 let err = get(&mut client, &test_out_json(), "nobody@example.com")
116 .await
117 .unwrap_err();
118 assert!(matches!(err, ApiError::NotFound(_)));
119 }
120
121 #[tokio::test]
122 async fn users_me_delegates_to_get_with_me() {
123 let server = MockServer::start().await;
124 Mock::given(method("GET"))
125 .and(path("/v2/users/me"))
126 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
127 "id": "self-user", "email": "me@example.com", "display_name": "Me"
128 })))
129 .mount(&server)
130 .await;
131
132 let mut client =
133 ZoomClient::new_for_test(format!("{}/v2", server.uri()), server.uri(), "tok".into());
134 me(&mut client, &test_out_json()).await.unwrap();
135 }
136}