"""Tests for Telegram notification service.""" import os import uuid from decimal import Decimal from datetime import datetime, timezone from unittest.mock import AsyncMock, MagicMock, patch import pytest import pytest_asyncio from shared.models import Signal, Order, OrderSide, OrderType, OrderStatus, Position from shared.notifier import TelegramNotifier class TestTelegramNotifierEnabled: """Test the enabled property.""" def test_disabled_when_no_token(self): notifier = TelegramNotifier(bot_token="", chat_id="123") assert notifier.enabled is False def test_enabled_with_token(self): notifier = TelegramNotifier(bot_token="fake-token", chat_id="123") assert notifier.enabled is True def test_disabled_when_token_is_empty_string(self): notifier = TelegramNotifier(bot_token="", chat_id="") assert notifier.enabled is False class TestTelegramNotifierSend: """Test send method.""" @pytest.mark.asyncio async def test_send_does_nothing_when_disabled(self): notifier = TelegramNotifier(bot_token="", chat_id="123") # Should not raise or do anything await notifier.send("test message") @pytest.mark.asyncio async def test_send_posts_to_api(self): notifier = TelegramNotifier(bot_token="fake-token", chat_id="12345") mock_response = AsyncMock() mock_response.status = 200 mock_response.json = AsyncMock(return_value={"ok": True}) mock_response.__aenter__ = AsyncMock(return_value=mock_response) mock_response.__aexit__ = AsyncMock(return_value=False) mock_session = AsyncMock() mock_session.post = MagicMock(return_value=mock_response) with patch.object(notifier, "_session", mock_session): await notifier.send("Hello, world!") mock_session.post.assert_called_once() call_args = mock_session.post.call_args assert "fake-token" in call_args[0][0] assert call_args[1]["json"]["chat_id"] == "12345" assert call_args[1]["json"]["text"] == "Hello, world!" assert call_args[1]["json"]["parse_mode"] == "HTML" @pytest.mark.asyncio async def test_send_with_custom_parse_mode(self): notifier = TelegramNotifier(bot_token="fake-token", chat_id="12345") mock_response = AsyncMock() mock_response.status = 200 mock_response.json = AsyncMock(return_value={"ok": True}) mock_response.__aenter__ = AsyncMock(return_value=mock_response) mock_response.__aexit__ = AsyncMock(return_value=False) mock_session = AsyncMock() mock_session.post = MagicMock(return_value=mock_response) with patch.object(notifier, "_session", mock_session): await notifier.send("test", parse_mode="Markdown") call_args = mock_session.post.call_args assert call_args[1]["json"]["parse_mode"] == "Markdown" class TestTelegramNotifierFormatters: """Test message formatting methods.""" @pytest.mark.asyncio async def test_send_signal_formats_message(self): notifier = TelegramNotifier(bot_token="fake-token", chat_id="123") signal = Signal( strategy="rsi_strategy", symbol="BTCUSDT", side=OrderSide.BUY, price=Decimal("50000.00"), quantity=Decimal("0.01"), reason="RSI oversold", ) with patch.object(notifier, "send", new_callable=AsyncMock) as mock_send: await notifier.send_signal(signal) mock_send.assert_called_once() msg = mock_send.call_args[0][0] assert "BUY" in msg assert "rsi_strategy" in msg assert "BTCUSDT" in msg assert "50000.00" in msg assert "0.01" in msg assert "RSI oversold" in msg @pytest.mark.asyncio async def test_send_order_formats_message(self): notifier = TelegramNotifier(bot_token="fake-token", chat_id="123") order = Order( signal_id=str(uuid.uuid4()), symbol="ETHUSDT", side=OrderSide.SELL, type=OrderType.LIMIT, price=Decimal("3000.50"), quantity=Decimal("1.5"), status=OrderStatus.FILLED, ) with patch.object(notifier, "send", new_callable=AsyncMock) as mock_send: await notifier.send_order(order) mock_send.assert_called_once() msg = mock_send.call_args[0][0] assert "FILLED" in msg assert "ETHUSDT" in msg assert "SELL" in msg assert "3000.50" in msg assert "1.5" in msg @pytest.mark.asyncio async def test_send_error_formats_message(self): notifier = TelegramNotifier(bot_token="fake-token", chat_id="123") with patch.object(notifier, "send", new_callable=AsyncMock) as mock_send: await notifier.send_error("Connection failed", service="executor") mock_send.assert_called_once() msg = mock_send.call_args[0][0] assert "Connection failed" in msg assert "executor" in msg @pytest.mark.asyncio async def test_send_daily_summary_formats_message(self): notifier = TelegramNotifier(bot_token="fake-token", chat_id="123") positions = [ Position( symbol="BTCUSDT", quantity=Decimal("0.1"), avg_entry_price=Decimal("50000"), current_price=Decimal("51000"), ), ] with patch.object(notifier, "send", new_callable=AsyncMock) as mock_send: await notifier.send_daily_summary( positions=positions, total_value=Decimal("5100.00"), daily_pnl=Decimal("100.00"), ) mock_send.assert_called_once() msg = mock_send.call_args[0][0] assert "BTCUSDT" in msg assert "5100.00" in msg assert "100.00" in msg class TestTelegramNotifierClose: """Test close method.""" @pytest.mark.asyncio async def test_close_closes_session(self): notifier = TelegramNotifier(bot_token="fake-token", chat_id="123") mock_session = AsyncMock() notifier._session = mock_session await notifier.close() mock_session.close.assert_called_once()