chore: update development dependencies and improve test coverage
Some checks failed
CI / lint-and-test (push) Failing after 11s
Some checks failed
CI / lint-and-test (push) Failing after 11s
- Upgraded `pytest-asyncio` to version 1.0 to ensure compatibility with the latest features and improvements. - Increased the coverage threshold in pytest configuration to 80%, enhancing the quality assurance process. - Added a new `conftest.py` file to manage shared fixtures and improve test organization. - Introduced multiple new test files to cover various components, ensuring comprehensive test coverage across the application. - Updated the `.coverage` file to reflect the latest coverage metrics.
This commit is contained in:
119
tests/test_run.py
Normal file
119
tests/test_run.py
Normal file
@@ -0,0 +1,119 @@
|
||||
"""Tests for duty_teller.run (main entry point with mocks)."""
|
||||
|
||||
import asyncio
|
||||
from unittest.mock import AsyncMock, 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()
|
||||
Reference in New Issue
Block a user