"""Tests for duty_teller.utils.http_client (safe_urlopen scheme check).""" from unittest.mock import MagicMock, patch import pytest from urllib.request import Request from duty_teller.utils.http_client import safe_urlopen def test_safe_urlopen_rejects_file_scheme(): """safe_urlopen: file:// URL raises ValueError and does not call urlopen.""" req = Request("file:///etc/passwd") with patch("duty_teller.utils.http_client.urlopen") as mock_urlopen: with pytest.raises(ValueError, match="scheme not allowed"): with safe_urlopen(req, timeout=5): pass mock_urlopen.assert_not_called() def test_safe_urlopen_rejects_ftp_scheme(): """safe_urlopen: ftp:// URL raises ValueError and does not call urlopen.""" req = Request("ftp://example.com/file.txt") with patch("duty_teller.utils.http_client.urlopen") as mock_urlopen: with pytest.raises(ValueError, match="scheme not allowed"): with safe_urlopen(req, timeout=5): pass mock_urlopen.assert_not_called() def test_safe_urlopen_allows_https(): """safe_urlopen: https:// URL is allowed and urlopen is called.""" req = Request("https://api.telegram.org/bot123/setChatMenuButton") mock_resp = MagicMock() mock_resp.__enter__ = MagicMock(return_value=mock_resp) mock_resp.__exit__ = MagicMock(return_value=False) with patch( "duty_teller.utils.http_client.urlopen", return_value=mock_resp ) as mock_urlopen: with safe_urlopen(req, timeout=10) as resp: assert resp is mock_resp mock_urlopen.assert_called_once_with(req, timeout=10) def test_safe_urlopen_allows_http_when_in_allowed_schemes(): """safe_urlopen: http:// URL is allowed when allowed_schemes includes http.""" req = Request("http://localhost:8080/health") mock_resp = MagicMock() mock_resp.__enter__ = MagicMock(return_value=mock_resp) mock_resp.__exit__ = MagicMock(return_value=False) with patch( "duty_teller.utils.http_client.urlopen", return_value=mock_resp ) as mock_urlopen: with safe_urlopen(req, timeout=5, allowed_schemes=("https", "http")) as resp: assert resp is mock_resp mock_urlopen.assert_called_once_with(req, timeout=5)