1use crate::commands::{Command, OutputFormat};
6use crate::constants::api::actions;
7use crate::errors::prelude::{CliError, Result as CliResult};
8use crate::output::{CliResponse, OutputFormatter};
9use crate::utils::output::print_success_result;
10use crate::utils::{
11 validate_chat_about, validate_chat_action, validate_chat_id, validate_chat_title,
12 validate_cursor,
13};
14use async_trait::async_trait;
15use clap::{Subcommand, ValueHint};
16use serde_json::json;
17use tracing::{debug, info};
18use vkteams_bot::prelude::*;
19
20#[derive(Subcommand, Debug, Clone)]
22pub enum ChatCommands {
23 GetChatInfo {
25 #[arg(short = 'c', long, required = true, value_name = "CHAT_ID", value_hint = ValueHint::Username)]
26 chat_id: String,
27 },
28 GetProfile {
30 #[arg(short = 'u', long, required = true, value_name = "USER_ID", value_hint = ValueHint::Username)]
31 user_id: String,
32 },
33 GetChatMembers {
35 #[arg(short = 'c', long, required = true, value_name = "CHAT_ID", value_hint = ValueHint::Username)]
36 chat_id: String,
37 #[arg(long, value_name = "CURSOR")]
38 cursor: Option<String>,
39 },
40 SetChatTitle {
42 #[arg(short = 'c', long, required = true, value_name = "CHAT_ID", value_hint = ValueHint::Username)]
43 chat_id: String,
44 #[arg(short = 't', long, required = true, value_name = "TITLE")]
45 title: String,
46 },
47 SetChatAbout {
49 #[arg(short = 'c', long, required = true, value_name = "CHAT_ID", value_hint = ValueHint::Username)]
50 chat_id: String,
51 #[arg(short = 'a', long, required = true, value_name = "ABOUT")]
52 about: String,
53 },
54 SendAction {
56 #[arg(short = 'c', long, required = true, value_name = "CHAT_ID", value_hint = ValueHint::Username)]
57 chat_id: String,
58 #[arg(short = 'a', long, required = true, value_name = "ACTION")]
59 action: String,
60 },
61 GetChatAdmins {
63 #[arg(short = 'c', long, required = true, value_name = "CHAT_ID", value_hint = ValueHint::Username)]
64 chat_id: String,
65 },
66}
67
68#[async_trait]
69impl Command for ChatCommands {
70 async fn execute(&self, bot: &Bot) -> CliResult<()> {
71 match self {
72 ChatCommands::GetChatInfo { chat_id } => execute_get_chat_info(bot, chat_id).await,
73 ChatCommands::GetProfile { user_id } => execute_get_profile(bot, user_id).await,
74 ChatCommands::GetChatMembers { chat_id, cursor } => {
75 execute_get_chat_members(bot, chat_id, cursor.as_deref()).await
76 }
77 ChatCommands::SetChatTitle { chat_id, title } => {
78 execute_set_chat_title(bot, chat_id, title).await
79 }
80 ChatCommands::SetChatAbout { chat_id, about } => {
81 execute_set_chat_about(bot, chat_id, about).await
82 }
83 ChatCommands::SendAction { chat_id, action } => {
84 execute_send_action(bot, chat_id, action).await
85 }
86 ChatCommands::GetChatAdmins { chat_id } => execute_get_chat_admins(bot, chat_id).await,
87 }
88 }
89
90 async fn execute_with_output(&self, bot: &Bot, output_format: &OutputFormat) -> CliResult<()> {
92 let response = match self {
93 ChatCommands::GetChatInfo { chat_id } => {
94 execute_get_chat_info_structured(bot, chat_id).await
95 }
96 ChatCommands::GetProfile { user_id } => {
97 execute_get_profile_structured(bot, user_id).await
98 }
99 ChatCommands::GetChatMembers { chat_id, cursor } => {
100 execute_get_chat_members_structured(bot, chat_id, cursor.as_deref()).await
101 }
102 ChatCommands::SetChatTitle { chat_id, title } => {
103 execute_set_chat_title_structured(bot, chat_id, title).await
104 }
105 ChatCommands::SetChatAbout { chat_id, about } => {
106 execute_set_chat_about_structured(bot, chat_id, about).await
107 }
108 ChatCommands::SendAction { chat_id, action } => {
109 execute_send_action_structured(bot, chat_id, action).await
110 }
111 ChatCommands::GetChatAdmins { chat_id } => {
112 execute_get_chat_admins_structured(bot, chat_id).await
113 }
114 };
115
116 OutputFormatter::print(&response, output_format)?;
117
118 if !response.success {
119 return Err(CliError::UnexpectedError("Command failed".to_string()));
120 }
121
122 Ok(())
123 }
124
125 fn name(&self) -> &'static str {
126 match self {
127 ChatCommands::GetChatInfo { .. } => "get-chat-info",
128 ChatCommands::GetProfile { .. } => "get-profile",
129 ChatCommands::GetChatMembers { .. } => "get-chat-members",
130 ChatCommands::SetChatTitle { .. } => "set-chat-title",
131 ChatCommands::SetChatAbout { .. } => "set-chat-about",
132 ChatCommands::SendAction { .. } => "send-action",
133 ChatCommands::GetChatAdmins { .. } => "get-chat-admins",
134 }
135 }
136
137 fn validate(&self) -> CliResult<()> {
138 match self {
139 ChatCommands::GetChatInfo { chat_id }
140 | ChatCommands::GetProfile { user_id: chat_id } => {
141 validate_chat_id(chat_id)?;
142 }
143 ChatCommands::GetChatMembers { chat_id, cursor } => {
144 validate_chat_id(chat_id)?;
145 if let Some(cursor_val) = cursor {
146 validate_cursor(cursor_val)?;
147 }
148 }
149 ChatCommands::SetChatTitle { chat_id, title } => {
150 validate_chat_id(chat_id)?;
151 validate_chat_title(title)?;
152 }
153 ChatCommands::SetChatAbout { chat_id, about } => {
154 validate_chat_id(chat_id)?;
155 validate_chat_about(about)?;
156 }
157 ChatCommands::SendAction { chat_id, action } => {
158 validate_chat_id(chat_id)?;
159 validate_chat_action(action)?;
160 }
161 ChatCommands::GetChatAdmins { chat_id } => {
162 validate_chat_id(chat_id)?;
163 }
164 }
165 Ok(())
166 }
167}
168
169async fn execute_get_chat_info_structured(
173 bot: &Bot,
174 chat_id: &str,
175) -> CliResponse<serde_json::Value> {
176 debug!("Getting chat info for {}", chat_id);
177
178 let request = RequestChatsGetInfo::new(ChatId::from_borrowed_str(chat_id));
179 match bot.send_api_request(request).await {
180 Ok(result) => {
181 info!("Successfully retrieved chat info for {}", chat_id);
182 let data = json!({
183 "chat_id": chat_id,
184 "chat_info": result
185 });
186 CliResponse::success("get-chat-info", data)
187 }
188 Err(e) => CliResponse::error("get-chat-info", format!("Failed to get chat info: {e}")),
189 }
190}
191
192async fn execute_get_profile_structured(
193 bot: &Bot,
194 user_id: &str,
195) -> CliResponse<serde_json::Value> {
196 debug!("Getting profile for user {}", user_id);
197
198 let request = RequestChatsGetInfo::new(ChatId::from_borrowed_str(user_id));
199 match bot.send_api_request(request).await {
200 Ok(result) => {
201 info!("Successfully retrieved profile for user {}", user_id);
202 let data = json!({
203 "user_id": user_id,
204 "profile": result
205 });
206 CliResponse::success("get-profile", data)
207 }
208 Err(e) => CliResponse::error("get-profile", format!("Failed to get profile: {e}")),
209 }
210}
211
212async fn execute_get_chat_members_structured(
213 bot: &Bot,
214 chat_id: &str,
215 cursor: Option<&str>,
216) -> CliResponse<serde_json::Value> {
217 debug!("Getting chat members for {}", chat_id);
218
219 let mut request = RequestChatsGetMembers::new(ChatId::from_borrowed_str(chat_id));
220 if let Some(cursor_val) = cursor {
221 match cursor_val.parse::<u32>() {
222 Ok(cursor_num) => {
223 request = request.with_cursor(cursor_num);
224 }
225 Err(e) => {
226 return CliResponse::error(
227 "get-chat-members",
228 format!("Invalid cursor value, must be a number: {e}"),
229 );
230 }
231 }
232 }
233
234 match bot.send_api_request(request).await {
235 Ok(result) => {
236 info!("Successfully retrieved members for chat {}", chat_id);
237 let data = json!({
238 "chat_id": chat_id,
239 "cursor": cursor,
240 "members": result
241 });
242 CliResponse::success("get-chat-members", data)
243 }
244 Err(e) => CliResponse::error(
245 "get-chat-members",
246 format!("Failed to get chat members: {e}"),
247 ),
248 }
249}
250
251async fn execute_set_chat_title_structured(
252 bot: &Bot,
253 chat_id: &str,
254 title: &str,
255) -> CliResponse<serde_json::Value> {
256 debug!("Setting chat title for {} to {}", chat_id, title);
257
258 let request =
259 RequestChatsSetTitle::new((ChatId::from_borrowed_str(chat_id), title.to_string()));
260
261 match bot.send_api_request(request).await {
262 Ok(_result) => {
263 info!("Successfully set title for chat {}: {}", chat_id, title);
264 let data = json!({
265 "chat_id": chat_id,
266 "title": title,
267 "action": "title_updated"
268 });
269 CliResponse::success("set-chat-title", data)
270 }
271 Err(e) => CliResponse::error("set-chat-title", format!("Failed to set chat title: {e}")),
272 }
273}
274
275async fn execute_set_chat_about_structured(
276 bot: &Bot,
277 chat_id: &str,
278 about: &str,
279) -> CliResponse<serde_json::Value> {
280 debug!("Setting chat description for {} to {}", chat_id, about);
281
282 let request =
283 RequestChatsSetAbout::new((ChatId::from_borrowed_str(chat_id), about.to_string()));
284
285 match bot.send_api_request(request).await {
286 Ok(_result) => {
287 info!(
288 "Successfully set description for chat {}: {}",
289 chat_id, about
290 );
291 let data = json!({
292 "chat_id": chat_id,
293 "about": about,
294 "action": "about_updated"
295 });
296 CliResponse::success("set-chat-about", data)
297 }
298 Err(e) => CliResponse::error("set-chat-about", format!("Failed to set chat about: {e}")),
299 }
300}
301
302async fn execute_send_action_structured(
303 bot: &Bot,
304 chat_id: &str,
305 action: &str,
306) -> CliResponse<serde_json::Value> {
307 debug!("Sending {} action to chat {}", action, chat_id);
308
309 let chat_action = match action {
310 actions::TYPING => ChatActions::Typing,
311 actions::LOOKING => ChatActions::Looking,
312 _ => {
313 return CliResponse::error(
314 "send-action",
315 format!(
316 "Unknown action: {}. Available actions: {}, {}",
317 action,
318 actions::TYPING,
319 actions::LOOKING
320 ),
321 );
322 }
323 };
324
325 let request = RequestChatsSendAction::new((ChatId::from_borrowed_str(chat_id), chat_action));
326
327 match bot.send_api_request(request).await {
328 Ok(_result) => {
329 info!("Successfully sent {} action to chat {}", action, chat_id);
330 let data = json!({
331 "chat_id": chat_id,
332 "action": action,
333 "status": "sent"
334 });
335 CliResponse::success("send-action", data)
336 }
337 Err(e) => CliResponse::error("send-action", format!("Failed to send action: {e}")),
338 }
339}
340
341async fn execute_get_chat_admins_structured(
342 bot: &Bot,
343 chat_id: &str,
344) -> CliResponse<serde_json::Value> {
345 debug!("Getting chat administrators for {}", chat_id);
346
347 let request = RequestChatsGetAdmins::new(ChatId::from_borrowed_str(chat_id));
348 match bot.send_api_request(request).await {
349 Ok(result) => {
350 info!("Successfully retrieved administrators for chat {}", chat_id);
351 let data = json!({
352 "chat_id": chat_id,
353 "admins": result
354 });
355 CliResponse::success("get-chat-admins", data)
356 }
357 Err(e) => CliResponse::error("get-chat-admins", format!("Failed to get chat admins: {e}")),
358 }
359}
360
361async fn execute_get_chat_info(bot: &Bot, chat_id: &str) -> CliResult<()> {
363 debug!("Getting chat info for {}", chat_id);
364
365 let request = RequestChatsGetInfo::new(ChatId::from_borrowed_str(chat_id));
366 let result = bot
367 .send_api_request(request)
368 .await
369 .map_err(CliError::ApiError)?;
370
371 info!("Successfully retrieved chat info for {}", chat_id);
372 print_success_result(&result, &OutputFormat::Pretty)?;
373 Ok(())
374}
375
376async fn execute_get_profile(bot: &Bot, user_id: &str) -> CliResult<()> {
377 debug!("Getting profile for user {}", user_id);
378
379 let request = RequestChatsGetInfo::new(ChatId::from_borrowed_str(user_id));
380 let result = bot
381 .send_api_request(request)
382 .await
383 .map_err(CliError::ApiError)?;
384
385 info!("Successfully retrieved profile for user {}", user_id);
386 print_success_result(&result, &OutputFormat::Pretty)?;
387 Ok(())
388}
389
390async fn execute_get_chat_members(bot: &Bot, chat_id: &str, cursor: Option<&str>) -> CliResult<()> {
391 debug!("Getting chat members for {}", chat_id);
392
393 let mut request = RequestChatsGetMembers::new(ChatId::from_borrowed_str(chat_id));
394 if let Some(cursor_val) = cursor {
395 match cursor_val.parse::<u32>() {
396 Ok(cursor_num) => {
397 request = request.with_cursor(cursor_num);
398 }
399 Err(e) => {
400 return Err(CliError::InputError(format!(
401 "Invalid cursor value, must be a number: {e}"
402 )));
403 }
404 }
405 }
406
407 let result = bot
408 .send_api_request(request)
409 .await
410 .map_err(CliError::ApiError)?;
411
412 info!("Successfully retrieved members for chat {}", chat_id);
413 print_success_result(&result, &OutputFormat::Pretty)?;
414 Ok(())
415}
416
417async fn execute_set_chat_title(bot: &Bot, chat_id: &str, title: &str) -> CliResult<()> {
418 debug!("Setting chat title for {} to {}", chat_id, title);
419
420 let request =
421 RequestChatsSetTitle::new((ChatId::from_borrowed_str(chat_id), title.to_string()));
422
423 let result = bot
424 .send_api_request(request)
425 .await
426 .map_err(CliError::ApiError)?;
427
428 info!("Successfully set title for chat {}: {}", chat_id, title);
429 print_success_result(&result, &OutputFormat::Pretty)?;
430 Ok(())
431}
432
433async fn execute_set_chat_about(bot: &Bot, chat_id: &str, about: &str) -> CliResult<()> {
434 debug!("Setting chat description for {} to {}", chat_id, about);
435
436 let request =
437 RequestChatsSetAbout::new((ChatId::from_borrowed_str(chat_id), about.to_string()));
438
439 let result = bot
440 .send_api_request(request)
441 .await
442 .map_err(CliError::ApiError)?;
443
444 info!(
445 "Successfully set description for chat {}: {}",
446 chat_id, about
447 );
448 print_success_result(&result, &OutputFormat::Pretty)?;
449 Ok(())
450}
451
452async fn execute_send_action(bot: &Bot, chat_id: &str, action: &str) -> CliResult<()> {
453 debug!("Sending {} action to chat {}", action, chat_id);
454
455 let chat_action = match action {
456 actions::TYPING => ChatActions::Typing,
457 actions::LOOKING => ChatActions::Looking,
458 _ => {
459 return Err(CliError::InputError(format!(
460 "Unknown action: {}. Available actions: {}, {}",
461 action,
462 actions::TYPING,
463 actions::LOOKING
464 )));
465 }
466 };
467
468 let request = RequestChatsSendAction::new((ChatId::from_borrowed_str(chat_id), chat_action));
469
470 let result = bot
471 .send_api_request(request)
472 .await
473 .map_err(CliError::ApiError)?;
474
475 info!("Successfully sent {} action to chat {}", action, chat_id);
476 print_success_result(&result, &OutputFormat::Pretty)?;
477 Ok(())
478}
479
480async fn execute_get_chat_admins(bot: &Bot, chat_id: &str) -> CliResult<()> {
481 debug!("Getting chat administrators for {}", chat_id);
482
483 let request = RequestChatsGetAdmins::new(ChatId::from_borrowed_str(chat_id));
484 let result = bot
485 .send_api_request(request)
486 .await
487 .map_err(CliError::ApiError)?;
488
489 info!("Successfully retrieved administrators for chat {}", chat_id);
490 print_success_result(&result, &OutputFormat::Pretty)?;
491 Ok(())
492}
493
494#[cfg(test)]
497mod tests {
498 use super::*;
499 use tokio::runtime::Runtime;
500
501 #[test]
502 fn test_parse_chat_id_valid() {
503 let input = "12345@chat";
504 let res = validate_chat_id(input);
505 assert!(res.is_ok());
506 }
507
508 #[test]
509 fn test_parse_chat_id_invalid() {
510 let input = "user with spaces";
511 let res = validate_chat_id(input);
512 assert!(res.is_err());
513 }
514
515 #[test]
516 fn test_handle_empty_input() {
517 let input = "";
518 let res = validate_chat_id(input);
519 assert!(res.is_err());
520 }
521
522 #[test]
523 fn test_parse_cursor_valid() {
524 let input = "123";
525 let res = validate_cursor(input);
526 assert!(res.is_ok());
527 }
528
529 #[test]
530 fn test_parse_cursor_empty() {
531 let input = "";
532 let res = validate_cursor(input);
533 assert!(res.is_err());
534 }
535
536 #[test]
537 fn test_validate_set_chat_title_invalid() {
538 let cmd = ChatCommands::SetChatTitle {
539 chat_id: "12345@chat".to_string(),
540 title: "".to_string(),
541 };
542 let res = cmd.validate();
543 assert!(res.is_err());
544 }
545
546 #[test]
547 fn test_validate_set_chat_about_invalid() {
548 let cmd = ChatCommands::SetChatAbout {
549 chat_id: "12345@chat".to_string(),
550 about: "".to_string(),
551 };
552 let res = cmd.validate();
553 assert!(res.is_err());
554 }
555
556 #[test]
557 fn test_validate_send_action_invalid() {
558 let cmd = ChatCommands::SendAction {
559 chat_id: "12345@chat".to_string(),
560 action: "invalid_action".to_string(),
561 };
562 let res = cmd.validate();
563 assert!(res.is_err());
564 }
565
566 #[test]
567 fn test_validate_get_profile_invalid() {
568 let cmd = ChatCommands::GetProfile {
569 user_id: "".to_string(),
570 };
571 let res = cmd.validate();
572 assert!(res.is_err());
573 }
574
575 #[test]
576 fn test_validate_get_chat_members_invalid_cursor() {
577 let cmd = ChatCommands::GetChatMembers {
578 chat_id: "12345@chat".to_string(),
579 cursor: Some("not_a_number".to_string()),
580 };
581 let res = cmd.validate();
582 assert!(res.is_err()); }
584
585 #[test]
586 fn test_validate_get_chat_members_empty_cursor() {
587 let cmd = ChatCommands::GetChatMembers {
588 chat_id: "12345@chat".to_string(),
589 cursor: Some("".to_string()),
590 };
591 let res = cmd.validate();
592 assert!(res.is_err());
593 }
594
595 #[test]
597 fn test_validate_set_chat_title_long() {
598 let cmd = ChatCommands::SetChatTitle {
599 chat_id: "12345@chat".to_string(),
600 title: "a".repeat(300),
601 };
602 let res = cmd.validate();
603 assert!(res.is_err());
604 }
605
606 #[test]
607 fn test_validate_set_chat_about_long() {
608 let cmd = ChatCommands::SetChatAbout {
609 chat_id: "12345@chat".to_string(),
610 about: "a".repeat(300),
611 };
612 let res = cmd.validate();
613 assert!(res.is_ok()); }
615
616 fn dummy_bot() -> Bot {
617 Bot::with_params(&APIVersionUrl::V1, "dummy_token", "https://dummy.api.com").unwrap()
618 }
619
620 #[test]
621 fn test_execute_get_chat_info_api_error() {
622 let cmd = ChatCommands::GetChatInfo {
623 chat_id: "12345@chat".to_string(),
624 };
625 let bot = dummy_bot();
626 let rt = Runtime::new().unwrap();
627 let res = rt.block_on(cmd.execute(&bot));
628 assert!(res.is_err());
629 }
630
631 #[test]
632 fn test_execute_get_profile_api_error() {
633 let cmd = ChatCommands::GetProfile {
634 user_id: "user123".to_string(),
635 };
636 let bot = dummy_bot();
637 let rt = Runtime::new().unwrap();
638 let res = rt.block_on(cmd.execute(&bot));
639 assert!(res.is_err());
640 }
641
642 #[test]
643 fn test_execute_get_chat_members_api_error() {
644 let cmd = ChatCommands::GetChatMembers {
645 chat_id: "12345@chat".to_string(),
646 cursor: None,
647 };
648 let bot = dummy_bot();
649 let rt = Runtime::new().unwrap();
650 let res = rt.block_on(cmd.execute(&bot));
651 assert!(res.is_err());
652 }
653
654 #[test]
655 fn test_execute_set_chat_title_api_error() {
656 let cmd = ChatCommands::SetChatTitle {
657 chat_id: "12345@chat".to_string(),
658 title: "New Title".to_string(),
659 };
660 let bot = dummy_bot();
661 let rt = Runtime::new().unwrap();
662 let res = rt.block_on(cmd.execute(&bot));
663 assert!(res.is_err());
664 }
665
666 #[test]
667 fn test_execute_set_chat_about_api_error() {
668 let cmd = ChatCommands::SetChatAbout {
669 chat_id: "12345@chat".to_string(),
670 about: "About text".to_string(),
671 };
672 let bot = dummy_bot();
673 let rt = Runtime::new().unwrap();
674 let res = rt.block_on(cmd.execute(&bot));
675 assert!(res.is_err());
676 }
677
678 #[test]
679 fn test_execute_send_action_invalid_action() {
680 let cmd = ChatCommands::SendAction {
681 chat_id: "12345@chat".to_string(),
682 action: "invalid".to_string(),
683 };
684 let bot = dummy_bot();
685 let rt = Runtime::new().unwrap();
686 let res = rt.block_on(cmd.execute(&bot));
687 assert!(res.is_err());
688 let err = res.unwrap_err();
689 match err {
690 CliError::InputError(msg) => assert!(msg.contains("Unknown action")),
691 _ => panic!("Expected InputError for unknown action"),
692 }
693 }
694}
695
696#[cfg(test)]
697mod happy_path_tests {
698 use super::*;
699 use crate::utils::bot::create_dummy_bot;
700 use tokio_test::block_on;
701
702 #[test]
703 fn test_execute_get_chat_info_success() {
704 let bot = create_dummy_bot();
705 let res = block_on(execute_get_chat_info(&bot, "chat123"));
706 let _ = res;
708 }
709
710 #[test]
711 fn test_execute_get_profile_success() {
712 let bot = create_dummy_bot();
713 let res = block_on(execute_get_profile(&bot, "user123"));
714 let _ = res;
715 }
716
717 #[test]
718 fn test_execute_get_chat_members_success() {
719 let bot = create_dummy_bot();
720 let res = block_on(execute_get_chat_members(&bot, "chat123", Some("1")));
721 let _ = res;
722 }
723
724 #[test]
725 fn test_execute_set_chat_title_success() {
726 let bot = create_dummy_bot();
727 let res = block_on(execute_set_chat_title(&bot, "chat123", "New Title"));
728 let _ = res;
729 }
730
731 #[test]
732 fn test_execute_set_chat_about_success() {
733 let bot = create_dummy_bot();
734 let res = block_on(execute_set_chat_about(&bot, "chat123", "About"));
735 let _ = res;
736 }
737
738 #[test]
739 fn test_execute_send_action_success() {
740 let bot = create_dummy_bot();
741 let res = block_on(execute_send_action(&bot, "chat123", "typing"));
742 let _ = res;
743 }
744}