"""Tests for markitect.llm._http.""" import json import urllib.error from unittest import mock import pytest from markitect.llm._http import post_json from markitect.llm.exceptions import LLMAPIError, LLMRateLimitError, LLMTimeoutError class TestPostJson: def _mock_urlopen(self, response_body: dict, status: int = 200): """Return a context-manager mock for urllib.request.urlopen.""" body_bytes = json.dumps(response_body).encode() resp = mock.MagicMock() resp.read.return_value = body_bytes resp.__enter__ = mock.MagicMock(return_value=resp) resp.__exit__ = mock.MagicMock(return_value=False) return resp @mock.patch("markitect.llm._http.urllib.request.urlopen") def test_success(self, mock_urlopen): expected = {"choices": [{"message": {"content": "hi"}}]} mock_urlopen.return_value = self._mock_urlopen(expected) result = post_json("https://api.test/v1", {"prompt": "hello"}) assert result == expected @mock.patch("markitect.llm._http.urllib.request.urlopen") def test_429_raises_rate_limit(self, mock_urlopen): exc = urllib.error.HTTPError( "https://api.test/v1", 429, "Too Many Requests", {}, None ) exc.read = mock.MagicMock(return_value=b"rate limited") mock_urlopen.side_effect = exc with pytest.raises(LLMRateLimitError) as exc_info: post_json("https://api.test/v1", {}) assert exc_info.value.status_code == 429 @mock.patch("markitect.llm._http.urllib.request.urlopen") def test_500_raises_api_error(self, mock_urlopen): exc = urllib.error.HTTPError( "https://api.test/v1", 500, "Internal Server Error", {}, None ) exc.read = mock.MagicMock(return_value=b"server error") mock_urlopen.side_effect = exc with pytest.raises(LLMAPIError) as exc_info: post_json("https://api.test/v1", {}) assert exc_info.value.status_code == 500 @mock.patch("markitect.llm._http.urllib.request.urlopen") def test_timeout_raises_timeout_error(self, mock_urlopen): exc = urllib.error.URLError("timed out") mock_urlopen.side_effect = exc with pytest.raises(LLMTimeoutError): post_json("https://api.test/v1", {}) @mock.patch("markitect.llm._http.urllib.request.urlopen") def test_generic_url_error_raises_api_error(self, mock_urlopen): exc = urllib.error.URLError("connection refused") mock_urlopen.side_effect = exc with pytest.raises(LLMAPIError): post_json("https://api.test/v1", {}) @mock.patch("markitect.llm._http.urllib.request.urlopen") def test_python_timeout_error(self, mock_urlopen): mock_urlopen.side_effect = TimeoutError("timed out") with pytest.raises(LLMTimeoutError): post_json("https://api.test/v1", {})