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
1 change: 1 addition & 0 deletions changelog/473.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
JsonDecodeError now includes server response content in error message when JSON decoding fails, providing better debugging information for non-JSON server responses.
2 changes: 2 additions & 0 deletions infrahub_sdk/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ def __init__(self, message: str | None = None, content: str | None = None, url:
self.url = url
if not self.message and self.url:
self.message = f"Unable to decode response as JSON data from {self.url}"
if self.content:
self.message += f". Server response: {self.content}"
super().__init__(self.message)


Expand Down
54 changes: 54 additions & 0 deletions tests/unit/sdk/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import json
import tempfile
import uuid
from pathlib import Path
from unittest.mock import Mock

import pytest
from graphql import parse
from whenever import Instant

from infrahub_sdk.exceptions import JsonDecodeError
from infrahub_sdk.utils import (
base16decode,
base16encode,
base36decode,
base36encode,
calculate_time_diff,
compare_lists,
decode_json,
deep_merge_dict,
dict_hash,
duplicates,
Expand Down Expand Up @@ -227,3 +231,53 @@ def test_calculate_time_diff() -> None:

time5 = Instant.now().subtract(hours=77, minutes=12, seconds=34).format_common_iso()
assert calculate_time_diff(time5) == "3d and 5h ago"


def test_decode_json_success() -> None:
"""Test decode_json with valid JSON response."""
mock_response = Mock()
mock_response.json.return_value = {"status": "ok", "data": {"key": "value"}}

result = decode_json(mock_response)
assert result == {"status": "ok", "data": {"key": "value"}}


def test_decode_json_failure_with_content() -> None:
"""Test decode_json with invalid JSON response includes server content in error message."""
mock_response = Mock()
mock_response.json.side_effect = json.decoder.JSONDecodeError("Invalid JSON", "document", 0)
mock_response.text = "Internal Server Error: Database connection failed"
mock_response.url = "https://example.com/api/graphql"

with pytest.raises(JsonDecodeError) as exc_info:
decode_json(mock_response)

error_message = str(exc_info.value)
assert "Unable to decode response as JSON data from https://example.com/api/graphql" in error_message
assert "Server response: Internal Server Error: Database connection failed" in error_message


def test_decode_json_failure_without_content() -> None:
"""Test decode_json with invalid JSON response and no content."""
mock_response = Mock()
mock_response.json.side_effect = json.decoder.JSONDecodeError("Invalid JSON", "document", 0)
mock_response.text = ""
mock_response.url = "https://example.com/api/graphql"

with pytest.raises(JsonDecodeError) as exc_info:
decode_json(mock_response)

error_message = str(exc_info.value)
assert "Unable to decode response as JSON data from https://example.com/api/graphql" in error_message
# Should not include server response part when content is empty
assert "Server response:" not in error_message


def test_json_decode_error_custom_message() -> None:
"""Test JsonDecodeError with custom message does not override custom message."""
custom_message = "Custom error message"
error = JsonDecodeError(message=custom_message, content="server error", url="https://example.com")

assert str(error) == custom_message
assert error.content == "server error"
assert error.url == "https://example.com"