99 lines
3.3 KiB
Python
99 lines
3.3 KiB
Python
"""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
|