feat: add generic send_message method to TelegramClient (issue #59) #60

Merged
jihoson merged 1 commits from feature/issue-59-send-message into main 2026-02-05 13:40:06 +09:00
2 changed files with 99 additions and 13 deletions
Showing only changes of commit 259f9d2e24 - Show all commits

View File

@@ -117,26 +117,28 @@ class TelegramClient:
if self._session is not None and not self._session.closed:
await self._session.close()
async def _send_notification(self, msg: NotificationMessage) -> None:
async def send_message(self, text: str, parse_mode: str = "HTML") -> bool:
"""
Send notification to Telegram with graceful degradation.
Send a generic text message to Telegram.
Args:
msg: Notification message to send
text: Message text to send
parse_mode: Parse mode for formatting (HTML or Markdown)
Returns:
True if message was sent successfully, False otherwise
"""
if not self._enabled:
return
return False
try:
await self._rate_limiter.acquire()
formatted_message = f"{msg.priority.emoji} {msg.message}"
url = f"{self.API_BASE.format(token=self._bot_token)}/sendMessage"
payload = {
"chat_id": self._chat_id,
"text": formatted_message,
"parse_mode": "HTML",
"text": text,
"parse_mode": parse_mode,
}
session = self._get_session()
@@ -146,15 +148,29 @@ class TelegramClient:
logger.error(
"Telegram API error (status=%d): %s", resp.status, error_text
)
else:
logger.debug("Telegram notification sent: %s", msg.message[:50])
return False
logger.debug("Telegram message sent: %s", text[:50])
return True
except asyncio.TimeoutError:
logger.error("Telegram notification timeout")
logger.error("Telegram message timeout")
return False
except aiohttp.ClientError as exc:
logger.error("Telegram notification failed: %s", exc)
logger.error("Telegram message failed: %s", exc)
return False
except Exception as exc:
logger.error("Unexpected error sending notification: %s", exc)
logger.error("Unexpected error sending message: %s", exc)
return False
async def _send_notification(self, msg: NotificationMessage) -> None:
"""
Send notification to Telegram with graceful degradation.
Args:
msg: Notification message to send
"""
formatted_message = f"{msg.priority.emoji} {msg.message}"
await self.send_message(formatted_message)
async def notify_trade_execution(
self,

View File

@@ -39,6 +39,76 @@ class TestTelegramClientInit:
class TestNotificationSending:
"""Test notification sending behavior."""
@pytest.mark.asyncio
async def test_send_message_success(self) -> None:
"""send_message returns True on successful send."""
client = TelegramClient(
bot_token="123:abc", chat_id="456", enabled=True
)
mock_resp = AsyncMock()
mock_resp.status = 200
mock_resp.__aenter__ = AsyncMock(return_value=mock_resp)
mock_resp.__aexit__ = AsyncMock(return_value=False)
with patch("aiohttp.ClientSession.post", return_value=mock_resp) as mock_post:
result = await client.send_message("Test message")
assert result is True
assert mock_post.call_count == 1
payload = mock_post.call_args.kwargs["json"]
assert payload["chat_id"] == "456"
assert payload["text"] == "Test message"
assert payload["parse_mode"] == "HTML"
@pytest.mark.asyncio
async def test_send_message_disabled_client(self) -> None:
"""send_message returns False when client disabled."""
client = TelegramClient(enabled=False)
with patch("aiohttp.ClientSession.post") as mock_post:
result = await client.send_message("Test message")
assert result is False
mock_post.assert_not_called()
@pytest.mark.asyncio
async def test_send_message_api_error(self) -> None:
"""send_message returns False on API error."""
client = TelegramClient(
bot_token="123:abc", chat_id="456", enabled=True
)
mock_resp = AsyncMock()
mock_resp.status = 400
mock_resp.text = AsyncMock(return_value="Bad Request")
mock_resp.__aenter__ = AsyncMock(return_value=mock_resp)
mock_resp.__aexit__ = AsyncMock(return_value=False)
with patch("aiohttp.ClientSession.post", return_value=mock_resp):
result = await client.send_message("Test message")
assert result is False
@pytest.mark.asyncio
async def test_send_message_with_markdown(self) -> None:
"""send_message supports different parse modes."""
client = TelegramClient(
bot_token="123:abc", chat_id="456", enabled=True
)
mock_resp = AsyncMock()
mock_resp.status = 200
mock_resp.__aenter__ = AsyncMock(return_value=mock_resp)
mock_resp.__aexit__ = AsyncMock(return_value=False)
with patch("aiohttp.ClientSession.post", return_value=mock_resp) as mock_post:
result = await client.send_message("*bold*", parse_mode="Markdown")
assert result is True
payload = mock_post.call_args.kwargs["json"]
assert payload["parse_mode"] == "Markdown"
@pytest.mark.asyncio
async def test_no_send_when_disabled(self) -> None:
"""Notifications not sent when client disabled."""