File size: 5,175 Bytes
d42edc0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f6f0626
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0c1f329
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
"""
conftest.py — настройка окружения ПЕРЕД импортом bot.py тестами.

bot.py на уровне модуля читает переменные окружения (BOT_TOKEN, GEMINI_API_KEY
и т.д.) и пишет лог-файл по пути из BOT_LOG_PATH (по умолчанию /app/bot.log).
Чтобы тесты можно было гонять где угодно (не только внутри Docker-контейнера
HF Spaces, где /app существует и доступен на запись), здесь выставляются
безопасные заглушки ДО того, как pytest соберёт и импортирует тестовые модули.

Ничего из этого не требует сети и не создаёт реальных Bot()/genai.Client() —
они создаются только внутри main(), которая тестами не вызывается.
"""
import os
import tempfile

os.environ.setdefault("BOT_LOG_PATH", os.path.join(tempfile.gettempdir(), "lumen_test_bot.log"))
os.environ.setdefault("BOT_TOKEN", "test-token-not-real")
os.environ.setdefault("GEMINI_API_KEY", "test-key-not-real")

import copy
import pytest


@pytest.fixture(autouse=True)
def _bot_global_state_guard():
    """Снимок наиболее часто вручную сохраняемых модульных глобалов bot.py перед
    каждым тестом и восстановление после (аудит техдолга, август 2026).

    Десятки тестов в test_bot_helpers.py вручную сохраняли/восстанавливали
    bot.chat_state/bot.GLOBAL_QUOTA/bot.client/bot.bot в try/finally — рабочий, но
    повторяющийся бойлерплейт и потенциальный источник тонких утечек между тестами
    при росте сьюта (забытый finally молча "протравливает" состояние в следующие
    тесты). Это ДОПОЛНИТЕЛЬНАЯ страховочная сетка, а не замена — существующие тесты
    со своим ручным cleanup продолжают работать точно так же, как раньше (двойное
    восстановление безвредно); новые тесты могут полагаться только на эту фикстуру
    и не писать собственный try/finally для этих четырёх глобалов.

    Намеренно НЕ снимается снимок вообще всего модульного состояния (_dirty_chat_ids,
    TELEGRAM_API_BASE_URL и т.п.) — тесты, которые их трогают, уже сами аккуратно
    восстанавливают именно то, что меняют (см. test_flush_dirty_state_once_* и
    аналогичные); расширять фикстуру на них без реальной необходимости было бы
    накоплением сложности "на будущее", а не решением подтверждённой проблемы."""
    import bot as _bot_module
    chat_state_snapshot = copy.deepcopy(_bot_module.chat_state)
    quota_snapshot = copy.deepcopy(_bot_module.GLOBAL_QUOTA)
    client_snapshot = _bot_module.client
    bot_snapshot = _bot_module.bot
    yield
    _bot_module.chat_state.clear()
    _bot_module.chat_state.update(chat_state_snapshot)
    _bot_module.GLOBAL_QUOTA.clear()
    _bot_module.GLOBAL_QUOTA.update(quota_snapshot)
    _bot_module.client = client_snapshot
    _bot_module.bot = bot_snapshot


@pytest.fixture(autouse=True)
def _instant_typing_pace():
    """Стриминг теперь искусственно "довыводит" остаток уже полученного, но ещё не
    показанного текста (см. STREAM_TYPING_*/_run_streaming_reply в bot.py и
    lumen_typing_pace.py) — несколько await bot._typing_sleep(...) между правками
    сообщения, чтобы визуально было похоже на живой набор текста. В тестах
    реальное ожидание не нужно и заметно замедлило бы весь сьют без единой пользы —
    bot._typing_sleep существует именно как точка подмены (тот же приём, что и
    bot._get_http_session/bot._openrouter_stream_pieces и т.п. в других местах),
    здесь она безусловно патчится на no-op для КАЖДОГО теста, а не только тех,
    что явно тестируют стриминг."""
    import bot as _bot_module
    original_typing_sleep = _bot_module._typing_sleep

    async def _instant_sleep(_seconds: float) -> None:
        return None

    _bot_module._typing_sleep = _instant_sleep
    yield
    _bot_module._typing_sleep = original_typing_sleep