diff --git a/jbi/jira/client.py b/jbi/jira/client.py index 34d66efe..863c2231 100644 --- a/jbi/jira/client.py +++ b/jbi/jira/client.py @@ -1,3 +1,4 @@ +import json import logging from typing import Collection, Iterable, Optional @@ -54,6 +55,7 @@ def raise_for_status(self, *args, **kwargs): except requests.HTTPError as exc: request = exc.request response = exc.response + assert response is not None, f"HTTPError {exc} has no attached response" atlassian_logger.error( "HTTP: %s %s -> %s %s", request.method, @@ -62,6 +64,18 @@ def raise_for_status(self, *args, **kwargs): response.reason, extra={"body": response.text}, ) + if str(exc) == "": + # Some Jira errors are raised as `HTTPError('')`. + # We are trying to turn them into insightful errors here. + try: + content = exc.response.json() + errors = content.get("errors", {}) + response_details = ",".join(f"{k}: {v}" for k, v in errors.items()) + except json.JSONDecodeError: + response_details = exc.response.text + # Set the exception message so that its str version contains details. + msg = f"HTTP {exc.response.status_code}: {response_details}" + exc.args = (msg,) + exc.args[1:] raise get_server_info = instrumented_method(Jira.get_server_info) diff --git a/tests/unit/jira/test_queue.py b/tests/unit/jira/test_queue.py index 9b1737ec..148f7d32 100644 --- a/tests/unit/jira/test_queue.py +++ b/tests/unit/jira/test_queue.py @@ -178,7 +178,7 @@ async def test_backend_get_invalid_json(backend: QueueBackend, queue_item_factor with pytest.raises(QueueItemRetrievalError): await anext(items) - + async def test_get_missing_timezone(backend: QueueBackend, queue_item_factory): item = queue_item_factory.build(payload__bug__id=666) dump = item.model_dump() diff --git a/tests/unit/test_runner.py b/tests/unit/test_runner.py index f7c8905d..8f5c7bc7 100644 --- a/tests/unit/test_runner.py +++ b/tests/unit/test_runner.py @@ -304,6 +304,55 @@ async def test_execute_or_queue_exception( mock_queue.track_failed.assert_called_once() +@pytest.mark.asyncio +@pytest.mark.no_mocked_bugzilla +@pytest.mark.no_mocked_jira +async def test_execute_or_queue_http_error_details( + actions, + dl_queue, + bugzilla_webhook_request, + context_comment_example, + mocked_responses, +): + bug = bugzilla_webhook_request.bug + settings = get_settings() + mocked_responses.add( + responses.GET, + f"{settings.bugzilla_base_url}/rest/bug/{bug.id}", + json={"bugs": [bug.model_dump()]}, + ) + mocked_responses.add( + responses.GET, + f"{settings.bugzilla_base_url}/rest/bug/{bug.id}/comment", + json={"bugs": {str(bug.id): {"comments": []}}}, + ) + mocked_responses.add( + responses.POST, + f"{settings.jira_base_url}rest/api/2/issue", + json={"key": "TEST-1"}, + ) + mocked_responses.add( + responses.POST, + f"{settings.jira_base_url}rest/api/2/issue/TEST-1/remotelink", + status=400, + json={ + "errorMessages": [], + "errors": {"resolution": "Field 'resolution' cannot be set."}, + }, + ) + + await execute_or_queue( + request=bugzilla_webhook_request, queue=dl_queue, actions=actions + ) + + items = (await dl_queue.retrieve())[bug.id] + [item] = [i async for i in items] + assert ( + item.error.description + == "HTTP 400: resolution: Field 'resolution' cannot be set." + ) + + def test_default_invalid_init(): with pytest.raises(TypeError): Executor()