From c0425193d00864954e13368f1114abb8fc86b52a Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 15 Jan 2020 15:15:50 +0100 Subject: [PATCH] Various error improvements --- fbchat/_exception.py | 25 +++++----- fbchat/_session.py | 6 ++- fbchat/_thread.py | 3 ++ tests/test_exception.py | 102 ++++++++++++++++++++++++++++++++++++++-- 4 files changed, 118 insertions(+), 18 deletions(-) diff --git a/fbchat/_exception.py b/fbchat/_exception.py index 49437d1..fec97e9 100644 --- a/fbchat/_exception.py +++ b/fbchat/_exception.py @@ -26,7 +26,7 @@ class HTTPError(FacebookError): def __str__(self): if not self.status_code: return self.message - return "#{}: {}".format(self.status_code, self.message) + return "Got {} response: {}".format(self.status_code, self.message) @attrs_exception @@ -43,7 +43,7 @@ class ParseError(FacebookError): """ def __str__(self): - msg = "{}. Please report this, and the associated data: {}" + msg = "{}. Please report this, along with the data below!\n{}" return msg.format(self.message, self.data) @@ -58,7 +58,7 @@ class ExternalError(FacebookError): def __str__(self): if self.code: - return "{}: #{}, {}".format(self.message, self.code, self.description) + return "#{} {}: {}".format(self.code, self.message, self.description) return "{}: {}".format(self.message, self.description) @@ -116,8 +116,7 @@ def handle_payload_error(j): error_cls = InvalidParameters else: error_cls = ExternalError - # TODO: Use j["errorSummary"] - raise error_cls("Error sending request", j["errorDescription"], code=code) + raise error_cls(j["errorSummary"], description=j["errorDescription"], code=code) def handle_graphql_errors(j): @@ -131,23 +130,27 @@ def handle_graphql_errors(j): # TODO: Use `severity` and `description` raise GraphQLError( # TODO: What data is always available? - error.get("summary", "Unknown error"), - error.get("message", ""), + message=error.get("summary", "Unknown error"), + description=error.get("message", ""), code=error.get("code"), debug_info=error.get("debug_info"), ) def handle_http_error(code): - msg = "Error sending request: Got {} response.".format(code) if code == 404: raise HTTPError( - msg + " This is either because you specified an invalid URL, or because" - " you provided an invalid id (Facebook usually require integer ids)", + "This might be because you provided an invalid id" + + " (Facebook usually require integer ids)", + status_code=code, + ) + if code == 500: + raise HTTPError( + "There is probably an error on the endpoint, or it might be rate limited", status_code=code, ) if 400 <= code < 600: - raise HTTPError(msg, status_code=code) + raise HTTPError("Failed sending request", status_code=code) def handle_requests_error(e): diff --git a/fbchat/_session.py b/fbchat/_session.py index e76ae18..59ded1b 100644 --- a/fbchat/_session.py +++ b/fbchat/_session.py @@ -202,7 +202,7 @@ class Session: else: code, msg = get_error_data(r.text, r.url) raise _exception.ExternalError( - "Login failed at url {}".format(r.url), msg, code=code + "Login failed at url {!r}".format(r.url), msg, code=code ) def is_logged_in(self): @@ -238,7 +238,7 @@ class Session: r = self._session.get(url, params={"ref": "mb", "h": logout_h}) except requests.RequestException as e: _exception.handle_requests_error(e) - handle_http_error(r.status_code) + _exception.handle_http_error(r.status_code) @classmethod def _from_session(cls, session): @@ -367,6 +367,8 @@ class Session: data["ephemeral_ttl_mode:"] = "0" j = self._post("/messaging/send/", data) + _exception.handle_payload_error(j) + # update JS token if received in response fb_dtsg = _util.get_jsmods_require(j, 2) if fb_dtsg is not None: diff --git a/fbchat/_thread.py b/fbchat/_thread.py index 5ba8378..21e93cb 100644 --- a/fbchat/_thread.py +++ b/fbchat/_thread.py @@ -359,6 +359,9 @@ class ThreadABC(metaclass=abc.ABCMeta): _graphql.from_query_id("515216185516880", data) ) + if not j[self.id]: + raise _exception.ParseError("Could not find images", data=j) + result = j[self.id]["message_shared_media"] print(len(result["edges"])) diff --git a/tests/test_exception.py b/tests/test_exception.py index c9d3d74..64b1ecb 100644 --- a/tests/test_exception.py +++ b/tests/test_exception.py @@ -64,10 +64,10 @@ ERROR_DATA = [ ] -@pytest.mark.parametrize("exception,code,description,summary", ERROR_DATA) +@pytest.mark.parametrize("exception,code,summary,description", ERROR_DATA) def test_handle_payload_error(exception, code, summary, description): data = {"error": code, "errorSummary": summary, "errorDescription": description} - with pytest.raises(exception, match=r"Error sending request: #\d+"): + with pytest.raises(exception, match=r"#\d+ .+:"): handle_payload_error(data) @@ -76,14 +76,14 @@ def test_handle_payload_error_no_error(): assert handle_payload_error({"payload": {"abc": ["Something", "else"]}}) is None -def test_handle_graphql_errors(): +def test_handle_graphql_crash(): error = { "allow_user_retry": False, "api_error_code": -1, "code": 1675030, "debug_info": None, "description": "Error performing query.", - "fbtrace_id": "CLkuLR752sB", + "fbtrace_id": "ABCDEFG", "is_silent": False, "is_transient": False, "message": ( @@ -98,11 +98,103 @@ def test_handle_graphql_errors(): "summary": "Query error", } with pytest.raises( - GraphQLError, match="Query error: #1675030, Errors while executing" + GraphQLError, match="#1675030 Query error: Errors while executing" ): handle_graphql_errors({"data": {"message_thread": None}, "errors": [error]}) +def test_handle_graphql_invalid_values(): + error = { + "message": ( + 'Invalid values provided for variables of operation "MessengerThreadlist":' + ' Value ""as"" cannot be used for variable "$limit": Expected an integer' + ' value, got "as".' + ), + "severity": "CRITICAL", + "code": 1675012, + "api_error_code": None, + "summary": "Your request couldn't be processed", + "description": ( + "There was a problem with this request." + " We're working on getting it fixed as soon as we can." + ), + "is_silent": False, + "is_transient": False, + "requires_reauth": False, + "allow_user_retry": False, + "debug_info": None, + "query_path": None, + "fbtrace_id": "ABCDEFG", + "www_request_id": "AABBCCDDEEFFGG", + } + msg = "#1675012 Your request couldn't be processed: Invalid values" + with pytest.raises(GraphQLError, match=msg): + handle_graphql_errors({"errors": [error]}) + + +def test_handle_graphql_no_message(): + error = { + "code": 1675012, + "api_error_code": None, + "summary": "Your request couldn't be processed", + "description": ( + "There was a problem with this request." + " We're working on getting it fixed as soon as we can." + ), + "is_silent": False, + "is_transient": False, + "requires_reauth": False, + "allow_user_retry": False, + "debug_info": None, + "query_path": None, + "fbtrace_id": "ABCDEFG", + "www_request_id": "AABBCCDDEEFFGG", + "sentry_block_user_info": None, + "help_center_id": None, + } + msg = "#1675012 Your request couldn't be processed: " + with pytest.raises(GraphQLError, match=msg): + handle_graphql_errors({"errors": [error]}) + + +def test_handle_graphql_no_summary(): + error = { + "message": ( + 'Errors while executing operation "MessengerViewerContactMethods":' + " At Query.viewer:Viewer.all_emails: Field implementation threw an" + " exception. Check your server logs for more information." + ), + "severity": "ERROR", + "path": ["viewer", "all_emails"], + } + with pytest.raises(GraphQLError, match="Unknown error: Errors while executing"): + handle_graphql_errors( + {"data": {"viewer": {"user": None, "all_emails": []}}, "errors": [error]} + ) + + +def test_handle_graphql_syntax_error(): + error = { + "code": 1675001, + "api_error_code": None, + "summary": "Query Syntax Error", + "description": "Syntax error.", + "is_silent": True, + "is_transient": False, + "requires_reauth": False, + "allow_user_retry": False, + "debug_info": 'Unexpected ">" at character 328: Expected ")".', + "query_path": None, + "fbtrace_id": "ABCDEFG", + "www_request_id": "AABBCCDDEEFFGG", + "sentry_block_user_info": None, + "help_center_id": None, + } + msg = "#1675001 Query Syntax Error: " + with pytest.raises(GraphQLError, match=msg): + handle_graphql_errors({"response": None, "error": error}) + + def test_handle_graphql_errors_singular_error_key(): with pytest.raises(GraphQLError, match="#123"): handle_graphql_errors({"error": {"code": 123}})