style: auto-format entire codebase with cargo fmt (stable rustfmt.toml)
Some checks failed
ci/woodpecker/pr/check Pipeline failed
CI / Check (pull_request) Has been cancelled
CI / Format (pull_request) Has been cancelled
CI / Clippy (pull_request) Has been cancelled
CI / Build (macos-latest) (pull_request) Has been cancelled
CI / Build (ubuntu-latest) (pull_request) Has been cancelled
CI / Build (windows-latest) (pull_request) Has been cancelled

This commit is contained in:
Mikhail Kilin
2026-02-22 17:09:51 +03:00
parent 2442a90e23
commit 264f183510
90 changed files with 1632 additions and 1450 deletions

View File

@@ -2,22 +2,48 @@
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use tele_tui::tdlib::{AuthState, ChatInfo, MessageInfo, NetworkState, ProfileInfo, ReplyInfo};
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<MessageId> },
ChatAction { chat_id: ChatId, user_id: UserId, action: String },
MessageInteractionInfo { chat_id: ChatId, message_id: MessageId, reactions: Vec<ReactionInfo> },
ConnectionState { state: NetworkState },
ChatReadOutbox { chat_id: ChatId, last_read_outbox_message_id: MessageId },
ChatDraftMessage { chat_id: ChatId, draft_text: Option<String> },
NewMessage {
chat_id: ChatId,
message: MessageInfo,
},
MessageContent {
chat_id: ChatId,
message_id: MessageId,
new_text: String,
},
DeleteMessages {
chat_id: ChatId,
message_ids: Vec<MessageId>,
},
ChatAction {
chat_id: ChatId,
user_id: UserId,
action: String,
},
MessageInteractionInfo {
chat_id: ChatId,
message_id: MessageId,
reactions: Vec<ReactionInfo>,
},
ConnectionState {
state: NetworkState,
},
ChatReadOutbox {
chat_id: ChatId,
last_read_outbox_message_id: MessageId,
},
ChatDraftMessage {
chat_id: ChatId,
draft_text: Option<String>,
},
}
/// Упрощённый mock TDLib клиента для тестов
@@ -30,14 +56,14 @@ pub struct FakeTdClient {
pub profiles: Arc<Mutex<HashMap<i64, ProfileInfo>>>,
pub drafts: Arc<Mutex<HashMap<i64, String>>>,
pub available_reactions: Arc<Mutex<Vec<String>>>,
// Состояние
pub network_state: Arc<Mutex<NetworkState>>,
pub typing_chat_id: Arc<Mutex<Option<i64>>>,
pub current_chat_id: Arc<Mutex<Option<i64>>>,
pub current_pinned_message: Arc<Mutex<Option<MessageInfo>>>,
pub auth_state: Arc<Mutex<AuthState>>,
// История действий (для проверки в тестах)
pub sent_messages: Arc<Mutex<Vec<SentMessage>>>,
pub edited_messages: Arc<Mutex<Vec<EditedMessage>>>,
@@ -45,12 +71,12 @@ pub struct FakeTdClient {
pub forwarded_messages: Arc<Mutex<Vec<ForwardedMessages>>>,
pub searched_queries: Arc<Mutex<Vec<SearchQuery>>>,
pub viewed_messages: Arc<Mutex<Vec<(i64, Vec<i64>)>>>, // (chat_id, message_ids)
pub chat_actions: Arc<Mutex<Vec<(i64, String)>>>, // (chat_id, action)
pub chat_actions: Arc<Mutex<Vec<(i64, String)>>>, // (chat_id, action)
pub pending_view_messages: Arc<Mutex<Vec<(ChatId, Vec<MessageId>)>>>, // Очередь для отметки как прочитанные
// Update channel для симуляции событий
pub update_tx: Arc<Mutex<Option<mpsc::UnboundedSender<TdUpdate>>>>,
// Скачанные файлы (file_id -> local_path)
pub downloaded_files: Arc<Mutex<HashMap<i32, String>>>,
@@ -142,8 +168,14 @@ impl FakeTdClient {
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(),
"👍".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)),
@@ -164,14 +196,14 @@ impl FakeTdClient {
fail_next_operation: Arc::new(Mutex::new(false)),
}
}
/// Создать update channel для получения событий
pub fn with_update_channel(self) -> (Self, mpsc::UnboundedReceiver<TdUpdate>) {
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;
@@ -179,7 +211,7 @@ impl FakeTdClient {
}
// ==================== Builder Methods ====================
/// Добавить чат
pub fn with_chat(self, chat: ChatInfo) -> Self {
self.chats.lock().unwrap().push(chat);
@@ -205,16 +237,16 @@ impl FakeTdClient {
/// Добавить несколько сообщений в чат
pub fn with_messages(self, chat_id: i64, messages: Vec<MessageInfo>) -> Self {
self.messages
.lock()
.unwrap()
.insert(chat_id, messages);
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.folders
.lock()
.unwrap()
.push(FolderInfo { id, name: name.to_string() });
self
}
@@ -241,10 +273,13 @@ impl FakeTdClient {
*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.downloaded_files
.lock()
.unwrap()
.insert(file_id, path.to_string());
self
}
@@ -255,60 +290,76 @@ impl FakeTdClient {
}
// ==================== Async TDLib Operations ====================
/// Загрузить список чатов
pub async fn load_chats(&self, limit: usize) -> Result<Vec<ChatInfo>, 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();
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<Vec<MessageInfo>, String> {
pub async fn get_chat_history(
&self,
chat_id: ChatId,
limit: i32,
) -> Result<Vec<MessageInfo>, 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
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<Vec<MessageInfo>, String> {
pub async fn load_older_messages(
&self,
chat_id: ChatId,
from_message_id: MessageId,
) -> Result<Vec<MessageInfo>, 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();
@@ -329,24 +380,24 @@ impl FakeTdClient {
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
true, // is_outgoing
text.clone(),
vec![], // entities
chrono::Utc::now().timestamp() as i32,
@@ -356,10 +407,10 @@ impl FakeTdClient {
true, // can_be_deleted_only_for_self
true, // can_be_deleted_for_all_users
reply_info,
None, // forward_from
None, // forward_from
vec![], // reactions
);
// Добавляем в историю
self.messages
.lock()
@@ -367,16 +418,13 @@ impl FakeTdClient {
.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(),
});
self.send_update(TdUpdate::NewMessage { chat_id, message: message.clone() });
Ok(message)
}
/// Редактировать сообщение
pub async fn edit_message(
&self,
@@ -387,41 +435,37 @@ impl FakeTdClient {
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,
});
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,
@@ -432,33 +476,30 @@ impl FakeTdClient {
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,
});
self.send_update(TdUpdate::DeleteMessages { chat_id, message_ids });
Ok(())
}
/// Переслать сообщения
pub async fn forward_messages(
&self,
@@ -469,26 +510,33 @@ impl FakeTdClient {
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,
});
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<Vec<MessageInfo>, String> {
pub async fn search_messages(
&self,
chat_id: ChatId,
query: &str,
) -> Result<Vec<MessageInfo>, 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())
@@ -499,43 +547,49 @@ impl FakeTdClient {
.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.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()));
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,
@@ -545,10 +599,10 @@ impl FakeTdClient {
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,
@@ -559,15 +613,18 @@ impl FakeTdClient {
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) {
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) {
@@ -582,10 +639,10 @@ impl FakeTdClient {
is_chosen: true,
});
}
let updated_reactions = reactions.clone();
drop(messages);
// Отправляем Update
self.send_update(TdUpdate::MessageInteractionInfo {
chat_id,
@@ -594,10 +651,10 @@ impl FakeTdClient {
});
}
}
Ok(())
}
/// Скачать файл (mock)
pub async fn download_file(&self, file_id: i32) -> Result<String, String> {
if self.should_fail() {
@@ -617,7 +674,7 @@ impl FakeTdClient {
if self.should_fail() {
return Err("Failed to get profile info".to_string());
}
self.profiles
.lock()
.unwrap()
@@ -625,7 +682,7 @@ impl FakeTdClient {
.cloned()
.ok_or_else(|| "Profile not found".to_string())
}
/// Отметить сообщения как просмотренные
pub async fn view_messages(&self, chat_id: ChatId, message_ids: Vec<MessageId>) {
self.viewed_messages
@@ -633,25 +690,25 @@ impl FakeTdClient {
.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();
@@ -662,16 +719,16 @@ impl FakeTdClient {
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(),
@@ -688,7 +745,7 @@ impl FakeTdClient {
None,
vec![],
);
// Добавляем в историю
self.messages
.lock()
@@ -696,26 +753,22 @@ impl FakeTdClient {
.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(),
});
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 {
@@ -723,9 +776,9 @@ impl FakeTdClient {
last_read_outbox_message_id: last_read_message_id,
});
}
// ==================== Getters for Test Assertions ====================
/// Получить все чаты
pub fn get_chats(&self) -> Vec<ChatInfo> {
self.chats.lock().unwrap().clone()
@@ -745,57 +798,57 @@ impl FakeTdClient {
.cloned()
.unwrap_or_default()
}
/// Получить отправленные сообщения
pub fn get_sent_messages(&self) -> Vec<SentMessage> {
self.sent_messages.lock().unwrap().clone()
}
/// Получить отредактированные сообщения
pub fn get_edited_messages(&self) -> Vec<EditedMessage> {
self.edited_messages.lock().unwrap().clone()
}
/// Получить удалённые сообщения
pub fn get_deleted_messages(&self) -> Vec<DeletedMessages> {
self.deleted_messages.lock().unwrap().clone()
}
/// Получить пересланные сообщения
pub fn get_forwarded_messages(&self) -> Vec<ForwardedMessages> {
self.forwarded_messages.lock().unwrap().clone()
}
/// Получить поисковые запросы
pub fn get_search_queries(&self) -> Vec<SearchQuery> {
self.searched_queries.lock().unwrap().clone()
}
/// Получить просмотренные сообщения
pub fn get_viewed_messages(&self) -> Vec<(i64, Vec<i64>)> {
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<i64> {
*self.current_chat_id.lock().unwrap()
}
/// Установить update channel для получения событий
pub fn set_update_channel(&self, tx: mpsc::UnboundedSender<TdUpdate>) {
*self.update_tx.lock().unwrap() = Some(tx);
}
/// Очистить всю историю действий
pub fn clear_all_history(&self) {
self.sent_messages.lock().unwrap().clear();
@@ -835,10 +888,12 @@ mod tests {
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;
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");
@@ -849,12 +904,17 @@ mod tests {
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 = 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 _ = 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");
@@ -865,25 +925,30 @@ mod tests {
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 = 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;
let _ = client
.send_message(chat_id, "Test".to_string(), None, None)
.await;
// Проверяем что получили Update
if let Some(update) = rx.recv().await {
match update {
@@ -896,39 +961,43 @@ mod tests {
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;
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;
let result2 = client
.send_message(chat_id, "Test2".to_string(), None, None)
.await;
assert!(result2.is_ok());
}
}