vkteams_bot_cli/commands/chat/
mod.rs

1//! Chat management commands module
2//!
3//! This module contains all commands related to chat management and information.
4
5use 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/// All chat management commands
21#[derive(Subcommand, Debug, Clone)]
22pub enum ChatCommands {
23    /// Get chat information
24    GetChatInfo {
25        #[arg(short = 'c', long, required = true, value_name = "CHAT_ID", value_hint = ValueHint::Username)]
26        chat_id: String,
27    },
28    /// Get user profile information
29    GetProfile {
30        #[arg(short = 'u', long, required = true, value_name = "USER_ID", value_hint = ValueHint::Username)]
31        user_id: String,
32    },
33    /// Get chat members
34    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    /// Set chat title
41    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    /// Set chat description
48    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    /// Send typing or looking action to chat
55    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    /// Get chat administrators
62    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    /// New method for structured output support
91    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
169// Command execution functions
170
171// Structured output versions
172async 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
361// Legacy output versions (for backward compatibility)
362async 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// Validation functions are now imported from utils/validation module
495
496#[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()); // cursor должен быть числом
583    }
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    // Property-based тесты для title/about/action (короткие и длинные строки)
596    #[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()); // валидация не ограничивает длину
614    }
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        // Dummy bot may return error or Ok, but function should not panic
707        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}