Files
roboco/tests/unit/services/test_telegram_client.py
T

219 lines
8.4 KiB
Python
Raw Normal View History

"""TelegramClient coverage: NullTelegramClient no-op, LiveTelegramClient calls."""
from __future__ import annotations
import json
import httpx
import pytest
from roboco.services.telegram_client import (
LiveTelegramClient,
NullTelegramClient,
build_telegram_client,
)
from roboco.services.telegram_credentials import TelegramCredentialsData
_CREDS = TelegramCredentialsData(bot_token="123456:ABC", chat_id="987654321")
def test_null_client_is_unconfigured() -> None:
client = build_telegram_client(None, timeout=5.0)
assert isinstance(client, NullTelegramClient)
assert client.configured is False
@pytest.mark.asyncio
async def test_null_client_send_message_is_a_noop() -> None:
client: NullTelegramClient = NullTelegramClient()
result = await client.send_message("hello")
assert result.sent is False
assert result.detail # non-empty reason
def test_build_telegram_client_with_creds_returns_live_client() -> None:
client = build_telegram_client(_CREDS, timeout=5.0)
assert isinstance(client, LiveTelegramClient)
assert client.configured is True
@pytest.mark.asyncio
async def test_live_client_send_message_success() -> None:
def handler(request: httpx.Request) -> httpx.Response:
assert request.url.path == "/bot123456:ABC/sendMessage"
body = json.loads(request.content.decode())
assert body == {"chat_id": _CREDS.chat_id, "text": "hi"}
return httpx.Response(200, json={"ok": True, "result": {"message_id": 1}})
transport = httpx.MockTransport(handler)
http_client = httpx.AsyncClient(transport=transport)
client = LiveTelegramClient(_CREDS, timeout=5.0, client=http_client)
result = await client.send_message("hi")
assert result.sent is True
await client.close()
@pytest.mark.asyncio
async def test_live_client_send_message_http_error_is_graceful() -> None:
def handler(_request: httpx.Request) -> httpx.Response:
return httpx.Response(401, text="unauthorized")
transport = httpx.MockTransport(handler)
http_client = httpx.AsyncClient(transport=transport)
client = LiveTelegramClient(_CREDS, timeout=5.0, client=http_client)
result = await client.send_message("hi")
assert result.sent is False
assert "401" in result.detail
await client.close()
_REPLY_TO_MESSAGE_ID = 42
_SENT_MESSAGE_ID = 7
@pytest.mark.asyncio
async def test_live_client_send_message_with_reply_markup_and_message_id() -> None:
def handler(request: httpx.Request) -> httpx.Response:
body = json.loads(request.content.decode())
assert body["reply_markup"] == {"force_reply": True}
assert body["reply_to_message_id"] == _REPLY_TO_MESSAGE_ID
return httpx.Response(
200, json={"ok": True, "result": {"message_id": _SENT_MESSAGE_ID}}
)
transport = httpx.MockTransport(handler)
http_client = httpx.AsyncClient(transport=transport)
client = LiveTelegramClient(_CREDS, timeout=5.0, client=http_client)
result = await client.send_message(
"hi",
reply_markup={"force_reply": True},
reply_to_message_id=_REPLY_TO_MESSAGE_ID,
)
assert result.sent is True
assert result.message_id == _SENT_MESSAGE_ID
await client.close()
@pytest.mark.asyncio
async def test_live_client_send_message_with_formatting_fields() -> None:
def handler(request: httpx.Request) -> httpx.Response:
body = json.loads(request.content.decode())
assert body["parse_mode"] == "HTML"
assert body["link_preview_options"] == {"is_disabled": True}
return httpx.Response(200, json={"ok": True, "result": {"message_id": 1}})
transport = httpx.MockTransport(handler)
http_client = httpx.AsyncClient(transport=transport)
client = LiveTelegramClient(_CREDS, timeout=5.0, client=http_client)
result = await client.send_message(
"<b>hi</b>", parse_mode="HTML", disable_link_preview=True
)
assert result.sent is True
await client.close()
@pytest.mark.asyncio
async def test_live_client_send_message_omits_formatting_fields_when_unset() -> None:
def handler(request: httpx.Request) -> httpx.Response:
body = json.loads(request.content.decode())
assert "parse_mode" not in body
assert "link_preview_options" not in body
return httpx.Response(200, json={"ok": True, "result": {"message_id": 1}})
transport = httpx.MockTransport(handler)
http_client = httpx.AsyncClient(transport=transport)
client = LiveTelegramClient(_CREDS, timeout=5.0, client=http_client)
result = await client.send_message("hi")
assert result.sent is True
await client.close()
@pytest.mark.asyncio
async def test_live_client_edit_message_text_with_parse_mode() -> None:
def handler(request: httpx.Request) -> httpx.Response:
assert request.url.path == "/bot123456:ABC/editMessageText"
body = json.loads(request.content.decode())
assert body["text"] == "<b>done</b>"
assert body["parse_mode"] == "HTML"
assert body["link_preview_options"] == {"is_disabled": True}
return httpx.Response(200, json={"ok": True, "result": True})
transport = httpx.MockTransport(handler)
http_client = httpx.AsyncClient(transport=transport)
client = LiveTelegramClient(_CREDS, timeout=5.0, client=http_client)
await client.edit_message_text(
7, "<b>done</b>", parse_mode="HTML", disable_link_preview=True
)
await client.close()
@pytest.mark.asyncio
async def test_live_client_get_updates_success() -> None:
def handler(request: httpx.Request) -> httpx.Response:
assert request.url.path == "/bot123456:ABC/getUpdates"
assert request.url.params["timeout"] == "25"
assert request.url.params["offset"] == "10"
return httpx.Response(
200, json={"ok": True, "result": [{"update_id": 10}, {"update_id": 11}]}
)
transport = httpx.MockTransport(handler)
http_client = httpx.AsyncClient(transport=transport)
client = LiveTelegramClient(_CREDS, timeout=5.0, client=http_client)
updates = await client.get_updates(offset=10, timeout=25, limit=50)
assert [u["update_id"] for u in updates] == [10, 11]
await client.close()
@pytest.mark.asyncio
async def test_live_client_get_updates_network_error_returns_empty() -> None:
def handler(_request: httpx.Request) -> httpx.Response:
raise httpx.ConnectError("boom")
transport = httpx.MockTransport(handler)
http_client = httpx.AsyncClient(transport=transport)
client = LiveTelegramClient(_CREDS, timeout=5.0, client=http_client)
updates = await client.get_updates(offset=None, timeout=25, limit=50)
assert updates == []
await client.close()
@pytest.mark.asyncio
async def test_live_client_answer_callback_query_posts_id() -> None:
def handler(request: httpx.Request) -> httpx.Response:
assert request.url.path == "/bot123456:ABC/answerCallbackQuery"
body = json.loads(request.content.decode())
assert body == {"callback_query_id": "cq1", "text": "ok"}
return httpx.Response(200, json={"ok": True, "result": True})
transport = httpx.MockTransport(handler)
http_client = httpx.AsyncClient(transport=transport)
client = LiveTelegramClient(_CREDS, timeout=5.0, client=http_client)
await client.answer_callback_query("cq1", "ok")
await client.close()
@pytest.mark.asyncio
async def test_live_client_edit_message_reply_markup_clears_keyboard() -> None:
def handler(request: httpx.Request) -> httpx.Response:
assert request.url.path == "/bot123456:ABC/editMessageReplyMarkup"
body = json.loads(request.content.decode())
assert body["reply_markup"] == {}
return httpx.Response(200, json={"ok": True, "result": True})
transport = httpx.MockTransport(handler)
http_client = httpx.AsyncClient(transport=transport)
client = LiveTelegramClient(_CREDS, timeout=5.0, client=http_client)
await client.edit_message_reply_markup(7, None)
await client.close()
@pytest.mark.asyncio
async def test_null_client_v2_methods_are_all_noops() -> None:
client: NullTelegramClient = NullTelegramClient()
assert await client.get_updates(offset=None, timeout=25, limit=50) == []
# None of these raise — that's the whole contract.
await client.answer_callback_query("cq1", "text")
await client.edit_message_reply_markup(1, None)
await client.edit_message_text(1, "done", parse_mode="HTML")
await client.send_message("hi", parse_mode="HTML", disable_link_preview=True)