File: test_error_handling.py

package info (click to toggle)
pycontrol4 2.0.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 656 kB
  • sloc: python: 1,438; makefile: 3
file content (191 lines) | stat: -rw-r--r-- 5,557 bytes parent folder | download
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