"""Bot /profile command unit tests (ticket 005). - ApiClient.get_profile contract against GET /api/v1/me/profile (respx) - _format_profile pretty-printer (pure function) The bot stays HTTP-thin; test_bot_boundary.py enforces no direct DB access. """ from __future__ import annotations import httpx import pytest import respx from contract_check.bot.client import ApiClient, ApiError from contract_check.bot.config import BotSettings from contract_check.bot.handlers import _format_profile BASE = "http://api:8000" _FAKE_JWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDAiLCJ0ZWxlZ3JhbV9pZCI6NDIsInR5cGUiOiJhY2Nlc3MiLCJleHAiOjk5OTk5OTk5OTksImlhdCI6MSwiYXVkIjoiY29udHJhY3QtY2hlY2sifQ.test-signature" def _client() -> ApiClient: c = ApiClient(BotSettings(bot_token="123:abc", api_url=BASE, bot_service_token="secret-token")) c._client = httpx.AsyncClient(base_url=BASE) return c @respx.mock async def test_get_profile_returns_body() -> None: api = _client() api._token_cache[42] = _FAKE_JWT route = respx.get(f"{BASE}/api/v1/me/profile").mock( return_value=httpx.Response( 200, json={ "language": "ru", "timezone": "Europe/Minsk", "notif_prefs": { "report_ready": True, "security": False, "marketing": False, }, "dashboard_prefs": { "severity_filter": "all", "per_page": 10, "density": "comfortable", }, }, ) ) body = await api.get_profile(42, "cid") assert route.called assert body["language"] == "ru" assert body["notif_prefs"]["security"] is False await api.aclose() @respx.mock async def test_get_profile_raises_on_error() -> None: api = _client() api._token_cache[42] = _FAKE_JWT respx.get(f"{BASE}/api/v1/me/profile").mock( return_value=httpx.Response(500, json={"detail": "boom"}) ) with pytest.raises(ApiError): await api.get_profile(42, "cid") await api.aclose() class TestFormatProfile: def test_full_profile(self) -> None: text = _format_profile( { "language": "ru", "timezone": "Europe/Minsk", "notif_prefs": { "report_ready": True, "security": False, "marketing": False, }, "dashboard_prefs": {"per_page": 10}, } ) assert "Русский" in text assert "Europe/Minsk" in text assert "Отчёты готовы: ✔" in text assert "Безопасность: ✘" in text assert "Маркетинг: ✘" in text assert "веб-интерфейс" in text def test_defaults_when_unset(self) -> None: text = _format_profile({"language": None, "timezone": None, "notif_prefs": {}}) assert "не выбран" in text # Missing toggles render as off. assert "Отчёты готовы: ✘" in text def test_belarusian_language_label(self) -> None: text = _format_profile({"language": "be"}) assert "Беларуская" in text