1use crate::api::types::{CreateUserInfo, CreateUserRequest};
2use crate::api::{ApiError, ZoomClient};
3use crate::output::{self, OutputConfig};
4
5pub async fn list(
6 client: &mut ZoomClient,
7 out: &OutputConfig,
8 status: Option<&str>,
9) -> Result<(), ApiError> {
10 let result = client.list_users(status).await?;
11
12 if out.json {
13 out.print_data(&serde_json::to_string_pretty(&result).expect("serialize"));
14 } else {
15 if result.users.is_empty() {
16 out.print_message("No users found.");
17 return Ok(());
18 }
19 let rows: Vec<Vec<String>> = result
20 .users
21 .iter()
22 .map(|u| {
23 vec![
24 u.id.clone(),
25 u.email.clone(),
26 u.display_name.clone().unwrap_or_default(),
27 u.status.clone().unwrap_or_default(),
28 ]
29 })
30 .collect();
31 out.print_data(&output::table(&["ID", "EMAIL", "NAME", "STATUS"], &rows));
32 if let Some(total) = result.total_records {
33 out.print_message(&format!("{total} user(s) total"));
34 }
35 }
36 Ok(())
37}
38
39pub async fn get(
40 client: &mut ZoomClient,
41 out: &OutputConfig,
42 id_or_email: &str,
43) -> Result<(), ApiError> {
44 let user = client.get_user(id_or_email).await?;
45
46 if out.json {
47 out.print_data(&serde_json::to_string_pretty(&user).expect("serialize"));
48 } else {
49 let name = user
50 .display_name
51 .clone()
52 .or_else(|| match (&user.first_name, &user.last_name) {
53 (Some(f), Some(l)) => Some(format!("{f} {l}")),
54 (Some(f), None) => Some(f.clone()),
55 _ => None,
56 })
57 .unwrap_or_default();
58 out.print_data(&output::kv_block(&[
59 ("id", user.id.clone()),
60 ("email", user.email.clone()),
61 ("name", name),
62 ("status", user.status.clone().unwrap_or_default()),
63 ]));
64 }
65 Ok(())
66}
67
68pub async fn me(client: &mut ZoomClient, out: &OutputConfig) -> Result<(), ApiError> {
69 get(client, out, "me").await
70}
71
72pub async fn create(
73 client: &mut ZoomClient,
74 out: &OutputConfig,
75 email: String,
76 first_name: Option<String>,
77 last_name: Option<String>,
78 user_type: u8,
79) -> Result<(), ApiError> {
80 let req = CreateUserRequest {
81 action: "create".into(),
82 user_info: CreateUserInfo {
83 email,
84 user_type,
85 first_name,
86 last_name,
87 },
88 };
89 let user = client.create_user(req).await?;
90 if out.json {
91 out.print_data(&serde_json::to_string_pretty(&user).expect("serialize"));
92 } else {
93 out.print_message(&format!("Created user: {} ({})", user.email, user.id));
94 }
95 Ok(())
96}
97
98pub async fn deactivate(
99 client: &mut ZoomClient,
100 out: &OutputConfig,
101 id_or_email: &str,
102) -> Result<(), ApiError> {
103 client.set_user_status(id_or_email, "deactivate").await?;
104 out.print_message(&format!("User {} deactivated.", id_or_email));
105 Ok(())
106}
107
108pub async fn activate(
109 client: &mut ZoomClient,
110 out: &OutputConfig,
111 id_or_email: &str,
112) -> Result<(), ApiError> {
113 client.set_user_status(id_or_email, "activate").await?;
114 out.print_message(&format!("User {} activated.", id_or_email));
115 Ok(())
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::api::ZoomClient;
122 use wiremock::matchers::{method, path};
123 use wiremock::{Mock, MockServer, ResponseTemplate};
124
125 fn test_out_json() -> OutputConfig {
126 OutputConfig::for_test()
127 }
128
129 #[tokio::test]
130 async fn users_list_json_includes_total_records() {
131 let server = MockServer::start().await;
132 Mock::given(method("GET"))
133 .and(path("/v2/users"))
134 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
135 "users": [
136 {"id": "u1", "email": "a@test.com", "display_name": "Alice", "status": "active"}
137 ],
138 "total_records": 1
139 })))
140 .mount(&server)
141 .await;
142
143 let mut client =
144 ZoomClient::new_for_test(format!("{}/v2", server.uri()), server.uri(), "tok".into());
145 list(&mut client, &test_out_json(), None).await.unwrap();
146 }
147
148 #[tokio::test]
149 async fn users_get_returns_not_found_for_unknown_user() {
150 let server = MockServer::start().await;
151 Mock::given(method("GET"))
152 .and(path("/v2/users/nobody@example.com"))
153 .respond_with(ResponseTemplate::new(404).set_body_string("User not found"))
154 .mount(&server)
155 .await;
156
157 let mut client =
158 ZoomClient::new_for_test(format!("{}/v2", server.uri()), server.uri(), "tok".into());
159 let err = get(&mut client, &test_out_json(), "nobody@example.com")
160 .await
161 .unwrap_err();
162 assert!(matches!(err, ApiError::NotFound(_)));
163 }
164
165 #[tokio::test]
166 async fn users_me_delegates_to_get_with_me() {
167 let server = MockServer::start().await;
168 Mock::given(method("GET"))
169 .and(path("/v2/users/me"))
170 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
171 "id": "self-user", "email": "me@example.com", "display_name": "Me"
172 })))
173 .mount(&server)
174 .await;
175
176 let mut client =
177 ZoomClient::new_for_test(format!("{}/v2", server.uri()), server.uri(), "tok".into());
178 me(&mut client, &test_out_json()).await.unwrap();
179 }
180
181 #[tokio::test]
182 async fn users_create_returns_created_user() {
183 let server = MockServer::start().await;
184 Mock::given(method("POST"))
185 .and(path("/v2/users"))
186 .and(wiremock::matchers::body_json(serde_json::json!({
187 "action": "create",
188 "user_info": {
189 "email": "jane@example.com",
190 "type": 1,
191 "first_name": "Jane",
192 "last_name": "Doe"
193 }
194 })))
195 .respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({
196 "id": "new-user-id",
197 "email": "jane@example.com",
198 "first_name": "Jane",
199 "last_name": "Doe",
200 "status": "active"
201 })))
202 .mount(&server)
203 .await;
204
205 let mut client =
206 ZoomClient::new_for_test(format!("{}/v2", server.uri()), server.uri(), "tok".into());
207 create(
208 &mut client,
209 &test_out_json(),
210 "jane@example.com".into(),
211 Some("Jane".into()),
212 Some("Doe".into()),
213 1,
214 )
215 .await
216 .unwrap();
217 }
218
219 #[tokio::test]
220 async fn users_deactivate_sends_correct_action() {
221 let server = MockServer::start().await;
222 Mock::given(method("PUT"))
223 .and(path("/v2/users/u1/status"))
224 .and(wiremock::matchers::body_json(
225 serde_json::json!({"action": "deactivate"}),
226 ))
227 .respond_with(ResponseTemplate::new(204))
228 .mount(&server)
229 .await;
230
231 let mut client =
232 ZoomClient::new_for_test(format!("{}/v2", server.uri()), server.uri(), "tok".into());
233 deactivate(&mut client, &test_out_json(), "u1")
234 .await
235 .unwrap();
236 }
237
238 #[tokio::test]
239 async fn users_activate_sends_correct_action() {
240 let server = MockServer::start().await;
241 Mock::given(method("PUT"))
242 .and(path("/v2/users/u1/status"))
243 .and(wiremock::matchers::body_json(
244 serde_json::json!({"action": "activate"}),
245 ))
246 .respond_with(ResponseTemplate::new(204))
247 .mount(&server)
248 .await;
249
250 let mut client =
251 ZoomClient::new_for_test(format!("{}/v2", server.uri()), server.uri(), "tok".into());
252 activate(&mut client, &test_out_json(), "u1").await.unwrap();
253 }
254}