discordBot/tests/test_morning_runner.py
deadzilla 8cd1e48ede fix: устранить RuntimeWarning о не-awaited coroutine в тестах
- TestSchedulerInit: замокать tasks.loop через контекстный менеджер
- TestSchedulerCalculateNextRun: добавить autouse fixture для патча tasks.loop
- TestSchedulerStartStop: добавить autouse fixture для патча tasks.loop
- test_run_morning_sends_embed: патчить fetch_weather/fetch_rss/fetch_cat
  напрямую вместо asyncio.gather, чтобы избежать не-awaited корутин
2026-05-31 12:34:29 +05:00

159 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Тесты для utils/morning_runner.py — Scheduler и run_morning."""
import asyncio
from datetime import datetime
from unittest.mock import AsyncMock, MagicMock, patch, PropertyMock
import discord
import pytest
from utils.morning_runner import Scheduler, run_morning
class TestSchedulerInit:
"""Тесты инициализации Scheduler."""
def test_init_sets_morning_time(self):
"""Инициализация должна устанавливать время."""
bot = AsyncMock()
mock_loop = MagicMock()
with patch("utils.morning_runner.tasks.loop", return_value=mock_loop):
scheduler = Scheduler(bot, "08:30")
assert scheduler.morning_time == "08:30"
def test_init_default_morning_time(self):
"""Инициализация с дефолтным временем."""
bot = AsyncMock()
mock_loop = MagicMock()
with patch("utils.morning_runner.tasks.loop", return_value=mock_loop):
scheduler = Scheduler(bot)
assert scheduler.morning_time == "07:00"
def test_init_creates_loop(self):
"""Инициализация должна создавать loop."""
bot = AsyncMock()
mock_loop = MagicMock()
with patch("utils.morning_runner.tasks.loop", return_value=mock_loop):
scheduler = Scheduler(bot)
assert scheduler.morning_loop is not None
class TestSchedulerCalculateNextRun:
"""Тесты расчёта следующего запуска."""
@pytest.fixture(autouse=True)
def _mock_loop(self):
"""Замокать tasks.loop, чтобы не создавать реальный coroutine."""
with patch("utils.morning_runner.tasks.loop", return_value=MagicMock()):
yield
def test_next_run_today_before_time(self):
"""Если сейчас раньше времени — вернуть сегодня."""
bot = AsyncMock()
scheduler = Scheduler(bot, "14:00")
with patch("utils.morning_runner.datetime") as mock_dt:
mock_dt.now.return_value = datetime(2026, 5, 29, 10, 0, 0)
next_run = scheduler._calculate_next_run()
assert next_run == datetime(2026, 5, 29, 14, 0, 0)
def test_next_run_tomorrow_after_time(self):
"""Если сейчас позже времени — вернуть завтра."""
bot = AsyncMock()
scheduler = Scheduler(bot, "14:00")
with patch("utils.morning_runner.datetime") as mock_dt:
mock_dt.now.return_value = datetime(2026, 5, 29, 15, 0, 0)
next_run = scheduler._calculate_next_run()
assert next_run == datetime(2026, 5, 30, 14, 0, 0)
def test_next_run_exact_time(self):
"""Если сейчас ровно время — вернуть завтра."""
bot = AsyncMock()
scheduler = Scheduler(bot, "14:00")
with patch("utils.morning_runner.datetime") as mock_dt:
mock_dt.now.return_value = datetime(2026, 5, 29, 14, 0, 0)
next_run = scheduler._calculate_next_run()
assert next_run == datetime(2026, 5, 30, 14, 0, 0)
class TestSchedulerStartStop:
"""Тесты запуска/остановки планировщика."""
@pytest.fixture(autouse=True)
def _mock_loop(self):
"""Замокать tasks.loop, чтобы не создавать реальный coroutine."""
with patch("utils.morning_runner.tasks.loop", return_value=MagicMock()):
yield
def test_start_starts_loop(self):
"""start() должен вызывать start() на loop."""
bot = AsyncMock()
scheduler = Scheduler(bot)
loop_mock = MagicMock()
scheduler.morning_loop = loop_mock
scheduler.start()
loop_mock.start.assert_called_once()
def test_stop_stops_loop(self):
"""stop() должен вызывать stop() на loop."""
bot = AsyncMock()
scheduler = Scheduler(bot)
loop_mock = MagicMock()
scheduler.morning_loop = loop_mock
scheduler.stop()
loop_mock.stop.assert_called_once()
class TestSchedulerCheckAndRun:
"""Тесты проверки и запуска morning."""
@pytest.mark.asyncio
async def test_check_and_run_same_day_no_duplicate(self):
"""Не должен запускать дважды в один день."""
bot = AsyncMock()
scheduler = Scheduler(bot, "07:00")
scheduler._last_run_date = datetime.now().day
with patch("utils.morning_runner.datetime") as mock_dt:
mock_dt.now.return_value = datetime(2026, 5, 29, 7, 0, 0)
await scheduler._check_and_run_morning()
# run_morning не должен вызываться
assert scheduler._last_run_date == datetime.now().day
class TestRunMorning:
"""Тесты run_morning."""
@pytest.mark.asyncio
async def test_run_morning_sends_embed(self):
"""run_morning должен отправлять embed в канал."""
bot = AsyncMock()
channel = AsyncMock()
channel.name = "test-channel"
channel.guild.me = MagicMock()
channel.permissions_for.return_value.send_messages = True
weather_data = {
"current_condition": [
{"temp_C": "20", "FeelsLikeC": "22", "weatherDesc": [{"value": "Clear"}], "humidity": "50", "windspeedKmph": "10", "pressure": "1013"}
]
}
articles = [{"title": "Test", "link": "http://test.com", "pub_date": "Mon, 01 Jan 2026 00:00:00 GMT", "creator": "", "tags": []}]
posts = [{"title": "Test", "link": "http://test.com", "pub_date": "Mon, 01 Jan 2026 00:00:00 GMT", "creator": "", "tags": []}]
with patch("utils.morning_runner.fetch_weather", new=AsyncMock(return_value=weather_data)), \
patch("utils.morning_runner.fetch_rss", new=AsyncMock(side_effect=[articles, posts])), \
patch("utils.morning_runner.fetch_cat", new=AsyncMock(return_value="http://cat.jpg")), \
patch("utils.morning_runner.discord.Embed") as mock_embed:
await run_morning(bot, channel)
channel.send.assert_called_once()
call_args = channel.send.call_args[1]
assert "embed" in call_args
assert call_args["embed"] is not None