// Fake TDLib client for testing use std::collections::HashMap; use std::sync::{Arc, Mutex}; use tele_tui::tdlib::types::{FolderInfo, ReactionInfo}; use tele_tui::tdlib::{AuthState, ChatInfo, MessageInfo, NetworkState, ProfileInfo, ReplyInfo}; use tele_tui::types::{ChatId, MessageId, UserId}; use tokio::sync::mpsc; /// Update события от TDLib (упрощённая версия) #[derive(Debug, Clone)] pub enum TdUpdate { NewMessage { chat_id: ChatId, message: MessageInfo, }, MessageContent { chat_id: ChatId, message_id: MessageId, new_text: String, }, DeleteMessages { chat_id: ChatId, message_ids: Vec, }, ChatAction { chat_id: ChatId, user_id: UserId, action: String, }, MessageInteractionInfo { chat_id: ChatId, message_id: MessageId, reactions: Vec, }, ConnectionState { state: NetworkState, }, ChatReadOutbox { chat_id: ChatId, last_read_outbox_message_id: MessageId, }, ChatDraftMessage { chat_id: ChatId, draft_text: Option, }, } /// Упрощённый mock TDLib клиента для тестов pub struct FakeTdClient { // Данные pub chats: Arc>>, pub messages: Arc>>>, pub folders: Arc>>, pub user_names: Arc>>, pub profiles: Arc>>, pub drafts: Arc>>, pub available_reactions: Arc>>, // Состояние pub network_state: Arc>, pub typing_chat_id: Arc>>, pub current_chat_id: Arc>>, pub current_pinned_message: Arc>>, pub auth_state: Arc>, // История действий (для проверки в тестах) pub sent_messages: Arc>>, pub edited_messages: Arc>>, pub deleted_messages: Arc>>, pub forwarded_messages: Arc>>, pub searched_queries: Arc>>, pub viewed_messages: Arc)>>>, // (chat_id, message_ids) pub chat_actions: Arc>>, // (chat_id, action) pub pending_view_messages: Arc)>>>, // Очередь для отметки как прочитанные // Update channel для симуляции событий pub update_tx: Arc>>>, // Скачанные файлы (file_id -> local_path) pub downloaded_files: Arc>>, // Настройки поведения pub simulate_delays: bool, pub fail_next_operation: Arc>, } #[derive(Debug, Clone)] pub struct SentMessage { pub chat_id: i64, pub text: String, pub reply_to: Option, pub reply_info: Option, } #[derive(Debug, Clone)] pub struct EditedMessage { pub chat_id: i64, pub message_id: MessageId, pub new_text: String, } #[derive(Debug, Clone)] pub struct DeletedMessages { pub chat_id: i64, pub message_ids: Vec, pub revoke: bool, } #[derive(Debug, Clone)] pub struct ForwardedMessages { pub from_chat_id: i64, pub to_chat_id: i64, pub message_ids: Vec, } #[derive(Debug, Clone)] pub struct SearchQuery { pub chat_id: i64, pub query: String, pub results_count: usize, } impl Default for FakeTdClient { fn default() -> Self { Self::new() } } impl Clone for FakeTdClient { fn clone(&self) -> Self { Self { chats: Arc::clone(&self.chats), messages: Arc::clone(&self.messages), folders: Arc::clone(&self.folders), user_names: Arc::clone(&self.user_names), profiles: Arc::clone(&self.profiles), drafts: Arc::clone(&self.drafts), available_reactions: Arc::clone(&self.available_reactions), network_state: Arc::clone(&self.network_state), typing_chat_id: Arc::clone(&self.typing_chat_id), current_chat_id: Arc::clone(&self.current_chat_id), current_pinned_message: Arc::clone(&self.current_pinned_message), auth_state: Arc::clone(&self.auth_state), sent_messages: Arc::clone(&self.sent_messages), edited_messages: Arc::clone(&self.edited_messages), deleted_messages: Arc::clone(&self.deleted_messages), forwarded_messages: Arc::clone(&self.forwarded_messages), searched_queries: Arc::clone(&self.searched_queries), viewed_messages: Arc::clone(&self.viewed_messages), chat_actions: Arc::clone(&self.chat_actions), pending_view_messages: Arc::clone(&self.pending_view_messages), downloaded_files: Arc::clone(&self.downloaded_files), update_tx: Arc::clone(&self.update_tx), simulate_delays: self.simulate_delays, fail_next_operation: Arc::clone(&self.fail_next_operation), } } } impl FakeTdClient { pub fn new() -> Self { Self { chats: Arc::new(Mutex::new(vec![])), messages: Arc::new(Mutex::new(HashMap::new())), folders: Arc::new(Mutex::new(vec![FolderInfo { id: 0, name: "All".to_string() }])), user_names: Arc::new(Mutex::new(HashMap::new())), profiles: Arc::new(Mutex::new(HashMap::new())), drafts: Arc::new(Mutex::new(HashMap::new())), available_reactions: Arc::new(Mutex::new(vec![ "👍".to_string(), "❤️".to_string(), "😂".to_string(), "😮".to_string(), "😢".to_string(), "🙏".to_string(), "👏".to_string(), "🔥".to_string(), ])), network_state: Arc::new(Mutex::new(NetworkState::Ready)), typing_chat_id: Arc::new(Mutex::new(None)), current_chat_id: Arc::new(Mutex::new(None)), current_pinned_message: Arc::new(Mutex::new(None)), auth_state: Arc::new(Mutex::new(AuthState::Ready)), sent_messages: Arc::new(Mutex::new(vec![])), edited_messages: Arc::new(Mutex::new(vec![])), deleted_messages: Arc::new(Mutex::new(vec![])), forwarded_messages: Arc::new(Mutex::new(vec![])), searched_queries: Arc::new(Mutex::new(vec![])), viewed_messages: Arc::new(Mutex::new(vec![])), chat_actions: Arc::new(Mutex::new(vec![])), pending_view_messages: Arc::new(Mutex::new(vec![])), downloaded_files: Arc::new(Mutex::new(HashMap::new())), update_tx: Arc::new(Mutex::new(None)), simulate_delays: false, fail_next_operation: Arc::new(Mutex::new(false)), } } /// Создать update channel для получения событий pub fn with_update_channel(self) -> (Self, mpsc::UnboundedReceiver) { let (tx, rx) = mpsc::unbounded_channel(); *self.update_tx.lock().unwrap() = Some(tx); (self, rx) } /// Включить симуляцию задержек (как в реальном TDLib) pub fn with_delays(mut self) -> Self { self.simulate_delays = true; self } // ==================== Builder Methods ==================== /// Добавить чат pub fn with_chat(self, chat: ChatInfo) -> Self { self.chats.lock().unwrap().push(chat); self } /// Добавить несколько чатов pub fn with_chats(self, chats: Vec) -> Self { self.chats.lock().unwrap().extend(chats); self } /// Добавить сообщение в чат pub fn with_message(self, chat_id: i64, message: MessageInfo) -> Self { self.messages .lock() .unwrap() .entry(chat_id) .or_insert_with(Vec::new) .push(message); self } /// Добавить несколько сообщений в чат pub fn with_messages(self, chat_id: i64, messages: Vec) -> Self { self.messages.lock().unwrap().insert(chat_id, messages); self } /// Добавить папку pub fn with_folder(self, id: i32, name: &str) -> Self { self.folders .lock() .unwrap() .push(FolderInfo { id, name: name.to_string() }); self } /// Добавить пользователя pub fn with_user(self, id: i64, name: &str) -> Self { self.user_names.lock().unwrap().insert(id, name.to_string()); self } /// Добавить профиль pub fn with_profile(self, chat_id: i64, profile: ProfileInfo) -> Self { self.profiles.lock().unwrap().insert(chat_id, profile); self } /// Установить состояние сети pub fn with_network_state(self, state: NetworkState) -> Self { *self.network_state.lock().unwrap() = state; self } /// Установить состояние авторизации pub fn with_auth_state(self, state: AuthState) -> Self { *self.auth_state.lock().unwrap() = state; self } /// Добавить скачанный файл (для mock download_file) pub fn with_downloaded_file(self, file_id: i32, path: &str) -> Self { self.downloaded_files .lock() .unwrap() .insert(file_id, path.to_string()); self } /// Установить доступные реакции pub fn with_available_reactions(self, reactions: Vec) -> Self { *self.available_reactions.lock().unwrap() = reactions; self } // ==================== Async TDLib Operations ==================== /// Загрузить список чатов pub async fn load_chats(&self, limit: usize) -> Result, String> { if self.should_fail() { return Err("Failed to load chats".to_string()); } if self.simulate_delays { tokio::time::sleep(tokio::time::Duration::from_millis(50)).await; } let chats = self .chats .lock() .unwrap() .iter() .take(limit) .cloned() .collect(); Ok(chats) } /// Открыть чат pub async fn open_chat(&self, chat_id: ChatId) -> Result<(), String> { if self.should_fail() { return Err("Failed to open chat".to_string()); } *self.current_chat_id.lock().unwrap() = Some(chat_id.as_i64()); Ok(()) } /// Получить историю чата pub async fn get_chat_history( &self, chat_id: ChatId, limit: i32, ) -> Result, String> { if self.should_fail() { return Err("Failed to load history".to_string()); } if self.simulate_delays { tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; } let messages = self .messages .lock() .unwrap() .get(&chat_id.as_i64()) .map(|msgs| msgs.iter().take(limit as usize).cloned().collect()) .unwrap_or_default(); Ok(messages) } /// Загрузить старые сообщения pub async fn load_older_messages( &self, chat_id: ChatId, from_message_id: MessageId, ) -> Result, String> { if self.should_fail() { return Err("Failed to load older messages".to_string()); } let messages = self.messages.lock().unwrap(); let chat_messages = messages.get(&chat_id.as_i64()).ok_or("Chat not found")?; // Найти индекс сообщения и вернуть предыдущие if let Some(idx) = chat_messages.iter().position(|m| m.id() == from_message_id) { let older: Vec<_> = chat_messages.iter().take(idx).cloned().collect(); Ok(older) } else { Ok(vec![]) } } /// Отправить сообщение pub async fn send_message( &self, chat_id: ChatId, text: String, reply_to: Option, reply_info: Option, ) -> Result { if self.should_fail() { return Err("Failed to send message".to_string()); } if self.simulate_delays { tokio::time::sleep(tokio::time::Duration::from_millis(200)).await; } let message_id = MessageId::new((self.sent_messages.lock().unwrap().len() as i64) + 1000); self.sent_messages.lock().unwrap().push(SentMessage { chat_id: chat_id.as_i64(), text: text.clone(), reply_to, reply_info: reply_info.clone(), }); let message = MessageInfo::new( message_id, "You".to_string(), true, // is_outgoing text.clone(), vec![], // entities chrono::Utc::now().timestamp() as i32, 0, false, // is_read (станет true после Update) true, // can_be_edited true, // can_be_deleted_only_for_self true, // can_be_deleted_for_all_users reply_info, None, // forward_from vec![], // reactions ); // Добавляем в историю self.messages .lock() .unwrap() .entry(chat_id.as_i64()) .or_insert_with(Vec::new) .push(message.clone()); // Отправляем Update::NewMessage self.send_update(TdUpdate::NewMessage { chat_id, message: message.clone() }); Ok(message) } /// Редактировать сообщение pub async fn edit_message( &self, chat_id: ChatId, message_id: MessageId, new_text: String, ) -> Result { if self.should_fail() { return Err("Failed to edit message".to_string()); } if self.simulate_delays { tokio::time::sleep(tokio::time::Duration::from_millis(150)).await; } self.edited_messages.lock().unwrap().push(EditedMessage { chat_id: chat_id.as_i64(), message_id, new_text: new_text.clone(), }); // Обновляем сообщение let mut messages = self.messages.lock().unwrap(); if let Some(chat_msgs) = messages.get_mut(&chat_id.as_i64()) { if let Some(msg) = chat_msgs.iter_mut().find(|m| m.id() == message_id) { msg.content.text = new_text.clone(); msg.metadata.edit_date = msg.metadata.date + 60; let updated = msg.clone(); drop(messages); // Освобождаем lock перед отправкой update // Отправляем Update self.send_update(TdUpdate::MessageContent { chat_id, message_id, new_text }); return Ok(updated); } } Err("Message not found".to_string()) } /// Удалить сообщения pub async fn delete_messages( &self, chat_id: ChatId, message_ids: Vec, revoke: bool, ) -> Result<(), String> { if self.should_fail() { return Err("Failed to delete messages".to_string()); } if self.simulate_delays { tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; } self.deleted_messages.lock().unwrap().push(DeletedMessages { chat_id: chat_id.as_i64(), message_ids: message_ids.clone(), revoke, }); // Удаляем из истории let mut messages = self.messages.lock().unwrap(); if let Some(chat_msgs) = messages.get_mut(&chat_id.as_i64()) { chat_msgs.retain(|m| !message_ids.contains(&m.id())); } drop(messages); // Отправляем Update self.send_update(TdUpdate::DeleteMessages { chat_id, message_ids }); Ok(()) } /// Переслать сообщения pub async fn forward_messages( &self, to_chat_id: ChatId, from_chat_id: ChatId, message_ids: Vec, ) -> Result<(), String> { if self.should_fail() { return Err("Failed to forward messages".to_string()); } if self.simulate_delays { tokio::time::sleep(tokio::time::Duration::from_millis(150)).await; } self.forwarded_messages .lock() .unwrap() .push(ForwardedMessages { from_chat_id: from_chat_id.as_i64(), to_chat_id: to_chat_id.as_i64(), message_ids, }); Ok(()) } /// Поиск сообщений в чате pub async fn search_messages( &self, chat_id: ChatId, query: &str, ) -> Result, String> { if self.should_fail() { return Err("Failed to search messages".to_string()); } let messages = self.messages.lock().unwrap(); let results: Vec<_> = messages .get(&chat_id.as_i64()) .map(|msgs| { msgs.iter() .filter(|m| m.text().to_lowercase().contains(&query.to_lowercase())) .cloned() .collect() }) .unwrap_or_default(); self.searched_queries.lock().unwrap().push(SearchQuery { chat_id: chat_id.as_i64(), query: query.to_string(), results_count: results.len(), }); Ok(results) } /// Установить черновик pub async fn set_draft_message(&self, chat_id: ChatId, text: String) -> Result<(), String> { if text.is_empty() { self.drafts.lock().unwrap().remove(&chat_id.as_i64()); } else { self.drafts .lock() .unwrap() .insert(chat_id.as_i64(), text.clone()); } self.send_update(TdUpdate::ChatDraftMessage { chat_id, draft_text: if text.is_empty() { None } else { Some(text) }, }); Ok(()) } /// Отправить действие в чате (typing, etc.) pub async fn send_chat_action(&self, chat_id: ChatId, action: String) { self.chat_actions .lock() .unwrap() .push((chat_id.as_i64(), action.clone())); if action == "Typing" { *self.typing_chat_id.lock().unwrap() = Some(chat_id.as_i64()); } else if action == "Cancel" { *self.typing_chat_id.lock().unwrap() = None; } } /// Получить доступные реакции для сообщения pub async fn get_message_available_reactions( &self, _chat_id: ChatId, _message_id: MessageId, ) -> Result, String> { if self.should_fail() { return Err("Failed to get available reactions".to_string()); } Ok(self.available_reactions.lock().unwrap().clone()) } /// Установить/удалить реакцию pub async fn toggle_reaction( &self, chat_id: ChatId, message_id: MessageId, emoji: String, ) -> Result<(), String> { if self.should_fail() { return Err("Failed to toggle reaction".to_string()); } // Обновляем реакции на сообщении let mut messages = self.messages.lock().unwrap(); if let Some(chat_msgs) = messages.get_mut(&chat_id.as_i64()) { if let Some(msg) = chat_msgs.iter_mut().find(|m| m.id() == message_id) { let reactions = &mut msg.interactions.reactions; // Toggle logic if let Some(pos) = reactions .iter() .position(|r| r.emoji == emoji && r.is_chosen) { // Удаляем свою реакцию reactions.remove(pos); } else if let Some(reaction) = reactions.iter_mut().find(|r| r.emoji == emoji) { // Добавляем себя к существующей реакции reaction.is_chosen = true; reaction.count += 1; } else { // Добавляем новую реакцию reactions.push(ReactionInfo { emoji: emoji.clone(), count: 1, is_chosen: true, }); } let updated_reactions = reactions.clone(); drop(messages); // Отправляем Update self.send_update(TdUpdate::MessageInteractionInfo { chat_id, message_id, reactions: updated_reactions, }); } } Ok(()) } /// Скачать файл (mock) pub async fn download_file(&self, file_id: i32) -> Result { if self.should_fail() { return Err("Failed to download file".to_string()); } self.downloaded_files .lock() .unwrap() .get(&file_id) .cloned() .ok_or_else(|| format!("File {} not found", file_id)) } /// Получить информацию о профиле pub async fn get_profile_info(&self, chat_id: ChatId) -> Result { if self.should_fail() { return Err("Failed to get profile info".to_string()); } self.profiles .lock() .unwrap() .get(&chat_id.as_i64()) .cloned() .ok_or_else(|| "Profile not found".to_string()) } /// Отметить сообщения как просмотренные pub async fn view_messages(&self, chat_id: ChatId, message_ids: Vec) { self.viewed_messages .lock() .unwrap() .push((chat_id.as_i64(), message_ids.iter().map(|id| id.as_i64()).collect())); } /// Загрузить чаты папки pub async fn load_folder_chats(&self, _folder_id: i32, _limit: usize) -> Result<(), String> { if self.should_fail() { return Err("Failed to load folder chats".to_string()); } Ok(()) } // ==================== Helper Methods ==================== /// Отправить update в канал (если он установлен) fn send_update(&self, update: TdUpdate) { if let Some(tx) = self.update_tx.lock().unwrap().as_ref() { let _ = tx.send(update); } } /// Проверить нужно ли симулировать ошибку fn should_fail(&self) -> bool { let mut fail = self.fail_next_operation.lock().unwrap(); if *fail { *fail = false; // Сбрасываем после первого использования true } else { false } } /// Симулировать ошибку в следующей операции pub fn fail_next(&self) { *self.fail_next_operation.lock().unwrap() = true; } /// Симулировать входящее сообщение pub fn simulate_incoming_message(&self, chat_id: ChatId, text: String, sender_name: &str) { let message_id = MessageId::new(9000 + chrono::Utc::now().timestamp()); let message = MessageInfo::new( message_id, sender_name.to_string(), false, // is_outgoing text, vec![], chrono::Utc::now().timestamp() as i32, 0, false, false, false, true, None, None, vec![], ); // Добавляем в историю self.messages .lock() .unwrap() .entry(chat_id.as_i64()) .or_insert_with(Vec::new) .push(message.clone()); // Отправляем Update self.send_update(TdUpdate::NewMessage { chat_id, message }); } /// Симулировать typing от собеседника pub fn simulate_typing(&self, chat_id: ChatId, user_id: UserId) { self.send_update(TdUpdate::ChatAction { chat_id, user_id, action: "Typing".to_string() }); } /// Симулировать изменение состояния сети pub fn simulate_network_change(&self, state: NetworkState) { *self.network_state.lock().unwrap() = state.clone(); self.send_update(TdUpdate::ConnectionState { state }); } /// Симулировать прочтение сообщений pub fn simulate_read_outbox(&self, chat_id: ChatId, last_read_message_id: MessageId) { self.send_update(TdUpdate::ChatReadOutbox { chat_id, last_read_outbox_message_id: last_read_message_id, }); } // ==================== Getters for Test Assertions ==================== /// Получить все чаты pub fn get_chats(&self) -> Vec { self.chats.lock().unwrap().clone() } /// Получить все папки pub fn get_folders(&self) -> Vec { self.folders.lock().unwrap().clone() } /// Получить сообщения чата pub fn get_messages(&self, chat_id: i64) -> Vec { self.messages .lock() .unwrap() .get(&chat_id) .cloned() .unwrap_or_default() } /// Получить отправленные сообщения pub fn get_sent_messages(&self) -> Vec { self.sent_messages.lock().unwrap().clone() } /// Получить отредактированные сообщения pub fn get_edited_messages(&self) -> Vec { self.edited_messages.lock().unwrap().clone() } /// Получить удалённые сообщения pub fn get_deleted_messages(&self) -> Vec { self.deleted_messages.lock().unwrap().clone() } /// Получить пересланные сообщения pub fn get_forwarded_messages(&self) -> Vec { self.forwarded_messages.lock().unwrap().clone() } /// Получить поисковые запросы pub fn get_search_queries(&self) -> Vec { self.searched_queries.lock().unwrap().clone() } /// Получить просмотренные сообщения pub fn get_viewed_messages(&self) -> Vec<(i64, Vec)> { self.viewed_messages.lock().unwrap().clone() } /// Получить действия в чатах pub fn get_chat_actions(&self) -> Vec<(i64, String)> { self.chat_actions.lock().unwrap().clone() } /// Получить текущее состояние сети pub fn get_network_state(&self) -> NetworkState { self.network_state.lock().unwrap().clone() } /// Получить ID текущего открытого чата pub fn get_current_chat_id(&self) -> Option { *self.current_chat_id.lock().unwrap() } /// Установить update channel для получения событий pub fn set_update_channel(&self, tx: mpsc::UnboundedSender) { *self.update_tx.lock().unwrap() = Some(tx); } /// Очистить всю историю действий pub fn clear_all_history(&self) { self.sent_messages.lock().unwrap().clear(); self.edited_messages.lock().unwrap().clear(); self.deleted_messages.lock().unwrap().clear(); self.forwarded_messages.lock().unwrap().clear(); self.searched_queries.lock().unwrap().clear(); self.viewed_messages.lock().unwrap().clear(); self.chat_actions.lock().unwrap().clear(); } } #[cfg(test)] mod tests { use super::*; use crate::helpers::test_data::create_test_chat; use tele_tui::types::ChatId; #[test] fn test_fake_client_creation() { let client = FakeTdClient::new(); assert_eq!(client.get_chats().len(), 0); assert_eq!(client.folders.lock().unwrap().len(), 1); // Default "All" folder } #[test] fn test_fake_client_with_chat() { let chat = create_test_chat("Mom", 123); let client = FakeTdClient::new().with_chat(chat); let chats = client.get_chats(); assert_eq!(chats.len(), 1); assert_eq!(chats[0].title, "Mom"); } #[tokio::test] async fn test_send_message() { let client = FakeTdClient::new(); let chat_id = ChatId::new(123); let result = client .send_message(chat_id, "Hello".to_string(), None, None) .await; assert!(result.is_ok()); let sent = client.get_sent_messages(); assert_eq!(sent.len(), 1); assert_eq!(sent[0].text, "Hello"); assert_eq!(client.get_messages(123).len(), 1); } #[tokio::test] async fn test_edit_message() { let client = FakeTdClient::new(); let chat_id = ChatId::new(123); let msg = client .send_message(chat_id, "Hello".to_string(), None, None) .await .unwrap(); let msg_id = msg.id(); let _ = client .edit_message(chat_id, msg_id, "Hello World".to_string()) .await; let edited = client.get_edited_messages(); assert_eq!(edited.len(), 1); assert_eq!(client.get_messages(123)[0].text(), "Hello World"); assert!(client.get_messages(123)[0].metadata.edit_date > 0); } #[tokio::test] async fn test_delete_message() { let client = FakeTdClient::new(); let chat_id = ChatId::new(123); let msg = client .send_message(chat_id, "Hello".to_string(), None, None) .await .unwrap(); let msg_id = msg.id(); let _ = client.delete_messages(chat_id, vec![msg_id], false).await; let deleted = client.get_deleted_messages(); assert_eq!(deleted.len(), 1); assert_eq!(client.get_messages(123).len(), 0); } #[tokio::test] async fn test_update_channel() { let (client, mut rx) = FakeTdClient::new().with_update_channel(); let chat_id = ChatId::new(123); // Отправляем сообщение let _ = client .send_message(chat_id, "Test".to_string(), None, None) .await; // Проверяем что получили Update if let Some(update) = rx.recv().await { match update { TdUpdate::NewMessage { chat_id: updated_chat, .. } => { assert_eq!(updated_chat, chat_id); } _ => panic!("Expected NewMessage update"), } } else { panic!("No update received"); } } #[tokio::test] async fn test_simulate_incoming_message() { let (client, mut rx) = FakeTdClient::new().with_update_channel(); let chat_id = ChatId::new(123); client.simulate_incoming_message(chat_id, "Hello from Bob".to_string(), "Bob"); // Проверяем Update if let Some(TdUpdate::NewMessage { message, .. }) = rx.recv().await { assert_eq!(message.text(), "Hello from Bob"); assert_eq!(message.sender_name(), "Bob"); assert!(!message.is_outgoing()); } // Проверяем что сообщение добавилось assert_eq!(client.get_messages(123).len(), 1); } #[tokio::test] async fn test_fail_next_operation() { let client = FakeTdClient::new(); let chat_id = ChatId::new(123); // Устанавливаем флаг ошибки client.fail_next(); // Следующая операция должна упасть let result = client .send_message(chat_id, "Test".to_string(), None, None) .await; assert!(result.is_err()); // Но следующая должна пройти let result2 = client .send_message(chat_id, "Test2".to_string(), None, None) .await; assert!(result2.is_ok()); } }