Coverage for tests / test_error_handling.py: 100%
133 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-03-21 11:59 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2026-03-21 11:59 +0000
1# SPDX-FileCopyrightText: 2025-2026 Arcangelo Massari <arcangelo.massari@unibo.it>
2#
3# SPDX-License-Identifier: ISC
5"""Unit tests for error handling edge cases using mocks."""
7from unittest.mock import MagicMock, patch
9import pycurl
10import pytest
12from sparqlite import EndpointError, SPARQLClient
15class TestHTTPErrorHandling:
16 """Tests for HTTP error handling."""
18 def test_server_error_500_triggers_retry(self):
19 """Test that 500 errors trigger retry logic."""
20 mock_curl = MagicMock()
21 mock_curl.getinfo.return_value = 500
23 call_count = 0
25 def mock_perform():
26 nonlocal call_count
27 call_count += 1
29 mock_curl.perform = mock_perform
31 with patch("pycurl.Curl", return_value=mock_curl):
32 client = SPARQLClient("http://example.org/sparql", max_retries=2)
33 client._curl = mock_curl
35 with pytest.raises(EndpointError) as exc_info:
36 client._request("SELECT * WHERE { ?s ?p ?o }", "application/json")
38 assert "500" in str(exc_info.value)
39 assert exc_info.value.status_code == 500
40 assert call_count == 3
42 client.close()
44 def test_http_403_forbidden(self):
45 """Test that 403 errors raise EndpointError without retry."""
46 mock_curl = MagicMock()
47 mock_curl.getinfo.return_value = 403
49 buffer_content = b"Forbidden"
51 def mock_setopt(opt, val):
52 if opt == pycurl.WRITEDATA:
53 val.write(buffer_content)
55 mock_curl.setopt = mock_setopt
57 with patch("pycurl.Curl", return_value=mock_curl):
58 client = SPARQLClient("http://example.org/sparql")
59 client._curl = mock_curl
61 with pytest.raises(EndpointError) as exc_info:
62 client._request("SELECT * WHERE { ?s ?p ?o }", "application/json")
64 assert exc_info.value.status_code == 403
65 assert "403" in str(exc_info.value)
67 client.close()
70class TestPycurlErrorHandling:
71 """Tests for pycurl error handling."""
73 def test_timeout_error(self):
74 """Test that timeout errors are handled."""
75 mock_curl = MagicMock()
76 mock_curl.perform.side_effect = pycurl.error(
77 pycurl.E_OPERATION_TIMEDOUT, "Operation timed out"
78 )
80 with patch("pycurl.Curl", return_value=mock_curl):
81 client = SPARQLClient("http://example.org/sparql", max_retries=0)
82 client._curl = mock_curl
84 with pytest.raises(EndpointError) as exc_info:
85 client._request("SELECT * WHERE { ?s ?p ?o }", "application/json")
87 assert "Timeout" in str(exc_info.value)
89 client.close()
91 def test_resolve_host_error(self):
92 """Test that host resolution errors are handled."""
93 mock_curl = MagicMock()
94 mock_curl.perform.side_effect = pycurl.error(
95 pycurl.E_COULDNT_RESOLVE_HOST, "Could not resolve host"
96 )
98 with patch("pycurl.Curl", return_value=mock_curl):
99 client = SPARQLClient("http://nonexistent.invalid/sparql", max_retries=0)
100 client._curl = mock_curl
102 with pytest.raises(EndpointError) as exc_info:
103 client._request("SELECT * WHERE { ?s ?p ?o }", "application/json")
105 assert "Connection error" in str(exc_info.value)
107 client.close()
109 def test_generic_pycurl_error(self):
110 """Test that generic pycurl errors are handled."""
111 mock_curl = MagicMock()
112 mock_curl.perform.side_effect = pycurl.error(
113 pycurl.E_SSL_CONNECT_ERROR, "SSL connection error"
114 )
116 with patch("pycurl.Curl", return_value=mock_curl):
117 client = SPARQLClient("https://example.org/sparql", max_retries=0)
118 client._curl = mock_curl
120 with pytest.raises(EndpointError) as exc_info:
121 client._request("SELECT * WHERE { ?s ?p ?o }", "application/json")
123 assert "Request error" in str(exc_info.value)
125 client.close()
128class TestTimeoutConfiguration:
129 """Tests for timeout configuration."""
131 def test_timeout_parameter_stored(self):
132 """Test that timeout parameter is stored correctly."""
133 with patch("pycurl.Curl") as mock_curl_class:
134 mock_curl = MagicMock()
135 mock_curl_class.return_value = mock_curl
137 client = SPARQLClient("http://example.org/sparql", timeout=30.0)
138 assert client.timeout == 30.0
139 client.close()
141 def test_timeout_default_is_none(self):
142 """Test that timeout defaults to None."""
143 with patch("pycurl.Curl") as mock_curl_class:
144 mock_curl = MagicMock()
145 mock_curl_class.return_value = mock_curl
147 client = SPARQLClient("http://example.org/sparql")
148 assert client.timeout is None
149 client.close()
151 def test_timeout_ms_set_when_timeout_provided(self):
152 """Test that TIMEOUT_MS is set when timeout is provided."""
153 mock_curl = MagicMock()
154 mock_curl.getinfo.return_value = 200
156 setopt_calls = []
158 def track_setopt(opt, val):
159 setopt_calls.append((opt, val))
161 mock_curl.setopt = track_setopt
163 with patch("pycurl.Curl", return_value=mock_curl):
164 client = SPARQLClient("http://example.org/sparql", timeout=5.5)
165 client._curl = mock_curl
166 client._request("SELECT * WHERE { ?s ?p ?o }", "application/json")
167 client.close()
169 timeout_calls = [(opt, val) for opt, val in setopt_calls if opt == pycurl.TIMEOUT_MS]
170 assert timeout_calls == [(pycurl.TIMEOUT_MS, 5500)]
172 def test_timeout_ms_not_set_when_timeout_none(self):
173 """Test that TIMEOUT_MS is not set when timeout is None."""
174 mock_curl = MagicMock()
175 mock_curl.getinfo.return_value = 200
177 setopt_calls = []
179 def track_setopt(opt, val):
180 setopt_calls.append((opt, val))
182 mock_curl.setopt = track_setopt
184 with patch("pycurl.Curl", return_value=mock_curl):
185 client = SPARQLClient("http://example.org/sparql")
186 client._curl = mock_curl
187 client._request("SELECT * WHERE { ?s ?p ?o }", "application/json")
188 client.close()
190 timeout_calls = [opt for opt, _ in setopt_calls if opt == pycurl.TIMEOUT_MS]
191 assert timeout_calls == []
194class TestHTTPMethod:
196 def test_post_method_uses_postfields(self):
197 mock_curl = MagicMock()
198 mock_curl.getinfo.return_value = 200
199 setopt_calls: list[tuple[int, object]] = []
201 def track_setopt(opt: int, val: object) -> None:
202 setopt_calls.append((opt, val))
204 mock_curl.setopt = track_setopt
206 with patch("pycurl.Curl", return_value=mock_curl):
207 client = SPARQLClient("http://example.org/sparql")
208 client._curl = mock_curl
209 client._request("SELECT ?s WHERE { ?s ?p ?o }", "application/json", method="POST")
210 client.close()
212 assert any(opt == pycurl.POSTFIELDS for opt, _ in setopt_calls)
213 assert not any(opt == pycurl.HTTPGET for opt, _ in setopt_calls)
215 def test_request_on_closed_client_raises(self):
216 with patch("pycurl.Curl") as mock_curl_class:
217 mock_curl_class.return_value = MagicMock()
218 client = SPARQLClient("http://example.org/sparql")
219 client.close()
221 with pytest.raises(EndpointError):
222 client._request("SELECT ?s WHERE { ?s ?p ?o }", "application/json")