|
4 | 4 |
|
5 | 5 | import tableauserverclient as TSC |
6 | 6 | from tableauserverclient.server.endpoint import Endpoint |
| 7 | +from tableauserverclient.server.endpoint.exceptions import ( |
| 8 | + FailedSignInError, |
| 9 | + NonXMLResponseError, |
| 10 | + ServerResponseError, |
| 11 | +) |
7 | 12 |
|
8 | 13 | import requests_mock |
9 | 14 |
|
@@ -91,3 +96,66 @@ def test_set_user_agent_when_blank(server: TSC.Server) -> None: |
91 | 96 | params = {"headers": {}} # type: ignore |
92 | 97 | result = Endpoint.set_user_agent(params) |
93 | 98 | assert result["headers"]["User-Agent"].startswith("Tableau Server Client") |
| 99 | + |
| 100 | + |
| 101 | +# --- ServerResponseError / FailedSignInError exception parsing (issue #1083) --- |
| 102 | + |
| 103 | +NS = {"t": "http://tableau.com/api"} |
| 104 | + |
| 105 | +STANDARD_ERROR_XML = b"""<?xml version='1.0' encoding='UTF-8'?> |
| 106 | +<tsResponse xmlns="http://tableau.com/api"> |
| 107 | + <error code="401002"> |
| 108 | + <summary>Unauthorized Access</summary> |
| 109 | + <detail>Invalid credentials were provided.</detail> |
| 110 | + </error> |
| 111 | +</tsResponse>""" |
| 112 | + |
| 113 | +NO_ERROR_ELEMENT_XML = b"""<?xml version='1.0' encoding='UTF-8'?> |
| 114 | +<tsResponse xmlns="http://tableau.com/api"> |
| 115 | + <message>Something went wrong but with no error element</message> |
| 116 | +</tsResponse>""" |
| 117 | + |
| 118 | +NOT_XML_CONTENT = b"Internal Server Error (not XML at all)" |
| 119 | + |
| 120 | + |
| 121 | +def test_server_response_error_standard_xml(): |
| 122 | + """Standard XML with a t:error element parses code/summary/detail correctly.""" |
| 123 | + err = ServerResponseError.from_response(STANDARD_ERROR_XML, NS, "http://test/") |
| 124 | + assert err.code == "401002" |
| 125 | + assert "Unauthorized" in err.summary |
| 126 | + assert "Invalid credentials" in err.detail |
| 127 | + |
| 128 | + |
| 129 | +def test_server_response_error_no_error_element_does_not_raise(): |
| 130 | + """XML without a t:error element must not raise AttributeError (issue #1083).""" |
| 131 | + err = ServerResponseError.from_response(NO_ERROR_ELEMENT_XML, NS, "http://test/") |
| 132 | + assert err.code == "" |
| 133 | + # The raw XML content should appear in summary/detail as the fallback |
| 134 | + assert "Something went wrong" in err.summary or len(err.summary) > 0 |
| 135 | + |
| 136 | + |
| 137 | +def test_server_response_error_not_xml_raises_parse_error(): |
| 138 | + """Non-XML content causes fromstring to raise a ParseError (not AttributeError).""" |
| 139 | + import xml.etree.ElementTree as ET |
| 140 | + |
| 141 | + with pytest.raises(ET.ParseError): |
| 142 | + ServerResponseError.from_response(NOT_XML_CONTENT, NS, "http://test/") |
| 143 | + |
| 144 | + |
| 145 | +def test_failed_sign_in_error_no_error_element_does_not_raise(): |
| 146 | + """FailedSignInError shares from_response — same None guard must apply.""" |
| 147 | + err = FailedSignInError.from_response(NO_ERROR_ELEMENT_XML, NS, "http://test/") |
| 148 | + assert err.code == "" |
| 149 | + assert isinstance(err, FailedSignInError) |
| 150 | + |
| 151 | + |
| 152 | +def test_server_response_error_missing_summary_and_detail(): |
| 153 | + """XML with t:error but missing summary/detail children falls back gracefully.""" |
| 154 | + xml = b"""<?xml version='1.0' encoding='UTF-8'?> |
| 155 | +<tsResponse xmlns="http://tableau.com/api"> |
| 156 | + <error code="500001"></error> |
| 157 | +</tsResponse>""" |
| 158 | + err = ServerResponseError.from_response(xml, NS, "http://test/") |
| 159 | + assert err.code == "500001" |
| 160 | + assert err.summary == "" |
| 161 | + assert err.detail == "" |
0 commit comments