diff --git a/tests/cassettes/SpreadsheetTest.test_bad_json_api_error.json b/tests/cassettes/SpreadsheetTest.test_bad_json_api_error.json new file mode 100644 index 000000000..a689aaed8 --- /dev/null +++ b/tests/cassettes/SpreadsheetTest.test_bad_json_api_error.json @@ -0,0 +1,232 @@ +{ + "version": 1, + "interactions": [ + { + "request": { + "method": "POST", + "uri": "https://www.googleapis.com/drive/v3/files?supportsAllDrives=True", + "body": "{\"name\": \"Test SpreadsheetTest test_bad_json_api_error\", \"mimeType\": \"application/vnd.google-apps.spreadsheet\"}", + "headers": { + "User-Agent": [ + "python-requests/2.32.3" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Accept": [ + "*/*" + ], + "Connection": [ + "keep-alive" + ], + "Content-Length": [ + "111" + ], + "Content-Type": [ + "application/json" + ], + "authorization": [ + "" + ] + } + }, + "response": { + "status": { + "code": 200, + "message": "OK" + }, + "headers": { + "Expires": [ + "Mon, 01 Jan 1990 00:00:00 GMT" + ], + "Content-Type": [ + "application/json; charset=UTF-8" + ], + "Date": [ + "Sun, 22 Sep 2024 21:14:56 GMT" + ], + "Alt-Svc": [ + "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" + ], + "Transfer-Encoding": [ + "chunked" + ], + "Vary": [ + "Origin, X-Origin" + ], + "X-XSS-Protection": [ + "0" + ], + "X-Frame-Options": [ + "SAMEORIGIN" + ], + "Server": [ + "ESF" + ], + "Cache-Control": [ + "no-cache, no-store, max-age=0, must-revalidate" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Pragma": [ + "no-cache" + ], + "content-length": [ + "198" + ] + }, + "body": { + "string": "{\n \"kind\": \"drive#file\",\n \"id\": \"1YCFTdhfXm6o_JeYoFgYquVItZBeeAju8sF7Xuz1Tr-Y\",\n \"name\": \"Test SpreadsheetTest test_bad_json_api_error\",\n \"mimeType\": \"application/vnd.google-apps.spreadsheet\"\n}\n" + } + } + }, + { + "request": { + "method": "GET", + "uri": "https://sheets.googleapis.com/v4/spreadsheets/1YCFTdhfXm6o_JeYoFgYquVItZBeeAju8sF7Xuz1Tr-Y?includeGridData=false", + "body": null, + "headers": { + "User-Agent": [ + "python-requests/2.32.3" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Accept": [ + "*/*" + ], + "Connection": [ + "keep-alive" + ], + "authorization": [ + "" + ] + } + }, + "response": { + "status": { + "code": 200, + "message": "OK" + }, + "headers": { + "Content-Type": [ + "application/json; charset=UTF-8" + ], + "x-l2-request-path": [ + "l2-managed-6" + ], + "Alt-Svc": [ + "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" + ], + "Transfer-Encoding": [ + "chunked" + ], + "Vary": [ + "Origin", + "X-Origin", + "Referer" + ], + "X-XSS-Protection": [ + "0" + ], + "X-Frame-Options": [ + "SAMEORIGIN" + ], + "Server": [ + "ESF" + ], + "Cache-Control": [ + "private" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Date": [ + "Sun, 22 Sep 2024 21:14:57 GMT" + ], + "content-length": [ + "3342" + ] + }, + "body": { + "string": "{\n \"spreadsheetId\": \"1YCFTdhfXm6o_JeYoFgYquVItZBeeAju8sF7Xuz1Tr-Y\",\n \"properties\": {\n \"title\": \"Test SpreadsheetTest test_bad_json_api_error\",\n \"locale\": \"en_US\",\n \"autoRecalc\": \"ON_CHANGE\",\n \"timeZone\": \"Etc/GMT\",\n \"defaultFormat\": {\n \"backgroundColor\": {\n \"red\": 1,\n \"green\": 1,\n \"blue\": 1\n },\n \"padding\": {\n \"top\": 2,\n \"right\": 3,\n \"bottom\": 2,\n \"left\": 3\n },\n \"verticalAlignment\": \"BOTTOM\",\n \"wrapStrategy\": \"OVERFLOW_CELL\",\n \"textFormat\": {\n \"foregroundColor\": {},\n \"fontFamily\": \"arial,sans,sans-serif\",\n \"fontSize\": 10,\n \"bold\": false,\n \"italic\": false,\n \"strikethrough\": false,\n \"underline\": false,\n \"foregroundColorStyle\": {\n \"rgbColor\": {}\n }\n },\n \"backgroundColorStyle\": {\n \"rgbColor\": {\n \"red\": 1,\n \"green\": 1,\n \"blue\": 1\n }\n }\n },\n \"spreadsheetTheme\": {\n \"primaryFontFamily\": \"Arial\",\n \"themeColors\": [\n {\n \"colorType\": \"TEXT\",\n \"color\": {\n \"rgbColor\": {}\n }\n },\n {\n \"colorType\": \"BACKGROUND\",\n \"color\": {\n \"rgbColor\": {\n \"red\": 1,\n \"green\": 1,\n \"blue\": 1\n }\n }\n },\n {\n \"colorType\": \"ACCENT1\",\n \"color\": {\n \"rgbColor\": {\n \"red\": 0.25882354,\n \"green\": 0.52156866,\n \"blue\": 0.95686275\n }\n }\n },\n {\n \"colorType\": \"ACCENT2\",\n \"color\": {\n \"rgbColor\": {\n \"red\": 0.91764706,\n \"green\": 0.2627451,\n \"blue\": 0.20784314\n }\n }\n },\n {\n \"colorType\": \"ACCENT3\",\n \"color\": {\n \"rgbColor\": {\n \"red\": 0.9843137,\n \"green\": 0.7372549,\n \"blue\": 0.015686275\n }\n }\n },\n {\n \"colorType\": \"ACCENT4\",\n \"color\": {\n \"rgbColor\": {\n \"red\": 0.20392157,\n \"green\": 0.65882355,\n \"blue\": 0.3254902\n }\n }\n },\n {\n \"colorType\": \"ACCENT5\",\n \"color\": {\n \"rgbColor\": {\n \"red\": 1,\n \"green\": 0.42745098,\n \"blue\": 0.003921569\n }\n }\n },\n {\n \"colorType\": \"ACCENT6\",\n \"color\": {\n \"rgbColor\": {\n \"red\": 0.27450982,\n \"green\": 0.7411765,\n \"blue\": 0.7764706\n }\n }\n },\n {\n \"colorType\": \"LINK\",\n \"color\": {\n \"rgbColor\": {\n \"red\": 0.06666667,\n \"green\": 0.33333334,\n \"blue\": 0.8\n }\n }\n }\n ]\n }\n },\n \"sheets\": [\n {\n \"properties\": {\n \"sheetId\": 0,\n \"title\": \"Sheet1\",\n \"index\": 0,\n \"sheetType\": \"GRID\",\n \"gridProperties\": {\n \"rowCount\": 1000,\n \"columnCount\": 26\n }\n }\n }\n ],\n \"spreadsheetUrl\": \"https://docs.google.com/spreadsheets/d/1YCFTdhfXm6o_JeYoFgYquVItZBeeAju8sF7Xuz1Tr-Y/edit\"\n}\n" + } + } + }, + { + "request": { + "method": "DELETE", + "uri": "https://www.googleapis.com/drive/v3/files/1YCFTdhfXm6o_JeYoFgYquVItZBeeAju8sF7Xuz1Tr-Y?supportsAllDrives=True", + "body": null, + "headers": { + "User-Agent": [ + "python-requests/2.32.3" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Accept": [ + "*/*" + ], + "Connection": [ + "keep-alive" + ], + "Content-Length": [ + "0" + ], + "authorization": [ + "" + ] + } + }, + "response": { + "status": { + "code": 204, + "message": "No Content" + }, + "headers": { + "Expires": [ + "Mon, 01 Jan 1990 00:00:00 GMT" + ], + "Content-Type": [ + "text/html" + ], + "Alt-Svc": [ + "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" + ], + "Content-Length": [ + "0" + ], + "Vary": [ + "Origin, X-Origin" + ], + "X-XSS-Protection": [ + "0" + ], + "X-Frame-Options": [ + "SAMEORIGIN" + ], + "Server": [ + "ESF" + ], + "Cache-Control": [ + "no-cache, no-store, max-age=0, must-revalidate" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Pragma": [ + "no-cache" + ], + "Date": [ + "Sun, 22 Sep 2024 21:14:57 GMT" + ] + }, + "body": { + "string": "" + } + } + } + ] +} diff --git a/tests/conftest.py b/tests/conftest.py index 676f54829..82f17da48 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,8 @@ +import io import itertools import os import unittest -from typing import Any, Dict, Generator, Optional +from typing import Any, Dict, Generator, Optional, Tuple import pytest from google.auth.credentials import Credentials @@ -107,6 +108,26 @@ def request(self, *args: Any, **kwargs: Any) -> Response: raise e +class InvalidJsonApiErrorClient(VCRHTTPClient): + """Special HTTP client that always raises an exception due to 500 error with + an invalid JSON body. + In this case for now it returns some HTML to simulate the use of the wrong HTTP endpoint. + """ + + ERROR_MSG = bytes("

Failed

", "utf-8") + + def request(self, *args: Any, **kwargs: Any) -> Response: + resp = Response() + # fake an HTML response instead of a valid JSON response. + # urllib3 expect 'raw' to be bytes. + resp.raw = io.BytesIO(self.ERROR_MSG) + resp.status_code = 500 + resp.encoding = "text/html" + + # now raise the APIError exception as the regular HTTP client would + raise gspread.exceptions.APIError(resp) + + @pytest.fixture(scope="module") def client() -> Client: if CREDS_FILENAME is not None: @@ -119,3 +140,16 @@ def client() -> Client: raise AssertionError return gc + + +def invalid_json_client() -> Tuple[Client, bytes]: + """Returns an HTTP client that always returns an invalid JSON payload + and the expected error message from the raised exception. + """ + return ( + Client( + auth=DummyCredentials(DUMMY_ACCESS_TOKEN), + http_client=InvalidJsonApiErrorClient, + ), + InvalidJsonApiErrorClient.ERROR_MSG, + ) diff --git a/tests/spreadsheet_test.py b/tests/spreadsheet_test.py index 79d78ac82..ca1b5ed79 100644 --- a/tests/spreadsheet_test.py +++ b/tests/spreadsheet_test.py @@ -5,7 +5,7 @@ import gspread -from .conftest import GspreadTest +from .conftest import GspreadTest, invalid_json_client class SpreadsheetTest(GspreadTest): @@ -20,6 +20,16 @@ def init(self, client, request): client.del_spreadsheet(SpreadsheetTest.spreadsheet.id) + @pytest.mark.vcr() + def test_bad_json_api_error(self): + # no need to pass auth tokens we use a custom HTTP Client that always fail + bad_client, error_msg = invalid_json_client() + + with pytest.raises(gspread.exceptions.APIError) as e: + bad_client.get_file_drive_metadata("abcdef0123456789") + + self.assertTrue(e.match(error_msg.decode())) + @pytest.mark.vcr() def test_properties(self): self.assertTrue(re.match(r"^[a-zA-Z0-9-_]+$", self.spreadsheet.id))