Skip to content

Commit 14ed020

Browse files
committed
fix(client): use HTTP reason for bodyless error responses
Failed HEAD responses normally have no body, so Gitlab.http_request() left error_message empty and raised errors rendered as "404: ". Fall back to the response reason phrase, or the standard HTTPStatus phrase when the server did not provide one, only when no message could be parsed from the response. JSON response messages still take precedence, and response_body remains unchanged. Add regression coverage for the low-level HEAD path, ProjectFileManager.head(), and JSON message precedence. Fixes #3461
1 parent bb9ea1b commit 14ed020

3 files changed

Lines changed: 72 additions & 1 deletion

File tree

‎gitlab/client.py‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import os
66
import re
7+
from http import HTTPStatus
78
from typing import Any, BinaryIO, cast, TYPE_CHECKING
89
from urllib import parse
910

@@ -756,7 +757,7 @@ def http_request(
756757
):
757758
continue
758759

759-
error_message = result.content
760+
error_message: str | bytes = result.content
760761
try:
761762
error_json = result.json()
762763
for k in ("message", "error"):
@@ -765,6 +766,15 @@ def http_request(
765766
except (KeyError, ValueError, TypeError):
766767
pass
767768

769+
if not error_message:
770+
# Bodyless responses, such as HEAD errors, do not carry a
771+
# GitLab error message. Use the HTTP reason phrase instead.
772+
try:
773+
default_reason = HTTPStatus(result.status_code).phrase
774+
except ValueError:
775+
default_reason = ""
776+
error_message = result.reason or default_reason
777+
768778
if result.status_code == 401:
769779
raise gitlab.exceptions.GitlabAuthenticationError(
770780
response_code=result.status_code,

‎tests/unit/objects/test_repositories.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import responses
1111
from requests.structures import CaseInsensitiveDict
1212

13+
from gitlab.exceptions import GitlabHeadError
1314
from gitlab.v4.objects import ProjectFile
1415

1516
file_path = "app/models/key.rb"
@@ -62,6 +63,26 @@ def test_head_repository_file(project, resp_head_repository_file):
6263
assert headers["X-Gitlab-File-Path"] == file_path
6364

6465

66+
@responses.activate
67+
def test_head_repository_file_error_without_body(project):
68+
missing_file_path = "nonexistent.yaml"
69+
encoded_path = quote(missing_file_path, safe="")
70+
responses.add(
71+
method=responses.HEAD,
72+
url=f"http://localhost/api/v4/projects/1/repository/files/{encoded_path}",
73+
status=404,
74+
)
75+
76+
with pytest.raises(GitlabHeadError) as exc_info:
77+
project.files.head(missing_file_path, ref=ref)
78+
79+
error = exc_info.value
80+
assert error.response_code == 404
81+
assert error.response_body == b""
82+
assert error.error_message == "Not Found"
83+
assert str(error) == "404: Not Found"
84+
85+
6586
@pytest.fixture
6687
def resp_get_repository_file():
6788
file_response = {

‎tests/unit/test_gitlab_http_methods.py‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,46 @@ def test_head_request(gl):
530530
assert result["X-Total"] == "1"
531531

532532

533+
@responses.activate
534+
def test_head_request_error_without_body_uses_reason(gl):
535+
url = "http://localhost/api/v4/not_there"
536+
responses.add(
537+
method=responses.HEAD,
538+
url=url,
539+
status=404,
540+
match=helpers.MATCH_EMPTY_QUERY_PARAMS,
541+
)
542+
543+
with pytest.raises(GitlabHttpError) as exc_info:
544+
gl.http_head("/not_there")
545+
546+
error = exc_info.value
547+
assert error.response_code == 404
548+
assert error.response_body == b""
549+
assert error.error_message == "Not Found"
550+
assert str(error) == "404: Not Found"
551+
552+
553+
@responses.activate
554+
def test_http_request_error_message_takes_precedence_over_reason(gl):
555+
url = "http://localhost/api/v4/not_there"
556+
responses.add(
557+
method=responses.GET,
558+
url=url,
559+
json={"message": "File Not Found"},
560+
status=404,
561+
match=helpers.MATCH_EMPTY_QUERY_PARAMS,
562+
)
563+
564+
with pytest.raises(GitlabHttpError) as exc_info:
565+
gl.http_get("/not_there")
566+
567+
error = exc_info.value
568+
assert error.response_body == b'{"message": "File Not Found"}'
569+
assert error.error_message == "File Not Found"
570+
assert str(error) == "404: File Not Found"
571+
572+
533573
@responses.activate
534574
def test_list_request(gl):
535575
url = "http://localhost/api/v4/projects"

0 commit comments

Comments
 (0)