From 72a9eea626784f9eaeaf83103646a76e4828976d Mon Sep 17 00:00:00 2001 From: Kien Dang Date: Tue, 20 Dec 2022 10:56:46 +0800 Subject: [PATCH 01/15] Update dependencies --- setup.cfg | 1 + setup.py | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/setup.cfg b/setup.cfg index b943008..615a251 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,6 +5,7 @@ ignore = E203, E501, W503 [isort] known_first_party=graphql_server +profile=black multi_line_output=3 include_trailing_comma=True force_grid_wrap=0 diff --git a/setup.py b/setup.py index e2dfcaf..008589e 100644 --- a/setup.py +++ b/setup.py @@ -1,21 +1,23 @@ from re import search from setuptools import setup, find_packages -install_requires = ["graphql-core>=3.2,<3.3", "typing-extensions>=4,<5"] +install_requires = [ + "graphql-core>=3.2,<3.3", + "Jinja2>=3.1,<4", + "typing-extensions>=4,<5", +] tests_requires = [ - "pytest>=6.2,<6.3", - "pytest-asyncio>=0.16,<1", - "pytest-cov>=3,<4", - "aiohttp>=3.8,<4", - "Jinja2>=2.11,<3", + "pytest>=7.2,<8", + "pytest-asyncio>=0.20,<1", + "pytest-cov>=4,<5", ] dev_requires = [ - "flake8>=4,<5", + "flake8>=5,<6", "isort>=5,<6", - "black>=19.10b0", - "mypy>=0.931,<1", + "black>=22.12,<22.13", + "mypy>=0.991,<1", "check-manifest>=0.47,<1", ] + tests_requires @@ -35,7 +37,7 @@ "aiohttp>=3.8,<4", ] -install_quart_requires = ["quart>=0.6.15,<0.15"] +install_quart_requires = ["quart>=0.15,<1"] install_all_requires = ( install_requires From 7b45e70ed710d2aba1517cd7bb1f97124bad31a4 Mon Sep 17 00:00:00 2001 From: Giovanni Campagna Date: Fri, 13 May 2022 11:32:35 -0700 Subject: [PATCH 02/15] Relax flask dependency to allow flask 2 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 008589e..695fd92 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ ] + tests_requires install_flask_requires = [ - "flask>=1,<2", + "flask>=1,<3", ] install_sanic_requires = [ From d184cdc8b9dc457cfea18ae84aae187527f493e2 Mon Sep 17 00:00:00 2001 From: Choongkyu Kim Date: Mon, 5 Sep 2022 22:58:51 -0700 Subject: [PATCH 03/15] Fixes for quart >=0.15 Fix quart.request.get_data signature QuartClient -> TestClientProtocol --- graphql_server/quart/graphqlview.py | 4 +- tests/quart/test_graphiqlview.py | 24 ++-- tests/quart/test_graphqlview.py | 183 ++++++++++++++++------------ 3 files changed, 120 insertions(+), 91 deletions(-) diff --git a/graphql_server/quart/graphqlview.py b/graphql_server/quart/graphqlview.py index ff737ec..6c94364 100644 --- a/graphql_server/quart/graphqlview.py +++ b/graphql_server/quart/graphqlview.py @@ -165,11 +165,11 @@ async def parse_body(): # information provided by content_type content_type = request.mimetype if content_type == "application/graphql": - refined_data = await request.get_data(raw=False) + refined_data = await request.get_data(as_text=True) return {"query": refined_data} elif content_type == "application/json": - refined_data = await request.get_data(raw=False) + refined_data = await request.get_data(as_text=True) return load_json_body(refined_data) elif content_type == "application/x-www-form-urlencoded": diff --git a/tests/quart/test_graphiqlview.py b/tests/quart/test_graphiqlview.py index 1d8d7e3..d183366 100644 --- a/tests/quart/test_graphiqlview.py +++ b/tests/quart/test_graphiqlview.py @@ -1,6 +1,8 @@ +from typing import Optional + import pytest from quart import Quart, Response, url_for -from quart.testing import QuartClient +from quart.typing import TestClientProtocol from werkzeug.datastructures import Headers from .app import create_app @@ -18,16 +20,16 @@ def app() -> Quart: @pytest.fixture -def client(app: Quart) -> QuartClient: +def client(app: Quart) -> TestClientProtocol: return app.test_client() @pytest.mark.asyncio async def execute_client( app: Quart, - client: QuartClient, + client: TestClientProtocol, method: str = "GET", - headers: Headers = None, + headers: Optional[Headers] = None, **extra_params ) -> Response: test_request_context = app.test_request_context(path="/", method=method) @@ -37,7 +39,7 @@ async def execute_client( @pytest.mark.asyncio -async def test_graphiql_is_enabled(app: Quart, client: QuartClient): +async def test_graphiql_is_enabled(app: Quart, client: TestClientProtocol): response = await execute_client( app, client, headers=Headers({"Accept": "text/html"}), externals=False ) @@ -45,7 +47,7 @@ async def test_graphiql_is_enabled(app: Quart, client: QuartClient): @pytest.mark.asyncio -async def test_graphiql_renders_pretty(app: Quart, client: QuartClient): +async def test_graphiql_renders_pretty(app: Quart, client: TestClientProtocol): response = await execute_client( app, client, headers=Headers({"Accept": "text/html"}), query="{test}" ) @@ -57,16 +59,16 @@ async def test_graphiql_renders_pretty(app: Quart, client: QuartClient): " }\n" "}".replace('"', '\\"').replace("\n", "\\n") ) - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert pretty_response in result @pytest.mark.asyncio -async def test_graphiql_default_title(app: Quart, client: QuartClient): +async def test_graphiql_default_title(app: Quart, client: TestClientProtocol): response = await execute_client( app, client, headers=Headers({"Accept": "text/html"}) ) - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert "GraphiQL" in result @@ -74,9 +76,9 @@ async def test_graphiql_default_title(app: Quart, client: QuartClient): @pytest.mark.parametrize( "app", [create_app(graphiql=True, graphiql_html_title="Awesome")] ) -async def test_graphiql_custom_title(app: Quart, client: QuartClient): +async def test_graphiql_custom_title(app: Quart, client: TestClientProtocol): response = await execute_client( app, client, headers=Headers({"Accept": "text/html"}) ) - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert "Awesome" in result diff --git a/tests/quart/test_graphqlview.py b/tests/quart/test_graphqlview.py index 79d1f73..8e0833c 100644 --- a/tests/quart/test_graphqlview.py +++ b/tests/quart/test_graphqlview.py @@ -1,9 +1,10 @@ import json +from typing import Optional from urllib.parse import urlencode import pytest from quart import Quart, Response, url_for -from quart.testing import QuartClient +from quart.typing import TestClientProtocol from werkzeug.datastructures import Headers from .app import create_app @@ -21,17 +22,17 @@ def app() -> Quart: @pytest.fixture -def client(app: Quart) -> QuartClient: +def client(app: Quart) -> TestClientProtocol: return app.test_client() @pytest.mark.asyncio async def execute_client( app: Quart, - client: QuartClient, + client: TestClientProtocol, method: str = "GET", - data: str = None, - headers: Headers = None, + data: Optional[str] = None, + headers: Optional[Headers] = None, **url_params, ) -> Response: test_request_context = app.test_request_context(path="/", method=method) @@ -62,16 +63,16 @@ def json_dump_kwarg_list(**kwargs): @pytest.mark.asyncio -async def test_allows_get_with_query_param(app: Quart, client: QuartClient): +async def test_allows_get_with_query_param(app: Quart, client: TestClientProtocol): response = await execute_client(app, client, query="{test}") assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == {"data": {"test": "Hello World"}} @pytest.mark.asyncio -async def test_allows_get_with_variable_values(app: Quart, client: QuartClient): +async def test_allows_get_with_variable_values(app: Quart, client: TestClientProtocol): response = await execute_client( app, client, @@ -80,12 +81,12 @@ async def test_allows_get_with_variable_values(app: Quart, client: QuartClient): ) assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == {"data": {"test": "Hello Dolly"}} @pytest.mark.asyncio -async def test_allows_get_with_operation_name(app: Quart, client: QuartClient): +async def test_allows_get_with_operation_name(app: Quart, client: TestClientProtocol): response = await execute_client( app, client, @@ -101,20 +102,20 @@ async def test_allows_get_with_operation_name(app: Quart, client: QuartClient): ) assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == { "data": {"test": "Hello World", "shared": "Hello Everyone"} } @pytest.mark.asyncio -async def test_reports_validation_errors(app: Quart, client: QuartClient): +async def test_reports_validation_errors(app: Quart, client: TestClientProtocol): response = await execute_client( app, client, query="{ test, unknownOne, unknownTwo }" ) assert response.status_code == 400 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == { "errors": [ { @@ -130,7 +131,9 @@ async def test_reports_validation_errors(app: Quart, client: QuartClient): @pytest.mark.asyncio -async def test_errors_when_missing_operation_name(app: Quart, client: QuartClient): +async def test_errors_when_missing_operation_name( + app: Quart, client: TestClientProtocol +): response = await execute_client( app, client, @@ -141,7 +144,7 @@ async def test_errors_when_missing_operation_name(app: Quart, client: QuartClien ) assert response.status_code == 400 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == { "errors": [ { @@ -153,7 +156,9 @@ async def test_errors_when_missing_operation_name(app: Quart, client: QuartClien @pytest.mark.asyncio -async def test_errors_when_sending_a_mutation_via_get(app: Quart, client: QuartClient): +async def test_errors_when_sending_a_mutation_via_get( + app: Quart, client: TestClientProtocol +): response = await execute_client( app, client, @@ -162,7 +167,7 @@ async def test_errors_when_sending_a_mutation_via_get(app: Quart, client: QuartC """, ) assert response.status_code == 405 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == { "errors": [ { @@ -174,7 +179,7 @@ async def test_errors_when_sending_a_mutation_via_get(app: Quart, client: QuartC @pytest.mark.asyncio async def test_errors_when_selecting_a_mutation_within_a_get( - app: Quart, client: QuartClient + app: Quart, client: TestClientProtocol ): response = await execute_client( app, @@ -187,7 +192,7 @@ async def test_errors_when_selecting_a_mutation_within_a_get( ) assert response.status_code == 405 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == { "errors": [ { @@ -198,7 +203,9 @@ async def test_errors_when_selecting_a_mutation_within_a_get( @pytest.mark.asyncio -async def test_allows_mutation_to_exist_within_a_get(app: Quart, client: QuartClient): +async def test_allows_mutation_to_exist_within_a_get( + app: Quart, client: TestClientProtocol +): response = await execute_client( app, client, @@ -210,12 +217,12 @@ async def test_allows_mutation_to_exist_within_a_get(app: Quart, client: QuartCl ) assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == {"data": {"test": "Hello World"}} @pytest.mark.asyncio -async def test_allows_post_with_json_encoding(app: Quart, client: QuartClient): +async def test_allows_post_with_json_encoding(app: Quart, client: TestClientProtocol): response = await execute_client( app, client, @@ -225,12 +232,14 @@ async def test_allows_post_with_json_encoding(app: Quart, client: QuartClient): ) assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == {"data": {"test": "Hello World"}} @pytest.mark.asyncio -async def test_allows_sending_a_mutation_via_post(app: Quart, client: QuartClient): +async def test_allows_sending_a_mutation_via_post( + app: Quart, client: TestClientProtocol +): response = await execute_client( app, client, @@ -240,12 +249,12 @@ async def test_allows_sending_a_mutation_via_post(app: Quart, client: QuartClien ) assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == {"data": {"writeTest": {"test": "Hello World"}}} @pytest.mark.asyncio -async def test_allows_post_with_url_encoding(app: Quart, client: QuartClient): +async def test_allows_post_with_url_encoding(app: Quart, client: TestClientProtocol): response = await execute_client( app, client, @@ -255,13 +264,13 @@ async def test_allows_post_with_url_encoding(app: Quart, client: QuartClient): ) assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == {"data": {"test": "Hello World"}} @pytest.mark.asyncio async def test_supports_post_json_query_with_string_variables( - app: Quart, client: QuartClient + app: Quart, client: TestClientProtocol ): response = await execute_client( app, @@ -275,13 +284,13 @@ async def test_supports_post_json_query_with_string_variables( ) assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == {"data": {"test": "Hello Dolly"}} @pytest.mark.asyncio async def test_supports_post_json_query_with_json_variables( - app: Quart, client: QuartClient + app: Quart, client: TestClientProtocol ): response = await execute_client( app, @@ -295,13 +304,13 @@ async def test_supports_post_json_query_with_json_variables( ) assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == {"data": {"test": "Hello Dolly"}} @pytest.mark.asyncio async def test_supports_post_url_encoded_query_with_string_variables( - app: Quart, client: QuartClient + app: Quart, client: TestClientProtocol ): response = await execute_client( app, @@ -317,13 +326,13 @@ async def test_supports_post_url_encoded_query_with_string_variables( ) assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == {"data": {"test": "Hello Dolly"}} @pytest.mark.asyncio async def test_supports_post_json_query_with_get_variable_values( - app: Quart, client: QuartClient + app: Quart, client: TestClientProtocol ): response = await execute_client( app, @@ -337,13 +346,13 @@ async def test_supports_post_json_query_with_get_variable_values( ) assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == {"data": {"test": "Hello Dolly"}} @pytest.mark.asyncio async def test_post_url_encoded_query_with_get_variable_values( - app: Quart, client: QuartClient + app: Quart, client: TestClientProtocol ): response = await execute_client( app, @@ -359,13 +368,13 @@ async def test_post_url_encoded_query_with_get_variable_values( ) assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == {"data": {"test": "Hello Dolly"}} @pytest.mark.asyncio async def test_supports_post_raw_text_query_with_get_variable_values( - app: Quart, client: QuartClient + app: Quart, client: TestClientProtocol ): response = await execute_client( app, @@ -377,12 +386,12 @@ async def test_supports_post_raw_text_query_with_get_variable_values( ) assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == {"data": {"test": "Hello Dolly"}} @pytest.mark.asyncio -async def test_allows_post_with_operation_name(app: Quart, client: QuartClient): +async def test_allows_post_with_operation_name(app: Quart, client: TestClientProtocol): response = await execute_client( app, client, @@ -402,14 +411,16 @@ async def test_allows_post_with_operation_name(app: Quart, client: QuartClient): ) assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == { "data": {"test": "Hello World", "shared": "Hello Everyone"} } @pytest.mark.asyncio -async def test_allows_post_with_get_operation_name(app: Quart, client: QuartClient): +async def test_allows_post_with_get_operation_name( + app: Quart, client: TestClientProtocol +): response = await execute_client( app, client, @@ -427,7 +438,7 @@ async def test_allows_post_with_get_operation_name(app: Quart, client: QuartClie ) assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == { "data": {"test": "Hello World", "shared": "Hello Everyone"} } @@ -435,35 +446,39 @@ async def test_allows_post_with_get_operation_name(app: Quart, client: QuartClie @pytest.mark.asyncio @pytest.mark.parametrize("app", [create_app(pretty=True)]) -async def test_supports_pretty_printing(app: Quart, client: QuartClient): +async def test_supports_pretty_printing(app: Quart, client: TestClientProtocol): response = await execute_client(app, client, query="{test}") - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert result == ("{\n" ' "data": {\n' ' "test": "Hello World"\n' " }\n" "}") @pytest.mark.asyncio @pytest.mark.parametrize("app", [create_app(pretty=False)]) -async def test_not_pretty_by_default(app: Quart, client: QuartClient): +async def test_not_pretty_by_default(app: Quart, client: TestClientProtocol): response = await execute_client(app, client, query="{test}") - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert result == '{"data":{"test":"Hello World"}}' @pytest.mark.asyncio -async def test_supports_pretty_printing_by_request(app: Quart, client: QuartClient): +async def test_supports_pretty_printing_by_request( + app: Quart, client: TestClientProtocol +): response = await execute_client(app, client, query="{test}", pretty="1") - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert result == "{\n" ' "data": {\n' ' "test": "Hello World"\n' " }\n" "}" @pytest.mark.asyncio -async def test_handles_field_errors_caught_by_graphql(app: Quart, client: QuartClient): +async def test_handles_field_errors_caught_by_graphql( + app: Quart, client: TestClientProtocol +): response = await execute_client(app, client, query="{thrower}") assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == { "errors": [ { @@ -477,10 +492,12 @@ async def test_handles_field_errors_caught_by_graphql(app: Quart, client: QuartC @pytest.mark.asyncio -async def test_handles_syntax_errors_caught_by_graphql(app: Quart, client: QuartClient): +async def test_handles_syntax_errors_caught_by_graphql( + app: Quart, client: TestClientProtocol +): response = await execute_client(app, client, query="syntaxerror") assert response.status_code == 400 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == { "errors": [ { @@ -493,19 +510,21 @@ async def test_handles_syntax_errors_caught_by_graphql(app: Quart, client: Quart @pytest.mark.asyncio async def test_handles_errors_caused_by_a_lack_of_query( - app: Quart, client: QuartClient + app: Quart, client: TestClientProtocol ): response = await execute_client(app, client) assert response.status_code == 400 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == { "errors": [{"message": "Must provide query string."}] } @pytest.mark.asyncio -async def test_handles_batch_correctly_if_is_disabled(app: Quart, client: QuartClient): +async def test_handles_batch_correctly_if_is_disabled( + app: Quart, client: TestClientProtocol +): response = await execute_client( app, client, @@ -515,7 +534,7 @@ async def test_handles_batch_correctly_if_is_disabled(app: Quart, client: QuartC ) assert response.status_code == 400 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == { "errors": [ { @@ -526,7 +545,7 @@ async def test_handles_batch_correctly_if_is_disabled(app: Quart, client: QuartC @pytest.mark.asyncio -async def test_handles_incomplete_json_bodies(app: Quart, client: QuartClient): +async def test_handles_incomplete_json_bodies(app: Quart, client: TestClientProtocol): response = await execute_client( app, client, @@ -536,14 +555,14 @@ async def test_handles_incomplete_json_bodies(app: Quart, client: QuartClient): ) assert response.status_code == 400 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == { "errors": [{"message": "POST body sent invalid JSON."}] } @pytest.mark.asyncio -async def test_handles_plain_post_text(app: Quart, client: QuartClient): +async def test_handles_plain_post_text(app: Quart, client: TestClientProtocol): response = await execute_client( app, client, @@ -553,14 +572,14 @@ async def test_handles_plain_post_text(app: Quart, client: QuartClient): variables=json.dumps({"who": "Dolly"}), ) assert response.status_code == 400 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == { "errors": [{"message": "Must provide query string."}] } @pytest.mark.asyncio -async def test_handles_poorly_formed_variables(app: Quart, client: QuartClient): +async def test_handles_poorly_formed_variables(app: Quart, client: TestClientProtocol): response = await execute_client( app, client, @@ -568,17 +587,17 @@ async def test_handles_poorly_formed_variables(app: Quart, client: QuartClient): variables="who:You", ) assert response.status_code == 400 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == { "errors": [{"message": "Variables are invalid JSON."}] } @pytest.mark.asyncio -async def test_handles_unsupported_http_methods(app: Quart, client: QuartClient): +async def test_handles_unsupported_http_methods(app: Quart, client: TestClientProtocol): response = await execute_client(app, client, method="PUT", query="{test}") assert response.status_code == 405 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response.headers["Allow"] in ["GET, POST", "HEAD, GET, POST, OPTIONS"] assert response_json(result) == { "errors": [{"message": "GraphQL only supports GET and POST requests."}] @@ -586,21 +605,25 @@ async def test_handles_unsupported_http_methods(app: Quart, client: QuartClient) @pytest.mark.asyncio -async def test_passes_request_into_request_context(app: Quart, client: QuartClient): +async def test_passes_request_into_request_context( + app: Quart, client: TestClientProtocol +): response = await execute_client(app, client, query="{request}", q="testing") assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == {"data": {"request": "testing"}} @pytest.mark.asyncio @pytest.mark.parametrize("app", [create_app(context={"session": "CUSTOM CONTEXT"})]) -async def test_passes_custom_context_into_context(app: Quart, client: QuartClient): +async def test_passes_custom_context_into_context( + app: Quart, client: TestClientProtocol +): response = await execute_client(app, client, query="{context { session request }}") assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) res = response_json(result) assert "data" in res assert "session" in res["data"]["context"] @@ -611,11 +634,11 @@ async def test_passes_custom_context_into_context(app: Quart, client: QuartClien @pytest.mark.asyncio @pytest.mark.parametrize("app", [create_app(context="CUSTOM CONTEXT")]) -async def test_context_remapped_if_not_mapping(app: Quart, client: QuartClient): +async def test_context_remapped_if_not_mapping(app: Quart, client: TestClientProtocol): response = await execute_client(app, client, query="{context { session request }}") assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) res = response_json(result) assert "data" in res assert "session" in res["data"]["context"] @@ -625,7 +648,7 @@ async def test_context_remapped_if_not_mapping(app: Quart, client: QuartClient): # @pytest.mark.asyncio -# async def test_post_multipart_data(app: Quart, client: QuartClient): +# async def test_post_multipart_data(app: Quart, client: TestClientProtocol): # query = "mutation TestMutation { writeTest { test } }" # response = await execute_client( # app, @@ -644,7 +667,9 @@ async def test_context_remapped_if_not_mapping(app: Quart, client: QuartClient): @pytest.mark.asyncio @pytest.mark.parametrize("app", [create_app(batch=True)]) -async def test_batch_allows_post_with_json_encoding(app: Quart, client: QuartClient): +async def test_batch_allows_post_with_json_encoding( + app: Quart, client: TestClientProtocol +): response = await execute_client( app, client, @@ -654,14 +679,14 @@ async def test_batch_allows_post_with_json_encoding(app: Quart, client: QuartCli ) assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == [{"data": {"test": "Hello World"}}] @pytest.mark.asyncio @pytest.mark.parametrize("app", [create_app(batch=True)]) async def test_batch_supports_post_json_query_with_json_variables( - app: Quart, client: QuartClient + app: Quart, client: TestClientProtocol ): response = await execute_client( app, @@ -675,13 +700,15 @@ async def test_batch_supports_post_json_query_with_json_variables( ) assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == [{"data": {"test": "Hello Dolly"}}] @pytest.mark.asyncio @pytest.mark.parametrize("app", [create_app(batch=True)]) -async def test_batch_allows_post_with_operation_name(app: Quart, client: QuartClient): +async def test_batch_allows_post_with_operation_name( + app: Quart, client: TestClientProtocol +): response = await execute_client( app, client, @@ -702,7 +729,7 @@ async def test_batch_allows_post_with_operation_name(app: Quart, client: QuartCl ) assert response.status_code == 200 - result = await response.get_data(raw=False) + result = await response.get_data(as_text=True) assert response_json(result) == [ {"data": {"test": "Hello World", "shared": "Hello Everyone"}} ] From 8bacf9a9b08cf63ce85ac73b169f07a0afd11fc5 Mon Sep 17 00:00:00 2001 From: Kien Dang Date: Tue, 20 Dec 2022 11:08:59 +0800 Subject: [PATCH 04/15] Lint --- graphql_server/__init__.py | 12 +----------- tests/test_asyncio.py | 6 +----- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/graphql_server/__init__.py b/graphql_server/__init__.py index ee54cdb..9a58a9f 100644 --- a/graphql_server/__init__.py +++ b/graphql_server/__init__.py @@ -9,17 +9,7 @@ import json from collections import namedtuple from collections.abc import MutableMapping -from typing import ( - Any, - Callable, - Collection, - Dict, - List, - Optional, - Type, - Union, - cast, -) +from typing import Any, Callable, Collection, Dict, List, Optional, Type, Union, cast from graphql.error import GraphQLError from graphql.execution import ExecutionResult, execute diff --git a/tests/test_asyncio.py b/tests/test_asyncio.py index e07a2f8..ee999af 100644 --- a/tests/test_asyncio.py +++ b/tests/test_asyncio.py @@ -1,10 +1,6 @@ import asyncio -from graphql.type.definition import ( - GraphQLField, - GraphQLNonNull, - GraphQLObjectType, -) +from graphql.type.definition import GraphQLField, GraphQLNonNull, GraphQLObjectType from graphql.type.scalars import GraphQLString from graphql.type.schema import GraphQLSchema From ee5b0775e46ceb5db33f3f1399c07c7014675d83 Mon Sep 17 00:00:00 2001 From: Kien Dang Date: Tue, 20 Dec 2022 13:01:09 +0800 Subject: [PATCH 05/15] Fix aiohttp tests --- tests/aiohttp/test_graphiqlview.py | 3 ++- tests/aiohttp/test_graphqlview.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/aiohttp/test_graphiqlview.py b/tests/aiohttp/test_graphiqlview.py index 4e5bd32..1dd30e0 100644 --- a/tests/aiohttp/test_graphiqlview.py +++ b/tests/aiohttp/test_graphiqlview.py @@ -1,4 +1,5 @@ import pytest +import pytest_asyncio from aiohttp.test_utils import TestClient, TestServer from jinja2 import Environment @@ -12,7 +13,7 @@ def app(): return app -@pytest.fixture +@pytest_asyncio.fixture async def client(app): client = TestClient(TestServer(app)) await client.start_server() diff --git a/tests/aiohttp/test_graphqlview.py b/tests/aiohttp/test_graphqlview.py index 815d23d..4a06ec5 100644 --- a/tests/aiohttp/test_graphqlview.py +++ b/tests/aiohttp/test_graphqlview.py @@ -2,6 +2,7 @@ from urllib.parse import urlencode import pytest +import pytest_asyncio from aiohttp import FormData from aiohttp.test_utils import TestClient, TestServer @@ -15,7 +16,7 @@ def app(): return app -@pytest.fixture +@pytest_asyncio.fixture async def client(app): client = TestClient(TestServer(app)) await client.start_server() From 80d3f7c70071b467600c024800ef828366275115 Mon Sep 17 00:00:00 2001 From: Kien Dang Date: Tue, 20 Dec 2022 15:50:44 +0800 Subject: [PATCH 06/15] Update sanic to v22.6 --- graphql_server/sanic/graphqlview.py | 4 +- setup.py | 3 +- tests/sanic/app.py | 5 +- tests/sanic/test_graphiqlview.py | 14 ++--- tests/sanic/test_graphqlview.py | 89 ++++++++++++++++------------- 5 files changed, 61 insertions(+), 54 deletions(-) diff --git a/graphql_server/sanic/graphqlview.py b/graphql_server/sanic/graphqlview.py index 569db53..8604e33 100644 --- a/graphql_server/sanic/graphqlview.py +++ b/graphql_server/sanic/graphqlview.py @@ -85,7 +85,7 @@ def get_validation_rules(self): return specified_rules return self.validation_rules - async def dispatch_request(self, request, *args, **kwargs): + async def __handle_request(self, request, *args, **kwargs): try: request_method = request.method.lower() data = self.parse_body(request) @@ -173,6 +173,8 @@ async def dispatch_request(self, request, *args, **kwargs): content_type="application/json", ) + get = post = put = head = options = patch = delete = __handle_request + # noinspection PyBroadException def parse_body(self, request): content_type = self.get_mime_type(request) diff --git a/setup.py b/setup.py index 695fd92..654ce11 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,7 @@ "pytest>=7.2,<8", "pytest-asyncio>=0.20,<1", "pytest-cov>=4,<5", + "sanic-testing>=22.3,<22.7", ] dev_requires = [ @@ -26,7 +27,7 @@ ] install_sanic_requires = [ - "sanic>=20.3,<21", + "sanic>=21.12,<22.7", ] install_webob_requires = [ diff --git a/tests/sanic/app.py b/tests/sanic/app.py index 84269cc..342c1fe 100644 --- a/tests/sanic/app.py +++ b/tests/sanic/app.py @@ -1,7 +1,6 @@ from urllib.parse import urlencode from sanic import Sanic -from sanic.testing import SanicTestClient from graphql_server.sanic import GraphQLView @@ -11,13 +10,11 @@ def create_app(path="/graphql", **kwargs): - app = Sanic(__name__) - app.debug = True + app = Sanic("TestApp") schema = kwargs.pop("schema", None) or Schema app.add_route(GraphQLView.as_view(schema=schema, **kwargs), path) - app.client = SanicTestClient(app) return app diff --git a/tests/sanic/test_graphiqlview.py b/tests/sanic/test_graphiqlview.py index 91711f0..06dbe27 100644 --- a/tests/sanic/test_graphiqlview.py +++ b/tests/sanic/test_graphiqlview.py @@ -18,7 +18,7 @@ def pretty_response(): @pytest.mark.parametrize("app", [create_app(graphiql=True)]) def test_graphiql_is_enabled(app): - _, response = app.client.get( + _, response = app.test_client.get( uri=url_string(query="{test}"), headers={"Accept": "text/html"} ) assert response.status == 200 @@ -26,7 +26,7 @@ def test_graphiql_is_enabled(app): @pytest.mark.parametrize("app", [create_app(graphiql=True)]) def test_graphiql_simple_renderer(app, pretty_response): - _, response = app.client.get( + _, response = app.test_client.get( uri=url_string(query="{test}"), headers={"Accept": "text/html"} ) assert response.status == 200 @@ -35,7 +35,7 @@ def test_graphiql_simple_renderer(app, pretty_response): @pytest.mark.parametrize("app", [create_app(graphiql=True, jinja_env=Environment())]) def test_graphiql_jinja_renderer(app, pretty_response): - _, response = app.client.get( + _, response = app.test_client.get( uri=url_string(query="{test}"), headers={"Accept": "text/html"} ) assert response.status == 200 @@ -46,7 +46,7 @@ def test_graphiql_jinja_renderer(app, pretty_response): "app", [create_app(graphiql=True, jinja_env=Environment(enable_async=True))] ) def test_graphiql_jinja_async_renderer(app, pretty_response): - _, response = app.client.get( + _, response = app.test_client.get( uri=url_string(query="{test}"), headers={"Accept": "text/html"} ) assert response.status == 200 @@ -55,7 +55,7 @@ def test_graphiql_jinja_async_renderer(app, pretty_response): @pytest.mark.parametrize("app", [create_app(graphiql=True)]) def test_graphiql_html_is_not_accepted(app): - _, response = app.client.get( + _, response = app.test_client.get( uri=url_string(), headers={"Accept": "application/json"} ) assert response.status == 400 @@ -66,7 +66,7 @@ def test_graphiql_html_is_not_accepted(app): ) def test_graphiql_enabled_async_schema(app): query = "{a,b,c}" - _, response = app.client.get( + _, response = app.test_client.get( uri=url_string(query=query), headers={"Accept": "text/html"} ) @@ -93,7 +93,7 @@ def test_graphiql_enabled_async_schema(app): ) def test_graphiql_enabled_sync_schema(app): query = "{a,b}" - _, response = app.client.get( + _, response = app.test_client.get( uri=url_string(query=query), headers={"Accept": "text/html"} ) diff --git a/tests/sanic/test_graphqlview.py b/tests/sanic/test_graphqlview.py index 7152150..85aea1c 100644 --- a/tests/sanic/test_graphqlview.py +++ b/tests/sanic/test_graphqlview.py @@ -21,7 +21,7 @@ def json_dump_kwarg_list(**kwargs): @pytest.mark.parametrize("app", [create_app()]) def test_allows_get_with_query_param(app): - _, response = app.client.get(uri=url_string(query="{test}")) + _, response = app.test_client.get(uri=url_string(query="{test}")) assert response.status == 200 assert response_json(response) == {"data": {"test": "Hello World"}} @@ -29,7 +29,7 @@ def test_allows_get_with_query_param(app): @pytest.mark.parametrize("app", [create_app()]) def test_allows_get_with_variable_values(app): - _, response = app.client.get( + _, response = app.test_client.get( uri=url_string( query="query helloWho($who: String){ test(who: $who) }", variables=json.dumps({"who": "Dolly"}), @@ -42,7 +42,7 @@ def test_allows_get_with_variable_values(app): @pytest.mark.parametrize("app", [create_app()]) def test_allows_get_with_operation_name(app): - _, response = app.client.get( + _, response = app.test_client.get( uri=url_string( query=""" query helloYou { test(who: "You"), ...shared } @@ -64,7 +64,7 @@ def test_allows_get_with_operation_name(app): @pytest.mark.parametrize("app", [create_app()]) def test_reports_validation_errors(app): - _, response = app.client.get( + _, response = app.test_client.get( uri=url_string(query="{ test, unknownOne, unknownTwo }") ) @@ -85,7 +85,7 @@ def test_reports_validation_errors(app): @pytest.mark.parametrize("app", [create_app()]) def test_errors_when_missing_operation_name(app): - _, response = app.client.get( + _, response = app.test_client.get( uri=url_string( query=""" query TestQuery { test } @@ -107,7 +107,7 @@ def test_errors_when_missing_operation_name(app): @pytest.mark.parametrize("app", [create_app()]) def test_errors_when_sending_a_mutation_via_get(app): - _, response = app.client.get( + _, response = app.test_client.get( uri=url_string( query=""" mutation TestMutation { writeTest { test } } @@ -126,7 +126,7 @@ def test_errors_when_sending_a_mutation_via_get(app): @pytest.mark.parametrize("app", [create_app()]) def test_errors_when_selecting_a_mutation_within_a_get(app): - _, response = app.client.get( + _, response = app.test_client.get( uri=url_string( query=""" query TestQuery { test } @@ -148,7 +148,7 @@ def test_errors_when_selecting_a_mutation_within_a_get(app): @pytest.mark.parametrize("app", [create_app()]) def test_allows_mutation_to_exist_within_a_get(app): - _, response = app.client.get( + _, response = app.test_client.get( uri=url_string( query=""" query TestQuery { test } @@ -164,7 +164,7 @@ def test_allows_mutation_to_exist_within_a_get(app): @pytest.mark.parametrize("app", [create_app()]) def test_allows_post_with_json_encoding(app): - _, response = app.client.post( + _, response = app.test_client.post( uri=url_string(), data=json_dump_kwarg(query="{test}"), headers={"content-type": "application/json"}, @@ -176,7 +176,7 @@ def test_allows_post_with_json_encoding(app): @pytest.mark.parametrize("app", [create_app()]) def test_allows_sending_a_mutation_via_post(app): - _, response = app.client.post( + _, response = app.test_client.post( uri=url_string(), data=json_dump_kwarg(query="mutation TestMutation { writeTest { test } }"), headers={"content-type": "application/json"}, @@ -192,7 +192,7 @@ def test_allows_post_with_url_encoding(app): # can be found at their repo. # https://github.com/huge-success/sanic/blob/master/tests/test_requests.py#L927 payload = "query={test}" - _, response = app.client.post( + _, response = app.test_client.post( uri=url_string(), data=payload, headers={"content-type": "application/x-www-form-urlencoded"}, @@ -204,7 +204,7 @@ def test_allows_post_with_url_encoding(app): @pytest.mark.parametrize("app", [create_app()]) def test_supports_post_json_query_with_string_variables(app): - _, response = app.client.post( + _, response = app.test_client.post( uri=url_string(), data=json_dump_kwarg( query="query helloWho($who: String){ test(who: $who) }", @@ -219,7 +219,7 @@ def test_supports_post_json_query_with_string_variables(app): @pytest.mark.parametrize("app", [create_app()]) def test_supports_post_json_query_with_json_variables(app): - _, response = app.client.post( + _, response = app.test_client.post( uri=url_string(), data=json_dump_kwarg( query="query helloWho($who: String){ test(who: $who) }", @@ -234,7 +234,7 @@ def test_supports_post_json_query_with_json_variables(app): @pytest.mark.parametrize("app", [create_app()]) def test_supports_post_url_encoded_query_with_string_variables(app): - _, response = app.client.post( + _, response = app.test_client.post( uri=url_string(), data=urlencode( dict( @@ -251,7 +251,7 @@ def test_supports_post_url_encoded_query_with_string_variables(app): @pytest.mark.parametrize("app", [create_app()]) def test_supports_post_json_query_with_get_variable_values(app): - _, response = app.client.post( + _, response = app.test_client.post( uri=url_string(variables=json.dumps({"who": "Dolly"})), data=json_dump_kwarg( query="query helloWho($who: String){ test(who: $who) }", @@ -265,7 +265,7 @@ def test_supports_post_json_query_with_get_variable_values(app): @pytest.mark.parametrize("app", [create_app()]) def test_post_url_encoded_query_with_get_variable_values(app): - _, response = app.client.post( + _, response = app.test_client.post( uri=url_string(variables=json.dumps({"who": "Dolly"})), data=urlencode( dict( @@ -281,7 +281,7 @@ def test_post_url_encoded_query_with_get_variable_values(app): @pytest.mark.parametrize("app", [create_app()]) def test_supports_post_raw_text_query_with_get_variable_values(app): - _, response = app.client.post( + _, response = app.test_client.post( uri=url_string(variables=json.dumps({"who": "Dolly"})), data="query helloWho($who: String){ test(who: $who) }", headers={"content-type": "application/graphql"}, @@ -293,7 +293,7 @@ def test_supports_post_raw_text_query_with_get_variable_values(app): @pytest.mark.parametrize("app", [create_app()]) def test_allows_post_with_operation_name(app): - _, response = app.client.post( + _, response = app.test_client.post( uri=url_string(), data=json_dump_kwarg( query=""" @@ -317,7 +317,7 @@ def test_allows_post_with_operation_name(app): @pytest.mark.parametrize("app", [create_app()]) def test_allows_post_with_get_operation_name(app): - _, response = app.client.post( + _, response = app.test_client.post( uri=url_string(operationName="helloWorld"), data=""" query helloYou { test(who: "You"), ...shared } @@ -338,7 +338,7 @@ def test_allows_post_with_get_operation_name(app): @pytest.mark.parametrize("app", [create_app(pretty=True)]) def test_supports_pretty_printing(app): - _, response = app.client.get(uri=url_string(query="{test}")) + _, response = app.test_client.get(uri=url_string(query="{test}")) assert response.body.decode() == ( "{\n" ' "data": {\n' ' "test": "Hello World"\n' " }\n" "}" @@ -347,14 +347,14 @@ def test_supports_pretty_printing(app): @pytest.mark.parametrize("app", [create_app(pretty=False)]) def test_not_pretty_by_default(app): - _, response = app.client.get(url_string(query="{test}")) + _, response = app.test_client.get(url_string(query="{test}")) assert response.body.decode() == '{"data":{"test":"Hello World"}}' @pytest.mark.parametrize("app", [create_app()]) def test_supports_pretty_printing_by_request(app): - _, response = app.client.get(uri=url_string(query="{test}", pretty="1")) + _, response = app.test_client.get(uri=url_string(query="{test}", pretty="1")) assert response.body.decode() == ( "{\n" ' "data": {\n' ' "test": "Hello World"\n' " }\n" "}" @@ -363,7 +363,7 @@ def test_supports_pretty_printing_by_request(app): @pytest.mark.parametrize("app", [create_app()]) def test_handles_field_errors_caught_by_graphql(app): - _, response = app.client.get(uri=url_string(query="{thrower}")) + _, response = app.test_client.get(uri=url_string(query="{thrower}")) assert response.status == 200 assert response_json(response) == { "data": None, @@ -379,7 +379,7 @@ def test_handles_field_errors_caught_by_graphql(app): @pytest.mark.parametrize("app", [create_app()]) def test_handles_syntax_errors_caught_by_graphql(app): - _, response = app.client.get(uri=url_string(query="syntaxerror")) + _, response = app.test_client.get(uri=url_string(query="syntaxerror")) assert response.status == 400 assert response_json(response) == { "errors": [ @@ -393,7 +393,7 @@ def test_handles_syntax_errors_caught_by_graphql(app): @pytest.mark.parametrize("app", [create_app()]) def test_handles_errors_caused_by_a_lack_of_query(app): - _, response = app.client.get(uri=url_string()) + _, response = app.test_client.get(uri=url_string()) assert response.status == 400 assert response_json(response) == { @@ -403,7 +403,7 @@ def test_handles_errors_caused_by_a_lack_of_query(app): @pytest.mark.parametrize("app", [create_app()]) def test_handles_batch_correctly_if_is_disabled(app): - _, response = app.client.post( + _, response = app.test_client.post( uri=url_string(), data="[]", headers={"content-type": "application/json"} ) @@ -419,7 +419,7 @@ def test_handles_batch_correctly_if_is_disabled(app): @pytest.mark.parametrize("app", [create_app()]) def test_handles_incomplete_json_bodies(app): - _, response = app.client.post( + _, response = app.test_client.post( uri=url_string(), data='{"query":', headers={"content-type": "application/json"} ) @@ -431,7 +431,7 @@ def test_handles_incomplete_json_bodies(app): @pytest.mark.parametrize("app", [create_app()]) def test_handles_plain_post_text(app): - _, response = app.client.post( + _, response = app.test_client.post( uri=url_string(variables=json.dumps({"who": "Dolly"})), data="query helloWho($who: String){ test(who: $who) }", headers={"content-type": "text/plain"}, @@ -444,7 +444,7 @@ def test_handles_plain_post_text(app): @pytest.mark.parametrize("app", [create_app()]) def test_handles_poorly_formed_variables(app): - _, response = app.client.get( + _, response = app.test_client.get( uri=url_string( query="query helloWho($who: String){ test(who: $who) }", variables="who:You" ) @@ -457,9 +457,12 @@ def test_handles_poorly_formed_variables(app): @pytest.mark.parametrize("app", [create_app()]) def test_handles_unsupported_http_methods(app): - _, response = app.client.put(uri=url_string(query="{test}")) + _, response = app.test_client.put(uri=url_string(query="{test}")) assert response.status == 405 - assert response.headers["Allow"] in ["GET, POST", "HEAD, GET, POST, OPTIONS"] + allowed_methods = set( + method.strip() for method in response.headers["Allow"].split(",") + ) + assert allowed_methods in [{"GET", "POST"}, {"HEAD", "GET", "POST", "OPTIONS"}] assert response_json(response) == { "errors": [ { @@ -471,7 +474,7 @@ def test_handles_unsupported_http_methods(app): @pytest.mark.parametrize("app", [create_app()]) def test_passes_request_into_request_context(app): - _, response = app.client.get(uri=url_string(query="{request}", q="testing")) + _, response = app.test_client.get(uri=url_string(query="{request}", q="testing")) assert response.status == 200 assert response_json(response) == {"data": {"request": "testing"}} @@ -479,7 +482,9 @@ def test_passes_request_into_request_context(app): @pytest.mark.parametrize("app", [create_app(context={"session": "CUSTOM CONTEXT"})]) def test_passes_custom_context_into_context(app): - _, response = app.client.get(uri=url_string(query="{context { session request }}")) + _, response = app.test_client.get( + uri=url_string(query="{context { session request }}") + ) assert response.status_code == 200 res = response_json(response) @@ -492,7 +497,9 @@ def test_passes_custom_context_into_context(app): @pytest.mark.parametrize("app", [create_app(context="CUSTOM CONTEXT")]) def test_context_remapped_if_not_mapping(app): - _, response = app.client.get(uri=url_string(query="{context { session request }}")) + _, response = app.test_client.get( + uri=url_string(query="{context { session request }}") + ) assert response.status_code == 200 res = response_json(response) @@ -521,7 +528,7 @@ def test_post_multipart_data(app): + "------sanicgraphql--\r\n" ) - _, response = app.client.post( + _, response = app.test_client.post( uri=url_string(), data=data, headers={"content-type": "multipart/form-data; boundary=----sanicgraphql"}, @@ -533,7 +540,7 @@ def test_post_multipart_data(app): @pytest.mark.parametrize("app", [create_app(batch=True)]) def test_batch_allows_post_with_json_encoding(app): - _, response = app.client.post( + _, response = app.test_client.post( uri=url_string(), data=json_dump_kwarg_list(id=1, query="{test}"), headers={"content-type": "application/json"}, @@ -545,7 +552,7 @@ def test_batch_allows_post_with_json_encoding(app): @pytest.mark.parametrize("app", [create_app(batch=True)]) def test_batch_supports_post_json_query_with_json_variables(app): - _, response = app.client.post( + _, response = app.test_client.post( uri=url_string(), data=json_dump_kwarg_list( id=1, @@ -561,7 +568,7 @@ def test_batch_supports_post_json_query_with_json_variables(app): @pytest.mark.parametrize("app", [create_app(batch=True)]) def test_batch_allows_post_with_operation_name(app): - _, response = app.client.post( + _, response = app.test_client.post( uri=url_string(), data=json_dump_kwarg_list( id=1, @@ -587,7 +594,7 @@ def test_batch_allows_post_with_operation_name(app): @pytest.mark.parametrize("app", [create_app(schema=AsyncSchema, enable_async=True)]) def test_async_schema(app): query = "{a,b,c}" - _, response = app.client.get(uri=url_string(query=query)) + _, response = app.test_client.get(uri=url_string(query=query)) assert response.status == 200 assert response_json(response) == {"data": {"a": "hey", "b": "hey2", "c": "hey3"}} @@ -595,7 +602,7 @@ def test_async_schema(app): @pytest.mark.parametrize("app", [create_app()]) def test_preflight_request(app): - _, response = app.client.options( + _, response = app.test_client.options( uri=url_string(), headers={"Access-Control-Request-Method": "POST"} ) @@ -604,7 +611,7 @@ def test_preflight_request(app): @pytest.mark.parametrize("app", [create_app()]) def test_preflight_incorrect_request(app): - _, response = app.client.options( + _, response = app.test_client.options( uri=url_string(), headers={"Access-Control-Request-Method": "OPTIONS"} ) From 271b4470d7db66df13ae6747b887f549baa56bbd Mon Sep 17 00:00:00 2001 From: Kien Dang Date: Tue, 20 Dec 2022 17:51:59 +0800 Subject: [PATCH 07/15] Make sanic v22.9 work --- setup.py | 4 ++-- tests/sanic/app.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 654ce11..f7c5fb4 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ "pytest>=7.2,<8", "pytest-asyncio>=0.20,<1", "pytest-cov>=4,<5", - "sanic-testing>=22.3,<22.7", + "sanic-testing>=22.3,<23", ] dev_requires = [ @@ -27,7 +27,7 @@ ] install_sanic_requires = [ - "sanic>=21.12,<22.7", + "sanic>=21.12,<23", ] install_webob_requires = [ diff --git a/tests/sanic/app.py b/tests/sanic/app.py index 342c1fe..f70dd8a 100644 --- a/tests/sanic/app.py +++ b/tests/sanic/app.py @@ -1,3 +1,4 @@ +import uuid from urllib.parse import urlencode from sanic import Sanic @@ -6,11 +7,10 @@ from .schema import Schema -Sanic.test_mode = True - def create_app(path="/graphql", **kwargs): - app = Sanic("TestApp") + random_valid_app_name = f"App{uuid.uuid4().hex}" + app = Sanic(random_valid_app_name) schema = kwargs.pop("schema", None) or Schema app.add_route(GraphQLView.as_view(schema=schema, **kwargs), path) From aac23bc685999e6e209977c4c417990e9db74109 Mon Sep 17 00:00:00 2001 From: Kien Dang Date: Tue, 20 Dec 2022 18:08:13 +0800 Subject: [PATCH 08/15] Fix deprecation warnings DeprecationWarning: Use 'content=<...>' to upload raw bytes/text content. --- tests/sanic/test_graphqlview.py | 38 +++++++++++++++++---------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/tests/sanic/test_graphqlview.py b/tests/sanic/test_graphqlview.py index 85aea1c..d6ef556 100644 --- a/tests/sanic/test_graphqlview.py +++ b/tests/sanic/test_graphqlview.py @@ -166,7 +166,7 @@ def test_allows_mutation_to_exist_within_a_get(app): def test_allows_post_with_json_encoding(app): _, response = app.test_client.post( uri=url_string(), - data=json_dump_kwarg(query="{test}"), + content=json_dump_kwarg(query="{test}"), headers={"content-type": "application/json"}, ) @@ -178,7 +178,7 @@ def test_allows_post_with_json_encoding(app): def test_allows_sending_a_mutation_via_post(app): _, response = app.test_client.post( uri=url_string(), - data=json_dump_kwarg(query="mutation TestMutation { writeTest { test } }"), + content=json_dump_kwarg(query="mutation TestMutation { writeTest { test } }"), headers={"content-type": "application/json"}, ) @@ -194,7 +194,7 @@ def test_allows_post_with_url_encoding(app): payload = "query={test}" _, response = app.test_client.post( uri=url_string(), - data=payload, + content=payload, headers={"content-type": "application/x-www-form-urlencoded"}, ) @@ -206,7 +206,7 @@ def test_allows_post_with_url_encoding(app): def test_supports_post_json_query_with_string_variables(app): _, response = app.test_client.post( uri=url_string(), - data=json_dump_kwarg( + content=json_dump_kwarg( query="query helloWho($who: String){ test(who: $who) }", variables=json.dumps({"who": "Dolly"}), ), @@ -221,7 +221,7 @@ def test_supports_post_json_query_with_string_variables(app): def test_supports_post_json_query_with_json_variables(app): _, response = app.test_client.post( uri=url_string(), - data=json_dump_kwarg( + content=json_dump_kwarg( query="query helloWho($who: String){ test(who: $who) }", variables={"who": "Dolly"}, ), @@ -236,7 +236,7 @@ def test_supports_post_json_query_with_json_variables(app): def test_supports_post_url_encoded_query_with_string_variables(app): _, response = app.test_client.post( uri=url_string(), - data=urlencode( + content=urlencode( dict( query="query helloWho($who: String){ test(who: $who) }", variables=json.dumps({"who": "Dolly"}), @@ -253,7 +253,7 @@ def test_supports_post_url_encoded_query_with_string_variables(app): def test_supports_post_json_query_with_get_variable_values(app): _, response = app.test_client.post( uri=url_string(variables=json.dumps({"who": "Dolly"})), - data=json_dump_kwarg( + content=json_dump_kwarg( query="query helloWho($who: String){ test(who: $who) }", ), headers={"content-type": "application/json"}, @@ -267,7 +267,7 @@ def test_supports_post_json_query_with_get_variable_values(app): def test_post_url_encoded_query_with_get_variable_values(app): _, response = app.test_client.post( uri=url_string(variables=json.dumps({"who": "Dolly"})), - data=urlencode( + content=urlencode( dict( query="query helloWho($who: String){ test(who: $who) }", ) @@ -283,7 +283,7 @@ def test_post_url_encoded_query_with_get_variable_values(app): def test_supports_post_raw_text_query_with_get_variable_values(app): _, response = app.test_client.post( uri=url_string(variables=json.dumps({"who": "Dolly"})), - data="query helloWho($who: String){ test(who: $who) }", + content="query helloWho($who: String){ test(who: $who) }", headers={"content-type": "application/graphql"}, ) @@ -295,7 +295,7 @@ def test_supports_post_raw_text_query_with_get_variable_values(app): def test_allows_post_with_operation_name(app): _, response = app.test_client.post( uri=url_string(), - data=json_dump_kwarg( + content=json_dump_kwarg( query=""" query helloYou { test(who: "You"), ...shared } query helloWorld { test(who: "World"), ...shared } @@ -319,7 +319,7 @@ def test_allows_post_with_operation_name(app): def test_allows_post_with_get_operation_name(app): _, response = app.test_client.post( uri=url_string(operationName="helloWorld"), - data=""" + content=""" query helloYou { test(who: "You"), ...shared } query helloWorld { test(who: "World"), ...shared } query helloDolly { test(who: "Dolly"), ...shared } @@ -404,7 +404,7 @@ def test_handles_errors_caused_by_a_lack_of_query(app): @pytest.mark.parametrize("app", [create_app()]) def test_handles_batch_correctly_if_is_disabled(app): _, response = app.test_client.post( - uri=url_string(), data="[]", headers={"content-type": "application/json"} + uri=url_string(), content="[]", headers={"content-type": "application/json"} ) assert response.status == 400 @@ -420,7 +420,9 @@ def test_handles_batch_correctly_if_is_disabled(app): @pytest.mark.parametrize("app", [create_app()]) def test_handles_incomplete_json_bodies(app): _, response = app.test_client.post( - uri=url_string(), data='{"query":', headers={"content-type": "application/json"} + uri=url_string(), + content='{"query":', + headers={"content-type": "application/json"}, ) assert response.status == 400 @@ -433,7 +435,7 @@ def test_handles_incomplete_json_bodies(app): def test_handles_plain_post_text(app): _, response = app.test_client.post( uri=url_string(variables=json.dumps({"who": "Dolly"})), - data="query helloWho($who: String){ test(who: $who) }", + content="query helloWho($who: String){ test(who: $who) }", headers={"content-type": "text/plain"}, ) assert response.status == 400 @@ -530,7 +532,7 @@ def test_post_multipart_data(app): _, response = app.test_client.post( uri=url_string(), - data=data, + content=data, headers={"content-type": "multipart/form-data; boundary=----sanicgraphql"}, ) @@ -542,7 +544,7 @@ def test_post_multipart_data(app): def test_batch_allows_post_with_json_encoding(app): _, response = app.test_client.post( uri=url_string(), - data=json_dump_kwarg_list(id=1, query="{test}"), + content=json_dump_kwarg_list(id=1, query="{test}"), headers={"content-type": "application/json"}, ) @@ -554,7 +556,7 @@ def test_batch_allows_post_with_json_encoding(app): def test_batch_supports_post_json_query_with_json_variables(app): _, response = app.test_client.post( uri=url_string(), - data=json_dump_kwarg_list( + content=json_dump_kwarg_list( id=1, query="query helloWho($who: String){ test(who: $who) }", variables={"who": "Dolly"}, @@ -570,7 +572,7 @@ def test_batch_supports_post_json_query_with_json_variables(app): def test_batch_allows_post_with_operation_name(app): _, response = app.test_client.post( uri=url_string(), - data=json_dump_kwarg_list( + content=json_dump_kwarg_list( id=1, query=""" query helloYou { test(who: "You"), ...shared } From 6623379220255a2ac4ed3634050897998138d1cb Mon Sep 17 00:00:00 2001 From: Kien Dang Date: Wed, 21 Dec 2022 23:40:31 +0800 Subject: [PATCH 09/15] Update graphiql to 1.4.7 for security reason "All versions of graphiql < 1.4.7 are vulnerable to an XSS attack." https://github.com/graphql/graphiql/blob/ab2b52f06213bd9bf90c905c1b460b6939f3d856/docs/security/2021-introspection-schema-xss.md --- graphql_server/render_graphiql.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/graphql_server/render_graphiql.py b/graphql_server/render_graphiql.py index c942300..498f53b 100644 --- a/graphql_server/render_graphiql.py +++ b/graphql_server/render_graphiql.py @@ -1,4 +1,4 @@ -"""Based on (express-graphql)[https://github.com/graphql/express-graphql/blob/master/src/renderGraphiQL.js] and +"""Based on (express-graphql)[https://github.com/graphql/express-graphql/blob/main/src/renderGraphiQL.ts] and (subscriptions-transport-ws)[https://github.com/apollographql/subscriptions-transport-ws]""" import json import re @@ -7,7 +7,7 @@ from jinja2 import Environment from typing_extensions import TypedDict -GRAPHIQL_VERSION = "1.0.3" +GRAPHIQL_VERSION = "1.4.7" GRAPHIQL_TEMPLATE = """