From 8afb20b09200130cf36d3e81cae3aaee1744b098 Mon Sep 17 00:00:00 2001 From: Alexandre Lavigne Date: Sun, 22 Sep 2024 23:20:35 +0200 Subject: [PATCH] Add test on receiving an invalid JSON in the APIError exception handler. Manually build an invalid JSON body. Using HTML to fake using the wrong endpoint that returns HTML instead of JSON. Use that new http client to do some request and make sure the error matches. Signed-off-by: Alexandre Lavigne --- ...readsheetTest.test_bad_json_api_error.json | 232 ++++++++++++++++++ tests/conftest.py | 36 ++- tests/spreadsheet_test.py | 12 +- 3 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 tests/cassettes/SpreadsheetTest.test_bad_json_api_error.json 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))