Skip to content

Commit a3225b8

Browse files
Fix #971: raise if bug info cannot be fetched from Bugzilla (#988)
* Fix #971: raise if bug info cannot be fetched from Bugzilla * Remove hardcoded value --------- Co-authored-by: Alex Cottner <[email protected]>
1 parent 60fd272 commit a3225b8

File tree

4 files changed

+114
-9
lines changed

4 files changed

+114
-9
lines changed

jbi/bugzilla/client.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ class BugzillaClientError(Exception):
2222
"""Errors raised by `BugzillaClient`."""
2323

2424

25+
class BugNotAccessibleError(BugzillaClientError):
26+
"""Bug is private or not accessible."""
27+
28+
2529
instrumented_method = instrument(
2630
prefix="bugzilla",
2731
exceptions=(
@@ -72,7 +76,17 @@ def get_bug(self, bugid) -> Bug:
7276
"""Retrieve details about the specified bug id."""
7377
# https://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#rest-single-bug
7478
url = f"{self.base_url}/rest/bug/{bugid}"
75-
bug_info = self._call("GET", url)
79+
80+
try:
81+
bug_info = self._call("GET", url)
82+
except requests.HTTPError as err:
83+
if err.response is not None and err.response.status_code in (401, 403, 404):
84+
if self.logged_in():
85+
# If bug returns 401 and credentials are valid.
86+
msg = err.response.json().get("message", "bug not accessible")
87+
raise BugNotAccessibleError(msg)
88+
raise
89+
7690
parsed = ApiResponse.model_validate(bug_info)
7791
if not parsed.bugs:
7892
raise BugzillaClientError(

jbi/runner.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from jbi import ActionResult, Operation, bugzilla, jira
1414
from jbi import steps as steps_module
15+
from jbi.bugzilla.client import BugNotAccessibleError
1516
from jbi.environment import get_settings
1617
from jbi.errors import ActionNotFoundError, IgnoreInvalidRequestError
1718
from jbi.models import (
@@ -219,13 +220,10 @@ def execute_action(
219220
)
220221
try:
221222
bug = bugzilla.get_service().refresh_bug_data(bug)
222-
except Exception as err:
223-
logger.exception(
224-
"Failed to get bug: %s", err, extra=runner_context.model_dump()
225-
)
226-
raise IgnoreInvalidRequestError(
227-
"bug not accessible or bugzilla down"
228-
) from err
223+
except BugNotAccessibleError as err:
224+
# This can happen if the bug is made private after the webhook
225+
# is processed (eg. if it spent some time in the DL queue)
226+
raise IgnoreInvalidRequestError(str(err)) from err
229227

230228
runner_context = runner_context.update(bug=bug)
231229
try:

tests/unit/bugzilla/test_client.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import pytest
2+
import requests
23
import responses
34
from responses import matchers
45

5-
from jbi.bugzilla.client import BugzillaClient, BugzillaClientError
6+
from jbi.bugzilla.client import (
7+
BugNotAccessibleError,
8+
BugzillaClient,
9+
BugzillaClientError,
10+
)
611

712

813
@pytest.fixture
@@ -95,6 +100,65 @@ def test_bugzilla_raises_if_response_has_error(
95100
assert "not happy" in str(exc)
96101

97102

103+
@pytest.mark.no_mocked_bugzilla
104+
def test_bugzilla_get_bug_raises_if_response_is_401_and_credentials_invalid(
105+
bugzilla_client, settings, mocked_responses
106+
):
107+
url = f"{settings.bugzilla_base_url}/rest/bug/42"
108+
mocked_responses.add(
109+
responses.GET,
110+
url,
111+
status=401,
112+
json={
113+
"code": 102,
114+
"documentation": "https://bmo.readthedocs.io/en/latest/api/",
115+
"error": True,
116+
"message": "You are not authorized to access bug 42.",
117+
},
118+
)
119+
mocked_responses.add(
120+
responses.GET,
121+
f"{settings.bugzilla_base_url}/rest/whoami",
122+
status=401,
123+
)
124+
125+
with pytest.raises(requests.HTTPError) as exc:
126+
bugzilla_client.get_bug(42)
127+
128+
assert (
129+
f"401 Client Error: Unauthorized for url: {settings.bugzilla_base_url}/rest/bug/42"
130+
in str(exc)
131+
)
132+
133+
134+
@pytest.mark.no_mocked_bugzilla
135+
def test_bugzilla_get_bug_raises_if_response_is_401_and_credentials_valid(
136+
bugzilla_client, settings, mocked_responses
137+
):
138+
url = f"{settings.bugzilla_base_url}/rest/bug/42"
139+
mocked_responses.add(
140+
responses.GET,
141+
url,
142+
status=401,
143+
json={
144+
"code": 102,
145+
"documentation": "https://bmo.readthedocs.io/en/latest/api/",
146+
"error": True,
147+
"message": "You are not authorized to access bug 42.",
148+
},
149+
)
150+
mocked_responses.add(
151+
responses.GET,
152+
f"{settings.bugzilla_base_url}/rest/whoami",
153+
json={"id": "you"},
154+
)
155+
156+
with pytest.raises(BugNotAccessibleError) as exc:
157+
bugzilla_client.get_bug(42)
158+
159+
assert "You are not authorized to access bug 42" in str(exc)
160+
161+
98162
@pytest.mark.no_mocked_bugzilla
99163
def test_bugzilla_get_bug_raises_if_response_has_no_bugs(
100164
bugzilla_client, settings, mocked_responses

tests/unit/test_runner.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import responses
77

88
from jbi import Operation
9+
from jbi.bugzilla.client import BugNotAccessibleError
910
from jbi.environment import get_settings
1011
from jbi.errors import ActionNotFoundError, IgnoreInvalidRequestError
1112
from jbi.models import ActionContext
@@ -54,6 +55,34 @@ def test_request_is_ignored_because_project_mismatch(
5455
assert str(exc_info.value) == "ignore linked project 'FXDROID' (!='JBI')"
5556

5657

58+
def test_request_is_ignored_because_bug_cannot_be_fetched(
59+
webhook_request_factory,
60+
actions,
61+
mocked_bugzilla,
62+
):
63+
webhook = webhook_request_factory()
64+
mocked_bugzilla.get_bug.side_effect = BugNotAccessibleError(
65+
"not authorized to access bug 12345"
66+
)
67+
68+
with pytest.raises(IgnoreInvalidRequestError) as exc_info:
69+
execute_action(request=webhook, actions=actions)
70+
71+
assert str(exc_info.value) == "not authorized to access bug 12345"
72+
73+
74+
def test_request_if_bugzilla_is_down_and_bug_cannot_be_fetched(
75+
webhook_request_factory,
76+
actions,
77+
mocked_bugzilla,
78+
):
79+
webhook = webhook_request_factory()
80+
mocked_bugzilla.get_bug.side_effect = requests.ConnectTimeout()
81+
82+
with pytest.raises(requests.ConnectTimeout):
83+
execute_action(request=webhook, actions=actions)
84+
85+
5786
def test_request_is_ignored_because_private(
5887
webhook_request_factory,
5988
actions,

0 commit comments

Comments
 (0)