"""Жизненный цикл общего на приложение `TelegramClient` (`app.services.tgbot.shared`). Смысл модуля — ровно в том, что экземпляр ОДИН: до правки три HTTP-ручки создавали клиент на каждый входящий запрос, и keep-alive не было ни на каком уровне. Если синглтон однажды перестанет быть синглтоном, тесты ручек этого не заметят (они подменяют аксессор целиком), а на проде вернутся TLS-хендшейки на каждое сообщение. Поэтому проверяем идентичность и закрытие здесь, отдельно. Реальных сетевых вызовов нет: `TelegramClient` создаёт `httpx.AsyncClient` лениво, при первом запросе, а мы до запросов не доходим. """ from __future__ import annotations import os import pytest os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test") from app.services.tgbot import shared from app.services.tgbot.client import TelegramClient @pytest.fixture(autouse=True) def _reset_singleton(): """Глобал модуля не должен течь между тестами — иначе порядок решает исход.""" shared._client = None yield shared._client = None def test_get_returns_same_instance() -> None: """Два обращения — один объект. Это и есть весь смысл модуля.""" first = shared.get_telegram_client() second = shared.get_telegram_client() assert isinstance(first, TelegramClient) assert first is second, "аксессор создал второй клиент — пул перестал переиспользоваться" def test_init_returns_the_shared_instance() -> None: """`init_telegram_client` в lifespan и `get_telegram_client` в ручке — один и тот же объект.""" created = shared.init_telegram_client() assert created is shared.get_telegram_client() async def test_close_resets_and_next_get_builds_a_fresh_one() -> None: """После shutdown глобал обнулён; повторный startup обязан получить рабочий клиент. Ленивость после закрытия намеренна: держать «остановленный» флаг значило бы, что повторный `init_telegram_client()` в том же процессе отдаёт закрытый пул. """ first = shared.get_telegram_client() await shared.close_telegram_client() assert shared._client is None, "глобал не обнулён — следующий startup взял бы закрытый пул" second = shared.get_telegram_client() assert second is not first async def test_close_is_idempotent() -> None: """Второй `close` не должен падать: lifespan зовёт его из `finally`.""" shared.get_telegram_client() await shared.close_telegram_client() await shared.close_telegram_client() assert shared._client is None async def test_close_without_client_is_a_noop() -> None: """Пустой токен — клиента в lifespan не создавали, закрывать нечего.""" assert shared._client is None await shared.close_telegram_client() assert shared._client is None