Merge pull request 'feat: add generic send_message method to TelegramClient (issue #59)' (#60) from feature/issue-59-send-message into main
Some checks failed
CI / test (push) Has been cancelled
Some checks failed
CI / test (push) Has been cancelled
Reviewed-on: #60
This commit was merged in pull request #60.
This commit is contained in:
@@ -117,26 +117,28 @@ class TelegramClient:
|
|||||||
if self._session is not None and not self._session.closed:
|
if self._session is not None and not self._session.closed:
|
||||||
await self._session.close()
|
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:
|
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:
|
if not self._enabled:
|
||||||
return
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self._rate_limiter.acquire()
|
await self._rate_limiter.acquire()
|
||||||
|
|
||||||
formatted_message = f"{msg.priority.emoji} {msg.message}"
|
|
||||||
url = f"{self.API_BASE.format(token=self._bot_token)}/sendMessage"
|
url = f"{self.API_BASE.format(token=self._bot_token)}/sendMessage"
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"chat_id": self._chat_id,
|
"chat_id": self._chat_id,
|
||||||
"text": formatted_message,
|
"text": text,
|
||||||
"parse_mode": "HTML",
|
"parse_mode": parse_mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
session = self._get_session()
|
session = self._get_session()
|
||||||
@@ -146,15 +148,29 @@ class TelegramClient:
|
|||||||
logger.error(
|
logger.error(
|
||||||
"Telegram API error (status=%d): %s", resp.status, error_text
|
"Telegram API error (status=%d): %s", resp.status, error_text
|
||||||
)
|
)
|
||||||
else:
|
return False
|
||||||
logger.debug("Telegram notification sent: %s", msg.message[:50])
|
logger.debug("Telegram message sent: %s", text[:50])
|
||||||
|
return True
|
||||||
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
logger.error("Telegram notification timeout")
|
logger.error("Telegram message timeout")
|
||||||
|
return False
|
||||||
except aiohttp.ClientError as exc:
|
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:
|
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(
|
async def notify_trade_execution(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -39,6 +39,76 @@ class TestTelegramClientInit:
|
|||||||
class TestNotificationSending:
|
class TestNotificationSending:
|
||||||
"""Test notification sending behavior."""
|
"""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
|
@pytest.mark.asyncio
|
||||||
async def test_no_send_when_disabled(self) -> None:
|
async def test_no_send_when_disabled(self) -> None:
|
||||||
"""Notifications not sent when client disabled."""
|
"""Notifications not sent when client disabled."""
|
||||||
|
|||||||
Reference in New Issue
Block a user