Files
duty-teller/tests/test_run.py
Nikolay Tatarinov aa89494bd5
All checks were successful
CI / lint-and-test (push) Successful in 22s
feat: enhance calendar ICS generation with event type filtering
- Added support for filtering calendar events by type in the ICS generation API endpoint, allowing users to specify whether to include only duty shifts or all event types (duty, unavailable, vacation).
- Updated the `get_duties_for_user` function to accept an optional `event_types` parameter, enabling more flexible data retrieval based on user preferences.
- Enhanced unit tests to cover the new event type filtering functionality, ensuring correct behavior and reliability of the ICS generation process.
2026-02-20 17:47:52 +03:00

120 lines
5.2 KiB
Python

"""Tests for duty_teller.run (main entry point with mocks)."""
import asyncio
from unittest.mock import MagicMock, patch
import pytest
from duty_teller.run import main, _run_uvicorn, _set_default_menu_button_webapp
def test_main_builds_app_and_starts_thread():
"""main: calls require_bot_token, builds Application, register_handlers, starts uvicorn thread, run_polling."""
mock_app = MagicMock()
mock_app.run_polling = MagicMock(side_effect=KeyboardInterrupt())
mock_builder = MagicMock()
mock_builder.token.return_value = mock_builder
mock_builder.post_init.return_value = mock_builder
mock_builder.build.return_value = mock_app
# Avoid loading real DB when main() imports api.app (prevents unclosed connection warnings).
mock_session = MagicMock()
mock_scope = MagicMock()
mock_scope.return_value.__enter__.return_value = mock_session
mock_scope.return_value.__exit__.return_value = None
with patch("duty_teller.run.require_bot_token"):
with patch("duty_teller.run.ApplicationBuilder", return_value=mock_builder):
with patch("duty_teller.run.register_handlers") as mock_register:
with patch("duty_teller.run.threading.Thread") as mock_thread_class:
with patch("duty_teller.db.session.session_scope", mock_scope):
mock_thread = MagicMock()
mock_thread_class.return_value = mock_thread
with pytest.raises(KeyboardInterrupt):
main()
mock_register.assert_called_once_with(mock_app)
mock_builder.token.assert_called_once()
mock_thread.start.assert_called_once()
mock_app.run_polling.assert_called_once()
def test_run_uvicorn_creates_server():
"""_run_uvicorn: creates uvicorn config and runs server (mocked)."""
with patch("uvicorn.Server") as mock_server_class:
mock_server = MagicMock()
# run_until_complete needs a real coroutine
mock_server.serve = MagicMock(return_value=asyncio.sleep(0))
mock_server_class.return_value = mock_server
_run_uvicorn(MagicMock(), 8080)
mock_server_class.assert_called_once()
mock_server.serve.assert_called_once()
def test_set_default_menu_button_skips_when_no_base_url():
"""_set_default_menu_button_webapp: returns early when MINI_APP_BASE_URL or BOT_TOKEN not set."""
with patch("duty_teller.run.config") as mock_cfg:
mock_cfg.MINI_APP_BASE_URL = ""
mock_cfg.BOT_TOKEN = "token"
_set_default_menu_button_webapp()
mock_cfg.MINI_APP_BASE_URL = "https://example.com"
mock_cfg.BOT_TOKEN = ""
_set_default_menu_button_webapp()
# No urlopen should be called
with patch("duty_teller.run.config") as mock_cfg:
mock_cfg.MINI_APP_BASE_URL = None
mock_cfg.BOT_TOKEN = "x"
_set_default_menu_button_webapp()
def test_set_default_menu_button_skips_when_not_https():
"""_set_default_menu_button_webapp: returns early when menu_url does not start with https."""
with patch("duty_teller.run.config") as mock_cfg:
mock_cfg.MINI_APP_BASE_URL = "http://example.com"
mock_cfg.BOT_TOKEN = "token"
_set_default_menu_button_webapp()
# No urlopen
def test_set_default_menu_button_calls_telegram_api():
"""_set_default_menu_button_webapp: when URL is https, calls Telegram API (mocked)."""
with patch("duty_teller.run.config") as mock_cfg:
mock_cfg.MINI_APP_BASE_URL = "https://example.com/"
mock_cfg.BOT_TOKEN = "123:ABC"
with patch("urllib.request.urlopen") as mock_urlopen:
mock_resp = MagicMock()
mock_resp.status = 200
mock_resp.__enter__ = MagicMock(return_value=mock_resp)
mock_resp.__exit__ = MagicMock(return_value=False)
mock_urlopen.return_value = mock_resp
_set_default_menu_button_webapp()
mock_urlopen.assert_called_once()
req = mock_urlopen.call_args[0][0]
assert "setChatMenuButton" in req.full_url
assert "123:ABC" in req.full_url
def test_set_default_menu_button_handles_urlopen_error():
"""_set_default_menu_button_webapp: on urlopen error, logs and does not raise."""
with patch("duty_teller.run.config") as mock_cfg:
mock_cfg.MINI_APP_BASE_URL = "https://example.com/"
mock_cfg.BOT_TOKEN = "123:ABC"
with patch("urllib.request.urlopen") as mock_urlopen:
mock_urlopen.side_effect = OSError("network error")
_set_default_menu_button_webapp()
mock_urlopen.assert_called_once()
def test_set_default_menu_button_handles_non_200_response():
"""_set_default_menu_button_webapp: on non-200 response, logs warning."""
with patch("duty_teller.run.config") as mock_cfg:
mock_cfg.MINI_APP_BASE_URL = "https://example.com/"
mock_cfg.BOT_TOKEN = "123:ABC"
with patch("urllib.request.urlopen") as mock_urlopen:
mock_resp = MagicMock()
mock_resp.status = 400
mock_resp.__enter__ = MagicMock(return_value=mock_resp)
mock_resp.__exit__ = MagicMock(return_value=False)
mock_urlopen.return_value = mock_resp
_set_default_menu_button_webapp()
mock_urlopen.assert_called_once()