1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
|
"""Tests for error_handling.py — all branches of check_response_for_error."""
import json
import pytest
from pyControl4.error_handling import (
BadCredentials,
BadToken,
C4Exception,
InvalidCategory,
NotFound,
Unauthorized,
check_response_for_error,
)
# --- Happy paths (no exception raised) ---
def test_json_no_error_keys():
"""JSON response without error keys should not raise."""
check_response_for_error(json.dumps({"result": "ok"}))
def test_xml_no_error_keys():
"""XML response without error keys should not raise."""
check_response_for_error("<result>ok</result>")
# --- C4ErrorResponse format ---
def test_c4error_bad_credentials():
"""C4ErrorResponse with matching details raises BadCredentials."""
payload = json.dumps(
{
"C4ErrorResponse": {
"code": 401,
"details": "Permission denied Bad credentials",
"message": "Permission denied",
}
}
)
with pytest.raises(BadCredentials):
check_response_for_error(payload)
def test_c4error_401_no_matching_details():
"""C4ErrorResponse with code 401 but non-matching details raises Unauthorized."""
payload = json.dumps(
{
"C4ErrorResponse": {
"code": 401,
"details": "",
"message": "Permission denied",
}
}
)
with pytest.raises(Unauthorized):
check_response_for_error(payload)
def test_c4error_404():
"""C4ErrorResponse with code 404 raises NotFound."""
payload = json.dumps(
{
"C4ErrorResponse": {
"code": 404,
"message": "Not found",
}
}
)
with pytest.raises(NotFound):
check_response_for_error(payload)
def test_c4error_unknown_code_falls_back_to_base():
"""C4ErrorResponse with unrecognized code raises exactly C4Exception (not a subclass)."""
payload = json.dumps(
{
"C4ErrorResponse": {
"code": 999,
"message": "Unknown error",
}
}
)
with pytest.raises(C4Exception) as exc_info:
check_response_for_error(payload)
assert type(exc_info.value) is C4Exception
# --- Flat JSON code format ---
def test_flat_json_404():
"""Flat JSON with code 404 raises NotFound."""
payload = json.dumps({"code": 404, "message": "Account not found"})
with pytest.raises(NotFound):
check_response_for_error(payload)
def test_flat_json_bad_credentials():
"""Flat JSON with matching details raises BadCredentials (details take priority)."""
payload = json.dumps(
{
"code": 401,
"details": "Permission denied Bad credentials",
"message": "Permission denied",
}
)
with pytest.raises(BadCredentials):
check_response_for_error(payload)
# --- Director error format ---
def test_director_error_bad_token():
"""Director error with matching details raises BadToken."""
payload = json.dumps(
{"error": "Unauthorized", "details": "Expired or invalid token"}
)
with pytest.raises(BadToken):
check_response_for_error(payload)
def test_director_error_unauthorized_no_matching_details():
"""Director 'Unauthorized' without matching details raises Unauthorized."""
payload = json.dumps({"error": "Unauthorized"})
with pytest.raises(Unauthorized):
check_response_for_error(payload)
def test_director_error_invalid_category():
"""Director 'Invalid category' raises InvalidCategory."""
payload = json.dumps({"error": "Invalid category"})
with pytest.raises(InvalidCategory):
check_response_for_error(payload)
def test_director_error_unknown_falls_back_to_base():
"""Director error with unrecognized string raises exactly C4Exception (not a subclass)."""
payload = json.dumps({"error": "Something else"})
with pytest.raises(C4Exception) as exc_info:
check_response_for_error(payload)
assert type(exc_info.value) is C4Exception
# --- XML C4ErrorResponse ---
def test_xml_c4error_401():
"""XML C4ErrorResponse with code 401 raises Unauthorized."""
xml = (
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
"<C4ErrorResponse>"
"<code>401</code>"
"<details></details>"
"<message>Permission denied</message>"
"<subCode>0</subCode>"
"</C4ErrorResponse>"
)
with pytest.raises(Unauthorized):
check_response_for_error(xml)
# --- Exception hierarchy and behavior ---
def test_exception_hierarchy():
"""Verify the exception inheritance chain."""
assert issubclass(BadCredentials, Unauthorized)
assert issubclass(BadToken, Unauthorized)
assert issubclass(Unauthorized, C4Exception)
assert issubclass(NotFound, C4Exception)
assert issubclass(InvalidCategory, C4Exception)
assert issubclass(C4Exception, Exception)
def test_exception_stores_message():
"""C4Exception stores the response text as .message."""
exc = C4Exception("some response text")
assert exc.message == "some response text"
def test_raised_exception_preserves_response_text():
"""Exception raised by check_response_for_error carries the original response."""
payload = json.dumps({"error": "Invalid category"})
with pytest.raises(InvalidCategory) as exc_info:
check_response_for_error(payload)
assert exc_info.value.message == payload
|