Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions python/src/github_docs/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,10 +251,19 @@ def _request(
) from e
return resp.status, parsed
except urllib.error.HTTPError as e:
raw = e.read()
# The body is only a nicer message; the status is already in hand.
# A read that times out or resets here is raised inside this
# handler, where the OSError clause below cannot catch it, so it
# falls back to the status-only message instead of escaping.
try:
raw = e.read()
except OSError:
raw = b""
# ValueError rather than JSONDecodeError, for the same reason as on
# the success path: bytes that are not UTF-8 fail before the parser.
try:
parsed = json.loads(raw) if raw else {}
except json.JSONDecodeError:
except ValueError:
parsed = {}
if e.code == 404 and allow_404:
return 404, parsed
Expand Down
20 changes: 20 additions & 0 deletions python/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,26 @@ def test_a_body_that_is_not_githubs_json_falls_back_to_the_status(self):
self.assertEqual(ctx.exception.status, 502)
self.assertEqual(str(ctx.exception), "GitHub API returned HTTP 502")

def test_a_timeout_while_reading_an_error_body_falls_back_to_the_status(self):
# The read happens inside the HTTPError handler, where the OSError
# clause beside it cannot catch what it raises.
error = _http_error(code=503, message="Service Unavailable")
error.read.side_effect = socket.timeout("timed out")
with mock.patch("urllib.request.urlopen", side_effect=error):
with self.assertRaises(GitHubDocsError) as ctx:
self.client.get_default_branch()
self.assertEqual(ctx.exception.status, 503)
self.assertEqual(str(ctx.exception), "GitHub API returned HTTP 503")

def test_an_error_body_that_is_not_utf8_falls_back_to_the_status(self):
# Undecodable bytes fail as a UnicodeDecodeError, not a JSONDecodeError.
error = _http_error(code=502, message="Bad Gateway", raw=b"\x80\x81 not text")
with mock.patch("urllib.request.urlopen", side_effect=error):
with self.assertRaises(GitHubDocsError) as ctx:
self.client.get_default_branch()
self.assertEqual(ctx.exception.status, 502)
self.assertEqual(str(ctx.exception), "GitHub API returned HTTP 502")

def test_allow_404_turns_a_missing_ref_into_none_rather_than_an_error(self):
with mock.patch("urllib.request.urlopen", side_effect=_http_error()):
self.assertIsNone(self.client.get_ref_sha("no-such-branch", allow_404=True))
Expand Down
Loading