diff --git a/language_tool_python/download_lt.py b/language_tool_python/download_lt.py index f08ee24..d74d005 100755 --- a/language_tool_python/download_lt.py +++ b/language_tool_python/download_lt.py @@ -172,7 +172,8 @@ def http_get( :param proxies: Optional dictionary of proxies to use for the request. :type proxies: Optional[Dict[str, str]] :raises TimeoutError: If the request times out. - :raises PathError: If the file could not be found at the given URL (HTTP 404). + :raises PathError: If the file could not be found at the given URL (HTTP 404), + if access is forbidden (HTTP 403), or for other HTTP errors. """ logger.info("Starting download from %s", url) try: @@ -185,6 +186,12 @@ def http_get( if req.status_code == 404: err = f"Could not find at URL {url}. The given version may not exist or is no longer available." raise PathError(err) + if req.status_code == 403: + err = f"Access forbidden to URL {url}. You may not have permission to access this resource. It may be related to network restrictions (e.g., firewall, proxy settings)." + raise PathError(err) + if req.status_code != 200: + err = f"Failed to download from {url}. HTTP status code: {req.status_code}." + raise PathError(err) version = ( url.split("/")[-1].split("-")[1].replace("-snapshot", "").replace(".zip", "") ) diff --git a/tests/test_download.py b/tests/test_download.py index fdef1a5..4bcdb35 100644 --- a/tests/test_download.py +++ b/tests/test_download.py @@ -1,8 +1,12 @@ """Tests for the download/language functionality of LanguageTool.""" +import io +from unittest.mock import MagicMock, patch + import pytest -from language_tool_python.exceptions import LanguageToolError +from language_tool_python.download_lt import http_get +from language_tool_python.exceptions import LanguageToolError, PathError def test_install_inexistent_version() -> None: @@ -31,3 +35,51 @@ def test_inexistent_language() -> None: with language_tool_python.LanguageTool("en-US") as tool, pytest.raises(ValueError): language_tool_python.LanguageTag("xx-XX", tool._get_languages()) + + +def test_http_get_403_forbidden() -> None: + """ + Test that http_get raises PathError when receiving a 403 Forbidden status code. + This test verifies that the function correctly handles forbidden access errors + when attempting to download files. + + :raises AssertionError: If PathError is not raised for a 403 status code. + """ + mock_response = MagicMock() + mock_response.status_code = 403 + mock_response.headers = {} + + with ( + patch( + "language_tool_python.download_lt.requests.get", return_value=mock_response + ), + pytest.raises(PathError, match="Access forbidden to URL"), + ): + out_file = io.BytesIO() + http_get("https://example.com/test.zip", out_file) + + +def test_http_get_other_error_codes() -> None: + """ + Test that http_get raises PathError for various HTTP error codes (other than 404 and 403). + This test verifies that the function correctly handles different HTTP error codes + like 500 (Internal Server Error), 503 (Service Unavailable), etc. + + :raises AssertionError: If PathError is not raised for error status codes. + """ + error_codes = [500, 502, 503, 504] + + for error_code in error_codes: + mock_response = MagicMock() + mock_response.status_code = error_code + mock_response.headers = {} + + with ( + patch( + "language_tool_python.download_lt.requests.get", + return_value=mock_response, + ), + pytest.raises(PathError, match=f"Failed to download.*{error_code}"), + ): + out_file = io.BytesIO() + http_get("https://example.com/test.zip", out_file)