import os
import sys
from pathlib import Path
import pytest
import asyncio
from unittest.mock import MagicMock, patch, AsyncMock
sys.path.append(str(Path(__file__).parent.parent))
from telegram_bot import TelegramBot
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
# Explicitly pass token and chat ID to ensure they are set
@pytest.fixture(autouse=True)
def mock_env_vars():
os.environ["TELEGRAM_BOT_TOKEN"] = "test_token"
os.environ["TELEGRAM_CHAT_ID"] = "test_chat_id"
@pytest.fixture
def mock_monitor():
monitor = MagicMock()
monitor.load_state.return_value = {"autopilot": True}
monitor.load_applications.return_value = {
"app1": {"company": "CompanyA"},
"app2": {"company": "CompanyB"},
"app3": {"company": "CompanyA"},
}
return monitor
@pytest.fixture
def telegram_bot(mock_monitor):
event_loop = asyncio.new_event_loop()
return TelegramBot(mock_monitor, bot_token="test_token", chat_id="test_chat_id", event_loop=event_loop)
@pytest.mark.asyncio
@patch("httpx.AsyncClient.post")
async def test_send_message(mock_post, telegram_bot):
mock_response = AsyncMock()
mock_response.status_code = 200
mock_post.return_value = mock_response
await telegram_bot._send_message("Test message")
mock_post.assert_called_once()
call_kwargs = mock_post.call_args[1]
assert call_kwargs["json"]["text"] == "Test message"
@pytest.mark.asyncio
@patch("httpx.AsyncClient.post")
async def test_send_photo(mock_post, telegram_bot):
mock_response = AsyncMock()
mock_response.status_code = 200
mock_post.return_value = mock_response
with patch("builtins.open", create=True):
await telegram_bot._send_photo("/path/to/photo.jpg", "Test caption")
mock_post.assert_called_once()
call_kwargs = mock_post.call_args[1]
assert call_kwargs["data"]["caption"] == "Test caption"
@pytest.mark.asyncio
@patch("telegram_bot.TelegramBot._send_message")
async def test_handle_status_command(mock_send_message, telegram_bot):
mock_send_message.return_value = asyncio.Future()
mock_send_message.return_value.set_result(None)
await telegram_bot._handle_status_command()
mock_send_message.assert_called_once()
assert "Autopilot" in mock_send_message.call_args[0][0]
@pytest.mark.asyncio
@patch("telegram_bot.TelegramBot._send_message")
async def test_handle_help_command(mock_send_message, telegram_bot):
mock_send_message.return_value = asyncio.Future()
mock_send_message.return_value.set_result(None)
await telegram_bot._handle_help_command()
mock_send_message.assert_called_once()
assert "Available commands" in mock_send_message.call_args[0][0]
@pytest.mark.asyncio
@patch("telegram_bot.TelegramBot._send_message")
async def test_handle_unknown_command(mock_send_message, telegram_bot):
mock_send_message.return_value = asyncio.Future()
mock_send_message.return_value.set_result(None)
await telegram_bot._handle_unknown_command("/unknown")
mock_send_message.assert_called_once()
assert "Unknown command" in mock_send_message.call_args[0][0]
@pytest.mark.asyncio
@patch("telegram_bot.TelegramBot._send_photo")
@patch("telegram_bot.TelegramBot._send_message")
async def test_handle_plot_command(mock_send_message, mock_send_photo, telegram_bot):
mock_send_photo.return_value = asyncio.Future()
mock_send_photo.return_value.set_result(None)
telegram_bot.app_handler._generate_weekly_plot = MagicMock(return_value="/path/to/plot.png")
await telegram_bot._handle_plot_command()
mock_send_photo.assert_called_once_with("/path/to/plot.png", "📊 Weekly Listing Patterns\n\nThis shows when new listings typically appear throughout the week.")
@pytest.mark.asyncio
@patch("telegram_bot.TelegramBot._send_photo")
@patch("telegram_bot.TelegramBot._send_message")
async def test_handle_plot_command_no_data(mock_send_message, mock_send_photo, telegram_bot):
mock_send_message.return_value = asyncio.Future()
mock_send_message.return_value.set_result(None)
mock_send_photo.return_value = asyncio.Future()
mock_send_photo.return_value.set_result(None)
telegram_bot.app_handler._generate_weekly_plot = MagicMock(return_value="")
await telegram_bot._handle_plot_command()
# When plot generation returns empty string, _send_photo is attempted but fails, not _send_message
mock_send_photo.assert_called_once()
@pytest.mark.asyncio
@patch("telegram_bot.TelegramBot._send_photo")
@patch("telegram_bot.TelegramBot._send_message")
async def test_handle_error_rate_command(mock_send_message, mock_send_photo, telegram_bot):
mock_send_photo.return_value = asyncio.Future()
mock_send_photo.return_value.set_result(None)
telegram_bot.app_handler._generate_error_rate_plot = MagicMock(return_value=("/path/to/error_rate.png", "Summary text"))
await telegram_bot._handle_error_rate_command()
mock_send_photo.assert_called_once_with("/path/to/error_rate.png", "📉 Autopilot Success vs Failure\n\nSummary text")
@pytest.mark.asyncio
@patch("telegram_bot.TelegramBot._send_photo")
@patch("telegram_bot.TelegramBot._send_message")
async def test_handle_error_rate_command_no_data(mock_send_message, mock_send_photo, telegram_bot):
mock_send_message.return_value = asyncio.Future()
mock_send_message.return_value.set_result(None)
mock_send_photo.return_value = asyncio.Future()
mock_send_photo.return_value.set_result(None)
telegram_bot.app_handler._generate_error_rate_plot = MagicMock(return_value=("", ""))
await telegram_bot._handle_error_rate_command()
# When plot generation returns empty string, _send_photo is attempted but fails
mock_send_photo.assert_called_once()
@pytest.mark.asyncio
@patch("telegram_bot.TelegramBot._send_message")
async def test_handle_autopilot_on_command(mock_send_message, telegram_bot):
"""Test enabling autopilot via command."""
mock_send_message.return_value = asyncio.Future()
mock_send_message.return_value.set_result(None)
telegram_bot.monitor.set_autopilot = MagicMock()
await telegram_bot._handle_autopilot_command("/autopilot on")
telegram_bot.monitor.set_autopilot.assert_called_once_with(True)
mock_send_message.assert_called()
@pytest.mark.asyncio
@patch("telegram_bot.TelegramBot._send_message")
async def test_handle_autopilot_off_command(mock_send_message, telegram_bot):
"""Test disabling autopilot via command."""
mock_send_message.return_value = asyncio.Future()
mock_send_message.return_value.set_result(None)
telegram_bot.monitor.set_autopilot = MagicMock()
await telegram_bot._handle_autopilot_command("/autopilot off")
telegram_bot.monitor.set_autopilot.assert_called_once_with(False)
mock_send_message.assert_called()
@pytest.mark.asyncio
@patch("telegram_bot.TelegramBot._send_message")
async def test_handle_retry_failed_command(mock_send_message, telegram_bot):
"""Test retry failed applications command."""
mock_send_message.return_value = asyncio.Future()
mock_send_message.return_value.set_result(None)
# Mock load_applications to return properly structured failed application
telegram_bot.app_handler.load_applications = MagicMock(return_value={
"id1": {
"listing_id": "id1",
"link": "http://example.com",
"success": False,
"retries": 0,
"rooms": "3",
"size": "75 m²",
"price": "1200 €",
"address": "Kreuzberg"
}
})
telegram_bot.app_handler.apply = AsyncMock(return_value={
"success": True,
"message": "Applied successfully"
})
telegram_bot.app_handler.save_application = MagicMock()
await telegram_bot._handle_retry_failed_command()
telegram_bot.app_handler.apply.assert_called_once()
assert mock_send_message.call_count >= 2 # Initial message + results
@pytest.mark.asyncio
@patch("telegram_bot.TelegramBot._send_message")
@patch("os.path.exists")
@patch("shutil.move")
async def test_handle_reset_listings_command(mock_move, mock_exists, mock_send_message, telegram_bot):
"""Test reset listings command."""
mock_send_message.return_value = asyncio.Future()
mock_send_message.return_value.set_result(None)
mock_exists.return_value = True
await telegram_bot._handle_reset_listings_command()
mock_move.assert_called_once()
mock_send_message.assert_called()