refactor: implement trait-based DI for TdClient and fix stack overflow
Implement complete trait-based dependency injection pattern for TdClient to enable testing with FakeTdClient mock. Fix critical stack overflow bugs caused by infinite recursion in trait implementations. Breaking Changes: - App is now generic: App<T: TdClientTrait = TdClient> - All UI and input handlers are generic over TdClientTrait - TdClient methods now accessed through trait interface New Files: - src/tdlib/trait.rs: TdClientTrait definition with 40+ methods - src/tdlib/client_impl.rs: TdClientTrait impl for TdClient - tests/helpers/fake_tdclient_impl.rs: TdClientTrait impl for FakeTdClient Critical Fixes: - Fix stack overflow in send_message, edit_message, delete_messages - Fix stack overflow in forward_messages, current_chat_messages - Fix stack overflow in current_pinned_message - All methods now call message_manager directly to avoid recursion Testing: - FakeTdClient supports configurable auth_state for auth screen tests - Added pinned message support in FakeTdClient - All 196+ tests passing (188 tests + 8 benchmarks) Dependencies: - Added async-trait = "0.1" Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,18 +2,14 @@
|
||||
|
||||
use ratatui::widgets::ListState;
|
||||
use std::collections::HashMap;
|
||||
use super::FakeTdClient;
|
||||
use tele_tui::app::{App, AppScreen, ChatState};
|
||||
use tele_tui::config::Config;
|
||||
use tele_tui::tdlib::AuthState;
|
||||
use tele_tui::tdlib::{ChatInfo, MessageInfo};
|
||||
use tele_tui::types::{ChatId, MessageId};
|
||||
|
||||
/// Builder для создания тестового App
|
||||
///
|
||||
/// Примечание: Так как App содержит реальный TdClient,
|
||||
/// этот билдер подходит только для UI/snapshot тестов.
|
||||
/// Для интеграционных тестов логики понадобится рефакторинг
|
||||
/// с выделением trait для TdClient.
|
||||
/// Builder для создания тестового App с FakeTdClient\n///\n/// Использует trait-based DI для подмены TdClient на FakeTdClient в тестах.
|
||||
pub struct TestAppBuilder {
|
||||
config: Config,
|
||||
screen: AppScreen,
|
||||
@@ -214,13 +210,36 @@ impl TestAppBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Построить App
|
||||
/// Построить App с FakeTdClient
|
||||
///
|
||||
/// ВАЖНО: Этот метод создаёт App с реальным TdClient,
|
||||
/// поэтому он подходит только для UI тестов, где мы
|
||||
/// не вызываем методы TdClient.
|
||||
pub fn build(self) -> App {
|
||||
let mut app = App::new(self.config);
|
||||
/// Создаёт App с FakeTdClient, подходит для любых тестов включая
|
||||
/// интеграционные тесты логики.
|
||||
pub fn build(self) -> App<FakeTdClient> {
|
||||
// Создаём FakeTdClient с чатами и сообщениями
|
||||
let mut fake_client = FakeTdClient::new();
|
||||
|
||||
// Добавляем чаты
|
||||
for chat in &self.chats {
|
||||
fake_client = fake_client.with_chat(chat.clone());
|
||||
}
|
||||
|
||||
// Добавляем сообщения
|
||||
for (chat_id, messages) in self.messages {
|
||||
fake_client = fake_client.with_messages(chat_id, messages);
|
||||
}
|
||||
|
||||
// Устанавливаем текущий чат если нужно
|
||||
if let Some(chat_id) = self.selected_chat_id {
|
||||
*fake_client.current_chat_id.lock().unwrap() = Some(chat_id);
|
||||
}
|
||||
|
||||
// Устанавливаем auth state если нужно
|
||||
if let Some(auth_state) = self.auth_state {
|
||||
fake_client = fake_client.with_auth_state(auth_state);
|
||||
}
|
||||
|
||||
// Создаём App с FakeTdClient
|
||||
let mut app = App::with_client(self.config, fake_client);
|
||||
|
||||
app.screen = self.screen;
|
||||
app.chats = self.chats;
|
||||
@@ -228,6 +247,7 @@ impl TestAppBuilder {
|
||||
app.message_input = self.message_input;
|
||||
app.is_searching = self.is_searching;
|
||||
app.search_query = self.search_query;
|
||||
|
||||
// Применяем chat_state если он установлен
|
||||
if let Some(chat_state) = self.chat_state {
|
||||
app.chat_state = chat_state;
|
||||
@@ -238,11 +258,6 @@ impl TestAppBuilder {
|
||||
app.status_message = Some(status);
|
||||
}
|
||||
|
||||
// Применяем auth state
|
||||
if let Some(auth_state) = self.auth_state {
|
||||
app.td_client.auth.state = auth_state;
|
||||
}
|
||||
|
||||
// Применяем auth inputs
|
||||
if let Some(phone) = self.phone_input {
|
||||
app.phone_input = phone;
|
||||
@@ -261,14 +276,6 @@ impl TestAppBuilder {
|
||||
app.chat_list_state = list_state;
|
||||
}
|
||||
|
||||
// Применяем сообщения к текущему открытому чату
|
||||
if let Some(chat_id) = self.selected_chat_id {
|
||||
if let Some(messages) = self.messages.get(&chat_id) {
|
||||
app.td_client.message_manager.current_chat_messages = messages.clone();
|
||||
app.td_client.set_current_chat_id(Some(ChatId::new(chat_id)));
|
||||
}
|
||||
}
|
||||
|
||||
app
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user