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:
Mikhail Kilin
2026-02-02 05:42:19 +03:00
parent ed5a4f9c72
commit 8e48d076de
38 changed files with 1053 additions and 161 deletions

View File

@@ -332,6 +332,150 @@ reaction_other = "gray"
## Последние обновления (2026-02-02)
### Исправление критической ошибки — Stack Overflow при работе с сообщениями ✅ (2026-02-02)
**Проблема**:
- Stack overflow при запуске приложения, отправке и редактировании сообщений
- Ошибка: `thread 'main' has overflowed its stack fatal runtime error: stack overflow, aborting`
**Причина**:
Бесконечная рекурсия в trait реализации из-за несоответствия сигнатур методов между trait и inherent impl:
- Trait методы: `&mut self`
- TdClient inherent методы: `&self`
- При вызове `self.method()` внутри trait impl, Rust не мог вызвать inherent метод (несовместимость типов) и вызывал trait метод → бесконечная рекурсия
**Исправлено 6 методов**:
1. **`send_message`** - прямой вызов `self.message_manager.send_message()` вместо `self.send_message()`
2. **`edit_message`** - прямой вызов `self.message_manager.edit_message()`
3. **`delete_messages`** - прямой вызов `self.message_manager.delete_messages()`
4. **`forward_messages`** - прямой вызов `self.message_manager.forward_messages()`
5. **`current_chat_messages`** - прямой доступ `self.message_manager.current_chat_messages.to_vec()`
6. **`current_pinned_message`** - прямой доступ `self.message_manager.current_pinned_message.clone()`
**Результат**:
- ✅ Компиляция успешна
-Все 196+ тестов проходят
- ✅ Приложение запускается без ошибок
- ✅ Отправка сообщений работает
- ✅ Редактирование сообщений работает
- ✅ Удаление и пересылка сообщений работают
**Файлы изменены**:
- `src/tdlib/client_impl.rs` - исправлены 6 методов trait реализации
---
### Рефакторинг — Dependency Injection для TdClient ЗАВЕРШЁН ✅ (2026-02-02)
**Статус**: ВСЕ 8 ЭТАПОВ ЗАВЕРШЕНЫ! 🎉
**Цель**: Реализовать trait-based DI для TdClient, чтобы тесты использовали FakeTdClient вместо реального TDLib клиента.
**План (8 этапов) - ВСЕ ГОТОВО**:
1. ✅ Создать trait TdClientTrait
2. ✅ Реализовать trait для TdClient
3. ✅ Реализовать trait для FakeTdClient
4. ✅ Сделать App generic: `App<T: TdClientTrait = TdClient>`
5. ✅ Обновить все input handlers (generic)
6. ✅ Обновить все UI модули (generic)
7. ✅ Обновить TestAppBuilder и тесты
8. ✅ Убрать timeout'ы (100ms), запустить тесты
**Что сделано (ВСЕ ЭТАПЫ)**:
**Этапы 1-2: Trait и impl для TdClient**
- ✅ Создан `src/tdlib/trait.rs` (130 строк):
- Trait `TdClientTrait` с 40+ методами
- Все async методы с `#[async_trait]`
- Auth, Chat, Message, User, Reaction методы
- Getters/Setters для state
- ✅ Создан `src/tdlib/client_impl.rs` (270 строк):
- `impl TdClientTrait for TdClient`
- Все методы делегируют к существующим
- Полное покрытие API
**Этап 3: FakeTdClient trait impl**
- ✅ Создан `tests/helpers/fake_tdclient_impl.rs` (~300 строк):
- `impl TdClientTrait for FakeTdClient`
- Делегирование к методам FakeTdClient
- Обработка Arc<Mutex<>> vs &references design limitation
- Некоторые методы возвращают пустые значения (для UI-only полей)
**Этап 4: Generic App**
- ✅ Обновлён `src/app/mod.rs`:
- `pub struct App<T: TdClientTrait = TdClient>`
- `impl<T: TdClientTrait> App<T>` - generic impl со всеми методами
- `impl App<TdClient>` - convenience `new(config)` для продакшена
- `with_client(config, td_client)` - generic конструктор
**Этап 5: Generic input handlers**
- ✅ Обновлены ВСЕ input handlers:
- `src/input/main_input.rs` - `handle<T: TdClientTrait>(app: &mut App<T>)`
- `src/input/auth.rs` - generic
- `src/input/handlers/global.rs` - `handle_global_commands<T>()` + `handle_pinned_messages<T>()`
- `src/input/handlers/profile.rs` - generic
- `src/input/handlers/chat_list.rs` - generic
- `src/input/handlers/modal.rs` - все 4 функции generic
- `src/input/handlers/search.rs` - обе функции generic
- `src/input/handlers/messages.rs` - generic
**Этап 6: Generic UI modules**
- ✅ Обновлены ВСЕ UI модули:
- `src/ui/mod.rs` - `render<T: TdClientTrait>()`
- `src/ui/loading.rs` - generic
- `src/ui/auth.rs` - generic
- `src/ui/main_screen.rs` - generic
- `src/ui/chat_list.rs` - generic
- `src/ui/footer.rs` - generic
- `src/ui/messages.rs` - generic
- `src/ui/profile.rs` - generic
**Этап 7: Тесты и TestAppBuilder**
- ✅ Обновлён `tests/helpers/app_builder.rs`:
- `build() -> App<FakeTdClient>` вместо `App`
- Использует `FakeTdClient::new()` + builder pattern
- Чистая работа без обращения к internal fields
- Все тесты билдера обновлены
- ✅ Обновлён `src/main.rs`:
- `run_app<B, T: TdClientTrait>()` - generic
- `main()` использует `App::new(config)` - работает как раньше
**Этап 8: Удалены timeout'ы**
- ✅ Удалены 3 timeout wrapper'а из `src/input/main_input.rs`:
- Typing status send (line ~869) - убран `tokio::time::timeout(100ms)`
- Draft save (line ~685) - убран `tokio::time::timeout(100ms)`
- Draft clear (line ~691) - убран `tokio::time::timeout(100ms)`
- Причина удаления: timeout'ы были добавлены "чтобы не блокировать UI в тестах", но теперь тесты используют FakeTdClient который возвращается мгновенно
**Файлы созданы**:
- `src/tdlib/trait.rs` - trait definition
- `src/tdlib/client_impl.rs` - impl for TdClient
- `tests/helpers/fake_tdclient_impl.rs` - impl for FakeTdClient
**Файлы изменены (основные)**:
- `src/tdlib/mod.rs` - экспорты FolderInfo, UserCache, TdClientTrait
- `src/app/mod.rs` - generic App<T>
- `src/main.rs` - generic run_app()
- `src/input/*.rs` - все handlers generic
- `src/ui/*.rs` - все UI функции generic
- `tests/helpers/app_builder.rs` - build() -> App<FakeTdClient>
- `tests/helpers/mod.rs` - добавлен fake_tdclient_impl модуль
- `Cargo.toml` - добавлен async-trait
**Результат**:
- ✅ Чистая архитектура с trait-based DI
- ✅ App работает с любым T: TdClientTrait
- ✅ Тесты используют FakeTdClient (быстро, без логов)
- ✅ Продакшн использует TdClient (реальный TDLib)
- ✅ Убраны timeout'ы из продакшн кода
- ✅ Priority 6 ЗАВЕРШЁН на 100%! 🎉
---
## Последние обновления (2026-02-02 ранее)
### Рефакторинг — UI компоненты message_bubble.rs ЗАВЕРШЁН ✅ (2026-02-02)
**Что сделано**: