diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index 4174171a9a..d2d431aefd 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -1,16 +1,10 @@ -import json import sys import weakref -try: - from urllib.parse import parse_qsl -except ImportError: - from urlparse import parse_qsl # type: ignore - from sentry_sdk.api import continue_trace from sentry_sdk._compat import reraise from sentry_sdk.consts import OP, SPANDATA -from sentry_sdk.hub import Hub, _should_send_default_pii +from sentry_sdk.hub import Hub from sentry_sdk.integrations import Integration, DidNotEnable from sentry_sdk.integrations.logging import ignore_logger from sentry_sdk.sessions import auto_session_tracking @@ -35,17 +29,14 @@ CONTEXTVARS_ERROR_MESSAGE, SENSITIVE_DATA_SUBSTITUTE, AnnotatedValue, - SentryGraphQLClientError, - _get_graphql_operation_name, - _get_graphql_operation_type, ) try: import asyncio from aiohttp import __version__ as AIOHTTP_VERSION - from aiohttp import ClientSession, ContentTypeError, TraceConfig - from aiohttp.web import Application, HTTPException, UrlDispatcher, Response + from aiohttp import ClientSession, TraceConfig + from aiohttp.web import Application, HTTPException, UrlDispatcher except ImportError: raise DidNotEnable("AIOHTTP not installed") @@ -54,11 +45,7 @@ if TYPE_CHECKING: from aiohttp.web_request import Request from aiohttp.abc import AbstractMatchInfo - from aiohttp import ( - TraceRequestStartParams, - TraceRequestEndParams, - TraceRequestChunkSentParams, - ) + from aiohttp import TraceRequestStartParams, TraceRequestEndParams from types import SimpleNamespace from typing import Any from typing import Dict @@ -77,8 +64,8 @@ class AioHttpIntegration(Integration): identifier = "aiohttp" - def __init__(self, transaction_style="handler_name", capture_graphql_errors=True): - # type: (str, bool) -> None + def __init__(self, transaction_style="handler_name"): + # type: (str) -> None if transaction_style not in TRANSACTION_STYLE_VALUES: raise ValueError( "Invalid value for transaction_style: %s (must be in %s)" @@ -86,8 +73,6 @@ def __init__(self, transaction_style="handler_name", capture_graphql_errors=True ) self.transaction_style = transaction_style - self.capture_graphql_errors = capture_graphql_errors - @staticmethod def setup_once(): # type: () -> None @@ -126,7 +111,7 @@ async def sentry_app_handle(self, request, *args, **kwargs): # create a task to wrap each request. with hub.configure_scope() as scope: scope.clear_breadcrumbs() - scope.add_event_processor(_make_server_processor(weak_request)) + scope.add_event_processor(_make_request_processor(weak_request)) transaction = continue_trace( request.headers, @@ -154,7 +139,6 @@ async def sentry_app_handle(self, request, *args, **kwargs): reraise(*_capture_exception(hub)) transaction.set_http_status(response.status) - return response Application._handle = sentry_app_handle @@ -214,8 +198,7 @@ def create_trace_config(): async def on_request_start(session, trace_config_ctx, params): # type: (ClientSession, SimpleNamespace, TraceRequestStartParams) -> None hub = Hub.current - integration = hub.get_integration(AioHttpIntegration) - if integration is None: + if hub.get_integration(AioHttpIntegration) is None: return method = params.method.upper() @@ -250,95 +233,28 @@ async def on_request_start(session, trace_config_ctx, params): params.headers[key] = value trace_config_ctx.span = span - trace_config_ctx.is_graphql_request = params.url.path == "/graphql" - - if integration.capture_graphql_errors and trace_config_ctx.is_graphql_request: - trace_config_ctx.request_headers = params.headers - - async def on_request_chunk_sent(session, trace_config_ctx, params): - # type: (ClientSession, SimpleNamespace, TraceRequestChunkSentParams) -> None - integration = Hub.current.get_integration(AioHttpIntegration) - if integration is None: - return - - if integration.capture_graphql_errors and trace_config_ctx.is_graphql_request: - trace_config_ctx.request_body = None - with capture_internal_exceptions(): - try: - trace_config_ctx.request_body = json.loads(params.chunk) - except json.JSONDecodeError: - return async def on_request_end(session, trace_config_ctx, params): # type: (ClientSession, SimpleNamespace, TraceRequestEndParams) -> None - hub = Hub.current - integration = hub.get_integration(AioHttpIntegration) - if integration is None: + if trace_config_ctx.span is None: return - response = params.response - - if trace_config_ctx.span is not None: - span = trace_config_ctx.span - span.set_http_status(int(response.status)) - span.set_data("reason", response.reason) - - if ( - integration.capture_graphql_errors - and trace_config_ctx.is_graphql_request - and response.method in ("GET", "POST") - and response.status == 200 - ): - with hub.configure_scope() as scope: - with capture_internal_exceptions(): - try: - response_content = await response.json() - except ContentTypeError: - pass - else: - scope.add_event_processor( - _make_client_processor( - trace_config_ctx=trace_config_ctx, - response=response, - response_content=response_content, - ) - ) - - if ( - response_content - and isinstance(response_content, dict) - and response_content.get("errors") - ): - try: - raise SentryGraphQLClientError - except SentryGraphQLClientError as ex: - event, hint = event_from_exception( - ex, - client_options=hub.client.options - if hub.client - else None, - mechanism={ - "type": AioHttpIntegration.identifier, - "handled": False, - }, - ) - hub.capture_event(event, hint=hint) - - if trace_config_ctx.span is not None: - span.finish() + span = trace_config_ctx.span + span.set_http_status(int(params.response.status)) + span.set_data("reason", params.response.reason) + span.finish() trace_config = TraceConfig() trace_config.on_request_start.append(on_request_start) - trace_config.on_request_chunk_sent.append(on_request_chunk_sent) trace_config.on_request_end.append(on_request_end) return trace_config -def _make_server_processor(weak_request): +def _make_request_processor(weak_request): # type: (Callable[[], Request]) -> EventProcessor - def aiohttp_server_processor( + def aiohttp_processor( event, # type: Dict[str, Any] hint, # type: Dict[str, Tuple[type, BaseException, Any]] ): @@ -370,63 +286,7 @@ def aiohttp_server_processor( return event - return aiohttp_server_processor - - -def _make_client_processor(trace_config_ctx, response, response_content): - # type: (SimpleNamespace, Response, Optional[Dict[str, Any]]) -> EventProcessor - def aiohttp_client_processor( - event, # type: Dict[str, Any] - hint, # type: Dict[str, Tuple[type, BaseException, Any]] - ): - # type: (...) -> Dict[str, Any] - with capture_internal_exceptions(): - request_info = event.setdefault("request", {}) - - parsed_url = parse_url(str(response.url), sanitize=False) - request_info["url"] = parsed_url.url - request_info["method"] = response.method - - if getattr(trace_config_ctx, "request_headers", None): - request_info["headers"] = _filter_headers( - dict(trace_config_ctx.request_headers) - ) - - if _should_send_default_pii(): - if getattr(trace_config_ctx, "request_body", None): - request_info["data"] = trace_config_ctx.request_body - - request_info["query_string"] = parsed_url.query - - if response.url.path == "/graphql": - request_info["api_target"] = "graphql" - - query = request_info.get("data") - if response.method == "GET": - query = dict(parse_qsl(parsed_url.query)) - - if query: - operation_name = _get_graphql_operation_name(query) - operation_type = _get_graphql_operation_type(query) - event["fingerprint"] = [ - operation_name, - operation_type, - response.status, - ] - event["exception"]["values"][0][ - "value" - ] = "GraphQL request failed, name: {}, type: {}".format( - operation_name, operation_type - ) - - if _should_send_default_pii() and response_content: - contexts = event.setdefault("contexts", {}) - response_context = contexts.setdefault("response", {}) - response_context["data"] = response_content - - return event - - return aiohttp_client_processor + return aiohttp_processor def _capture_exception(hub): diff --git a/sentry_sdk/integrations/httpx.py b/sentry_sdk/integrations/httpx.py index 0834d46d5f..04db5047b4 100644 --- a/sentry_sdk/integrations/httpx.py +++ b/sentry_sdk/integrations/httpx.py @@ -1,40 +1,19 @@ -import json - -try: - # py3 - from urllib.parse import parse_qsl -except ImportError: - # py2 - from urlparse import parse_qsl # type: ignore - -try: - # py3 - from json import JSONDecodeError -except ImportError: - # py2 doesn't throw a specialized json error, just Value/TypeErrors - JSONDecodeError = ValueError # type: ignore - +from sentry_sdk import Hub from sentry_sdk.consts import OP, SPANDATA -from sentry_sdk.hub import Hub, _should_send_default_pii from sentry_sdk.integrations import Integration, DidNotEnable from sentry_sdk.tracing import BAGGAGE_HEADER_NAME from sentry_sdk.tracing_utils import should_propagate_trace from sentry_sdk.utils import ( SENSITIVE_DATA_SUBSTITUTE, - SentryGraphQLClientError, capture_internal_exceptions, - event_from_exception, logger, parse_url, - _get_graphql_operation_name, - _get_graphql_operation_type, ) + from sentry_sdk._types import TYPE_CHECKING -from sentry_sdk.integrations._wsgi_common import _filter_headers if TYPE_CHECKING: - from typing import Any, Dict, Tuple - from sentry_sdk._types import EventProcessor + from typing import Any try: @@ -48,10 +27,6 @@ class HttpxIntegration(Integration): identifier = "httpx" - def __init__(self, capture_graphql_errors=True): - # type: (bool) -> None - self.capture_graphql_errors = capture_graphql_errors - @staticmethod def setup_once(): # type: () -> None @@ -70,8 +45,7 @@ def _install_httpx_client(): def send(self, request, **kwargs): # type: (Client, Request, **Any) -> Response hub = Hub.current - integration = hub.get_integration(HttpxIntegration) - if integration is None: + if hub.get_integration(HttpxIntegration) is None: return real_send(self, request, **kwargs) parsed_url = None @@ -112,9 +86,6 @@ def send(self, request, **kwargs): span.set_http_status(rv.status_code) span.set_data("reason", rv.reason_phrase) - if integration.capture_graphql_errors: - _capture_graphql_errors(hub, request, rv) - return rv Client.send = send @@ -127,8 +98,7 @@ def _install_httpx_async_client(): async def send(self, request, **kwargs): # type: (AsyncClient, Request, **Any) -> Response hub = Hub.current - integration = hub.get_integration(HttpxIntegration) - if integration is None: + if hub.get_integration(HttpxIntegration) is None: return await real_send(self, request, **kwargs) parsed_url = None @@ -169,95 +139,6 @@ async def send(self, request, **kwargs): span.set_http_status(rv.status_code) span.set_data("reason", rv.reason_phrase) - if integration.capture_graphql_errors: - _capture_graphql_errors(hub, request, rv) - return rv AsyncClient.send = send - - -def _make_request_processor(request, response): - # type: (Request, Response) -> EventProcessor - def httpx_processor( - event, # type: Dict[str, Any] - hint, # type: Dict[str, Tuple[type, BaseException, Any]] - ): - # type: (...) -> Dict[str, Any] - with capture_internal_exceptions(): - request_info = event.setdefault("request", {}) - - parsed_url = parse_url(str(request.url), sanitize=False) - request_info["url"] = parsed_url.url - request_info["method"] = request.method - request_info["headers"] = _filter_headers(dict(request.headers)) - - if _should_send_default_pii(): - request_info["query_string"] = parsed_url.query - - request_content = request.read() - if request_content: - try: - request_info["data"] = json.loads(request_content) - except (JSONDecodeError, TypeError): - pass - - if response: - response_content = response.json() - contexts = event.setdefault("contexts", {}) - response_context = contexts.setdefault("response", {}) - response_context["data"] = response_content - - if request.url.path == "/graphql": - request_info["api_target"] = "graphql" - - query = request_info.get("data") - if request.method == "GET": - query = dict(parse_qsl(parsed_url.query)) - - if query: - operation_name = _get_graphql_operation_name(query) - operation_type = _get_graphql_operation_type(query) - event["fingerprint"] = [operation_name, operation_type, 200] - event["exception"]["values"][0][ - "value" - ] = "GraphQL request failed, name: {}, type: {}".format( - operation_name, operation_type - ) - - return event - - return httpx_processor - - -def _capture_graphql_errors(hub, request, response): - # type: (Hub, Request, Response) -> None - if ( - request.url.path == "/graphql" - and request.method in ("GET", "POST") - and response.status_code == 200 - ): - with hub.configure_scope() as scope: - scope.add_event_processor(_make_request_processor(request, response)) - - with capture_internal_exceptions(): - try: - response_content = response.json() - except JSONDecodeError: - return - - if isinstance(response_content, dict) and response_content.get( - "errors" - ): - try: - raise SentryGraphQLClientError - except SentryGraphQLClientError as ex: - event, hint = event_from_exception( - ex, - client_options=hub.client.options if hub.client else None, - mechanism={ - "type": HttpxIntegration.identifier, - "handled": False, - }, - ) - hub.capture_event(event, hint=hint) diff --git a/sentry_sdk/integrations/stdlib.py b/sentry_sdk/integrations/stdlib.py index f8ed16d9b8..be02779d88 100644 --- a/sentry_sdk/integrations/stdlib.py +++ b/sentry_sdk/integrations/stdlib.py @@ -1,51 +1,31 @@ -import io -import json import os import subprocess import sys import platform - -try: - # py3 - from urllib.parse import parse_qsl -except ImportError: - # py2 - from urlparse import parse_qsl # type: ignore - -try: - # py3 - from json import JSONDecodeError -except ImportError: - # py2 doesn't throw a specialized json error, just Value/TypeErrors - JSONDecodeError = ValueError # type: ignore - from sentry_sdk.consts import OP, SPANDATA -from sentry_sdk.hub import Hub, _should_send_default_pii + +from sentry_sdk.hub import Hub from sentry_sdk.integrations import Integration from sentry_sdk.scope import add_global_event_processor from sentry_sdk.tracing_utils import EnvironHeaders, should_propagate_trace from sentry_sdk.utils import ( SENSITIVE_DATA_SUBSTITUTE, - SentryGraphQLClientError, capture_internal_exceptions, - event_from_exception, logger, safe_repr, parse_url, - _get_graphql_operation_name, - _get_graphql_operation_type, ) + from sentry_sdk._types import TYPE_CHECKING if TYPE_CHECKING: from typing import Any from typing import Callable from typing import Dict - from typing import List from typing import Optional - from typing import Tuple + from typing import List - from sentry_sdk._types import Event, EventProcessor, Hint + from sentry_sdk._types import Event, Hint try: @@ -64,10 +44,6 @@ class StdlibIntegration(Integration): identifier = "stdlib" - def __init__(self, capture_graphql_errors=True): - # type: (bool) -> None - self.capture_graphql_errors = capture_graphql_errors - @staticmethod def setup_once(): # type: () -> None @@ -88,7 +64,6 @@ def add_python_runtime_context(event, hint): def _install_httplib(): # type: () -> None real_putrequest = HTTPConnection.putrequest - real_endheaders = HTTPConnection.endheaders real_getresponse = HTTPConnection.getresponse def putrequest(self, method, url, *args, **kwargs): @@ -109,12 +84,10 @@ def putrequest(self, method, url, *args, **kwargs): port != default_port and ":%s" % port or "", url, ) - self._sentrysdk_url = real_url parsed_url = None with capture_internal_exceptions(): parsed_url = parse_url(real_url, sanitize=False) - self._sentrysdk_is_graphql_request = parsed_url.url.endswith("/graphql") span = hub.start_span( op=OP.HTTP_CLIENT, @@ -140,144 +113,28 @@ def putrequest(self, method, url, *args, **kwargs): self.putheader(key, value) self._sentrysdk_span = span - self._sentrysdk_method = method - - return rv - - def endheaders(self, message_body=None, **kwargs): - # type: (HTTPConnection, Any, **Any) -> Any - rv = real_endheaders(self, message_body, **kwargs) - - integration = Hub.current.get_integration(StdlibIntegration) - if integration is None: - return rv - - if integration.capture_graphql_errors and getattr( - self, "_sentrysdk_is_graphql_request", False - ): - self._sentry_request_body = message_body return rv def getresponse(self, *args, **kwargs): # type: (HTTPConnection, *Any, **Any) -> Any - rv = real_getresponse(self, *args, **kwargs) - - hub = Hub.current - integration = hub.get_integration(StdlibIntegration) - if integration is None: - return rv - span = getattr(self, "_sentrysdk_span", None) - if span is not None: - span.set_http_status(int(rv.status)) - span.set_data("reason", rv.reason) - span.finish() - url = getattr(self, "_sentrysdk_url", None) # type: Optional[str] - if url is None: - return rv + if span is None: + return real_getresponse(self, *args, **kwargs) - if integration.capture_graphql_errors: - response_body = None - if getattr(self, "_sentrysdk_is_graphql_request", False): - with capture_internal_exceptions(): - response_data = rv.read() - # once we've read() the body it can't be read() again by the - # app; save it so that it can be accessed again - saved_response = io.BytesIO(response_data) - rv.read = saved_response.read - rv.fp = saved_response - try: - # py3.6+ json.loads() can deal with bytes out of the box, but - # for older version we have to explicitly decode first - response_body = json.loads(response_data.decode()) - except (JSONDecodeError, UnicodeDecodeError, TypeError): - return rv - - is_graphql_response_with_errors = isinstance( - response_body, dict - ) and response_body.get("errors") - if is_graphql_response_with_errors: - method = getattr(self, "_sentrysdk_method", None) # type: Optional[str] - request_body = getattr(self, "_sentry_request_body", None) - with hub.configure_scope() as scope: - scope.add_event_processor( - _make_request_processor( - url, method, rv.status, request_body, response_body - ) - ) - try: - raise SentryGraphQLClientError - except SentryGraphQLClientError as ex: - event, hint = event_from_exception( - ex, - client_options=hub.client.options if hub.client else None, - mechanism={ - "type": StdlibIntegration.identifier, - "handled": False, - }, - ) - - hub.capture_event(event, hint=hint) + rv = real_getresponse(self, *args, **kwargs) + + span.set_http_status(int(rv.status)) + span.set_data("reason", rv.reason) + span.finish() return rv HTTPConnection.putrequest = putrequest - HTTPConnection.endheaders = endheaders HTTPConnection.getresponse = getresponse -def _make_request_processor(url, method, status, request_body, response_body): - # type: (str, Optional[str], int, Any, Any) -> EventProcessor - def stdlib_processor( - event, # type: Dict[str, Any] - hint, # type: Dict[str, Tuple[type, BaseException, Any]] - ): - # type: (...) -> Optional[Event] - with capture_internal_exceptions(): - request_info = event.setdefault("request", {}) - - parsed_url = parse_url(url, sanitize=False) - - if _should_send_default_pii(): - request_info["query_string"] = parsed_url.query - - request_info["url"] = parsed_url.url - request_info["method"] = method - - if _should_send_default_pii(): - try: - request_info["data"] = json.loads(request_body.decode()) - except (JSONDecodeError, AttributeError): - pass - - if response_body: - contexts = event.setdefault("contexts", {}) - response_context = contexts.setdefault("response", {}) - response_context["data"] = response_body - - if parsed_url.url.endswith("/graphql"): - request_info["api_target"] = "graphql" - query = request_info.get("data") - if method == "GET": - query = dict(parse_qsl(parsed_url.query)) - - if query: - operation_name = _get_graphql_operation_name(query) - operation_type = _get_graphql_operation_type(query) - event["fingerprint"] = [operation_name, operation_type, status] - event["exception"]["values"][0][ - "value" - ] = "GraphQL request failed, name: {}, type: {}".format( - operation_name, operation_type - ) - - return event - - return stdlib_processor - - def _init_argument(args, kwargs, name, position, setdefault_callback=None): # type: (List[Any], Dict[Any, Any], str, int, Optional[Callable[[Any], Any]]) -> Any """ diff --git a/sentry_sdk/scrubber.py b/sentry_sdk/scrubber.py index 8c828fe444..838ef08b4b 100644 --- a/sentry_sdk/scrubber.py +++ b/sentry_sdk/scrubber.py @@ -84,16 +84,6 @@ def scrub_request(self, event): if "data" in event["request"]: self.scrub_dict(event["request"]["data"]) - def scrub_response(self, event): - # type: (Event) -> None - with capture_internal_exceptions(): - if ( - "contexts" in event - and "response" in event["contexts"] - and "data" in event["contexts"]["response"] - ): - self.scrub_dict(event["contexts"]["response"]["data"]) - def scrub_extra(self, event): # type: (Event) -> None with capture_internal_exceptions(): @@ -133,7 +123,6 @@ def scrub_spans(self, event): def scrub_event(self, event): # type: (Event) -> None self.scrub_request(event) - self.scrub_response(event) self.scrub_extra(event) self.scrub_user(event) self.scrub_breadcrumbs(event) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 80076f9a61..475652c7bd 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -1287,39 +1287,6 @@ class ServerlessTimeoutWarning(Exception): # noqa: N818 pass -class SentryGraphQLClientError(Exception): - """Synthetic exception for GraphQL client errors.""" - - pass - - -def _get_graphql_operation_name(query): - # type: (Dict[str, Any]) -> str - if query.get("operationName"): - return query["operationName"] - - query = query["query"].strip() - - match = re.match( - r"((query|mutation|subscription) )(?P[a-zA-Z0-9]+).*\{", - query, - flags=re.IGNORECASE, - ) - if match: - return match.group("name") - return "anonymous" - - -def _get_graphql_operation_type(query): - # type: (Dict[str, Any]) -> str - query = query["query"].strip().lower() - if query.startswith("mutation"): - return "mutation" - if query.startswith("subscription"): - return "subscription" - return "query" - - class TimeoutThread(threading.Thread): """Creates a Thread which runs (sleeps) for a time duration equal to waiting_time and raises a custom ServerlessTimeout exception. diff --git a/tests/conftest.py b/tests/conftest.py index cb61bbbdbf..d9d88067dc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -584,12 +584,6 @@ def do_GET(self): # noqa: N802 self.end_headers() return - def do_POST(self): # noqa: N802 - # Process an HTTP POST request and return a response with an HTTP 200 status. - self.send_response(200) - self.end_headers() - return - def get_free_port(): s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM) diff --git a/tests/integrations/aiohttp/test_aiohttp.py b/tests/integrations/aiohttp/test_aiohttp.py index 79ed402554..8068365334 100644 --- a/tests/integrations/aiohttp/test_aiohttp.py +++ b/tests/integrations/aiohttp/test_aiohttp.py @@ -1,46 +1,20 @@ import asyncio import json from contextlib import suppress -from textwrap import dedent import pytest from aiohttp import web from aiohttp.client import ServerDisconnectedError -from aiohttp.web import Request, Response, json_response +from aiohttp.web_request import Request from sentry_sdk import capture_message, start_transaction from sentry_sdk.integrations.aiohttp import AioHttpIntegration -from sentry_sdk.utils import parse_version try: from unittest import mock # python 3.3 and above except ImportError: import mock # python < 3.3 -try: - from importlib.metadata import version # py 3.8+ - - AIOHTTP_VERSION = tuple(parse_version(version("aiohttp"))[:2]) - -except ImportError: - from pkg_resources import get_distribution - - AIOHTTP_VERSION = tuple(parse_version(get_distribution("aiohttp").version)[:2]) - - -def min_aiohttp_version(major, minor, reason=None): - if reason is None: - reason = "Requires aiohttp {}.{} or higher".format(major, minor) - - return pytest.mark.skipif(AIOHTTP_VERSION < (major, minor), reason=reason) - - -def max_aiohttp_version(major, minor, reason=None): - if reason is None: - reason = "Requires aiohttp {}.{} or lower".format(major, minor) - - return pytest.mark.skipif(AIOHTTP_VERSION > (major, minor), reason=reason) - @pytest.mark.asyncio async def test_basic(sentry_init, aiohttp_client, capture_events): @@ -560,306 +534,3 @@ async def handler(request): resp.request_info.headers["baggage"] == "custom=value,sentry-trace_id=0123456789012345678901234567890,sentry-environment=production,sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42,sentry-transaction=/interactions/other-dogs/new-dog,sentry-sample_rate=1.0,sentry-sampled=true" ) - - -@pytest.mark.asyncio -async def test_graphql_get_client_error_captured( - sentry_init, capture_events, aiohttp_raw_server, aiohttp_client -): - sentry_init(send_default_pii=True, integrations=[AioHttpIntegration()]) - - graphql_response = { - "data": None, - "errors": [ - { - "message": "some error", - "locations": [{"line": 2, "column": 3}], - "path": ["pet"], - } - ], - } - - async def handler(request): - return json_response(graphql_response) - - raw_server = await aiohttp_raw_server(handler) - events = capture_events() - - client = await aiohttp_client(raw_server) - response = await client.get( - "/graphql", params={"query": "query GetPet {pet{name}}"} - ) - - assert response.status == 200 - assert await response.json() == graphql_response - - (event,) = events - - assert event["request"]["url"] == "http://127.0.0.1:{}/graphql".format( - raw_server.port - ) - assert event["request"]["method"] == "GET" - assert event["request"]["query_string"] == "query=query+GetPet+%7Bpet%7Bname%7D%7D" - assert "data" not in event["request"] - assert event["contexts"]["response"]["data"] == graphql_response - - assert event["request"]["api_target"] == "graphql" - assert event["fingerprint"] == ["GetPet", "query", 200] - assert ( - event["exception"]["values"][0]["value"] - == "GraphQL request failed, name: GetPet, type: query" - ) - - -@pytest.mark.asyncio -async def test_graphql_post_client_error_captured( - sentry_init, capture_events, aiohttp_client, aiohttp_raw_server -): - sentry_init(send_default_pii=True, integrations=[AioHttpIntegration()]) - - graphql_request = { - "query": dedent( - """ - mutation AddPet ($name: String!) { - addPet(name: $name) { - id - name - } - } - """ - ), - "variables": { - "name": "Lucy", - }, - } - graphql_response = { - "data": None, - "errors": [ - { - "message": "already have too many pets", - "locations": [{"line": 1, "column": 1}], - } - ], - } - - async def handler(request): - return json_response(graphql_response) - - raw_server = await aiohttp_raw_server(handler) - events = capture_events() - - client = await aiohttp_client(raw_server) - response = await client.post("/graphql", json=graphql_request) - - assert response.status == 200 - assert await response.json() == graphql_response - - (event,) = events - - assert event["request"]["url"] == "http://127.0.0.1:{}/graphql".format( - raw_server.port - ) - assert event["request"]["method"] == "POST" - assert event["request"]["query_string"] == "" - assert event["request"]["data"] == graphql_request - assert event["contexts"]["response"]["data"] == graphql_response - - assert event["request"]["api_target"] == "graphql" - assert event["fingerprint"] == ["AddPet", "mutation", 200] - assert ( - event["exception"]["values"][0]["value"] - == "GraphQL request failed, name: AddPet, type: mutation" - ) - - -@pytest.mark.asyncio -async def test_graphql_get_client_no_errors_returned( - sentry_init, capture_events, aiohttp_raw_server, aiohttp_client -): - sentry_init(send_default_pii=True, integrations=[AioHttpIntegration()]) - - graphql_response = { - "data": None, - } - - async def handler(request): - return json_response(graphql_response) - - raw_server = await aiohttp_raw_server(handler) - events = capture_events() - - client = await aiohttp_client(raw_server) - response = await client.get( - "/graphql", params={"query": "query GetPet {pet{name}}"} - ) - - assert response.status == 200 - assert await response.json() == graphql_response - - assert not events - - -@pytest.mark.asyncio -async def test_graphql_post_client_no_errors_returned( - sentry_init, capture_events, aiohttp_client, aiohttp_raw_server -): - sentry_init(send_default_pii=True, integrations=[AioHttpIntegration()]) - - graphql_request = { - "query": dedent( - """ - mutation AddPet ($name: String!) { - addPet(name: $name) { - id - name - } - } - """ - ), - "variables": { - "name": "Lucy", - }, - } - graphql_response = { - "data": None, - } - - async def handler(request): - return json_response(graphql_response) - - raw_server = await aiohttp_raw_server(handler) - events = capture_events() - - client = await aiohttp_client(raw_server) - response = await client.post("/graphql", json=graphql_request) - - assert response.status == 200 - assert await response.json() == graphql_response - - assert not events - - -@pytest.mark.asyncio -async def test_graphql_no_get_errors_if_option_is_off( - sentry_init, capture_events, aiohttp_raw_server, aiohttp_client -): - sentry_init( - send_default_pii=True, - integrations=[AioHttpIntegration(capture_graphql_errors=False)], - ) - - graphql_response = { - "data": None, - "errors": [ - { - "message": "some error", - "locations": [{"line": 2, "column": 3}], - "path": ["pet"], - } - ], - } - - async def handler(request): - return json_response(graphql_response) - - raw_server = await aiohttp_raw_server(handler) - events = capture_events() - - client = await aiohttp_client(raw_server) - response = await client.get( - "/graphql", params={"query": "query GetPet {pet{name}}"} - ) - - assert response.status == 200 - assert await response.json() == graphql_response - - assert not events - - -@pytest.mark.asyncio -async def test_graphql_no_post_errors_if_option_is_off( - sentry_init, capture_events, aiohttp_client, aiohttp_raw_server -): - sentry_init( - send_default_pii=True, - integrations=[AioHttpIntegration(capture_graphql_errors=False)], - ) - - graphql_request = { - "query": dedent( - """ - mutation AddPet ($name: String!) { - addPet(name: $name) { - id - name - } - } - """ - ), - "variables": { - "name": "Lucy", - }, - } - graphql_response = { - "data": None, - "errors": [ - { - "message": "already have too many pets", - "locations": [{"line": 1, "column": 1}], - } - ], - } - - async def handler(request): - return json_response(graphql_response) - - raw_server = await aiohttp_raw_server(handler) - events = capture_events() - - client = await aiohttp_client(raw_server) - response = await client.post("/graphql", json=graphql_request) - - assert response.status == 200 - assert await response.json() == graphql_response - - assert not events - - -@pytest.mark.asyncio -async def test_graphql_non_json_response( - sentry_init, capture_events, aiohttp_client, aiohttp_raw_server -): - sentry_init( - send_default_pii=True, - integrations=[AioHttpIntegration()], - ) - - graphql_request = { - "query": dedent( - """ - mutation AddPet ($name: String!) { - addPet(name: $name) { - id - name - } - } - """ - ), - "variables": { - "name": "Lucy", - }, - } - - async def handler(request): - return Response(body=b"not json") - - raw_server = await aiohttp_raw_server(handler) - events = capture_events() - - client = await aiohttp_client(raw_server) - response = await client.post("/graphql", json=graphql_request) - - assert response.status == 200 - assert await response.text() == "not json" - - assert not events diff --git a/tests/integrations/httpx/test_httpx.py b/tests/integrations/httpx/test_httpx.py index 8bae3ee3c4..e141faa282 100644 --- a/tests/integrations/httpx/test_httpx.py +++ b/tests/integrations/httpx/test_httpx.py @@ -2,7 +2,7 @@ import pytest import httpx -from textwrap import dedent +import responses from sentry_sdk import capture_message, start_transaction from sentry_sdk.consts import MATCH_ALL, SPANDATA @@ -13,17 +13,12 @@ except ImportError: import mock # python < 3.3 -try: - from urllib.parse import parse_qsl -except ImportError: - from urlparse import parse_qsl # type: ignore - @pytest.mark.parametrize( "httpx_client", (httpx.Client(), httpx.AsyncClient()), ) -def test_crumb_capture_and_hint(sentry_init, capture_events, httpx_client, httpx_mock): +def test_crumb_capture_and_hint(sentry_init, capture_events, httpx_client): def before_breadcrumb(crumb, hint): crumb["data"]["extra"] = "foo" return crumb @@ -31,7 +26,7 @@ def before_breadcrumb(crumb, hint): sentry_init(integrations=[HttpxIntegration()], before_breadcrumb=before_breadcrumb) url = "http://example.com/" - httpx_mock.add_response() + responses.add(responses.GET, url, status=200) with start_transaction(): events = capture_events() @@ -66,11 +61,11 @@ def before_breadcrumb(crumb, hint): "httpx_client", (httpx.Client(), httpx.AsyncClient()), ) -def test_outgoing_trace_headers(sentry_init, httpx_client, httpx_mock): +def test_outgoing_trace_headers(sentry_init, httpx_client): sentry_init(traces_sample_rate=1.0, integrations=[HttpxIntegration()]) url = "http://example.com/" - httpx_mock.add_response() + responses.add(responses.GET, url, status=200) with start_transaction( name="/interactions/other-dogs/new-dog", @@ -98,9 +93,7 @@ def test_outgoing_trace_headers(sentry_init, httpx_client, httpx_mock): "httpx_client", (httpx.Client(), httpx.AsyncClient()), ) -def test_outgoing_trace_headers_append_to_baggage( - sentry_init, httpx_client, httpx_mock -): +def test_outgoing_trace_headers_append_to_baggage(sentry_init, httpx_client): sentry_init( traces_sample_rate=1.0, integrations=[HttpxIntegration()], @@ -108,7 +101,7 @@ def test_outgoing_trace_headers_append_to_baggage( ) url = "http://example.com/" - httpx_mock.add_response() + responses.add(responses.GET, url, status=200) with start_transaction( name="/interactions/other-dogs/new-dog", @@ -280,12 +273,12 @@ def test_option_trace_propagation_targets( @pytest.mark.tests_internal_exceptions -def test_omit_url_data_if_parsing_fails(sentry_init, capture_events, httpx_mock): +def test_omit_url_data_if_parsing_fails(sentry_init, capture_events): sentry_init(integrations=[HttpxIntegration()]) httpx_client = httpx.Client() url = "http://example.com" - httpx_mock.add_response() + responses.add(responses.GET, url, status=200) events = capture_events() with mock.patch( @@ -304,336 +297,3 @@ def test_omit_url_data_if_parsing_fails(sentry_init, capture_events, httpx_mock) "reason": "OK", # no url related data } - - -@pytest.mark.parametrize( - "httpx_client", - (httpx.Client(), httpx.AsyncClient()), -) -def test_graphql_get_client_error_captured( - sentry_init, capture_events, httpx_client, httpx_mock -): - sentry_init(send_default_pii=True, integrations=[HttpxIntegration()]) - - url = "http://example.com/graphql" - graphql_response = { - "data": None, - "errors": [ - { - "message": "some error", - "locations": [{"line": 2, "column": 3}], - "path": ["user"], - } - ], - } - params = {"query": "query QueryName {user{name}}"} - - httpx_mock.add_response(method="GET", json=graphql_response) - - events = capture_events() - - if asyncio.iscoroutinefunction(httpx_client.get): - response = asyncio.get_event_loop().run_until_complete( - httpx_client.get(url, params=params) - ) - else: - response = httpx_client.get(url, params=params) - - assert response.status_code == 200 - assert response.json() == graphql_response - - (event,) = events - - assert event["request"]["url"] == url - assert event["request"]["method"] == "GET" - assert dict(parse_qsl(event["request"]["query_string"])) == params - assert "data" not in event["request"] - assert event["contexts"]["response"]["data"] == graphql_response - - assert event["request"]["api_target"] == "graphql" - assert event["fingerprint"] == ["QueryName", "query", 200] - assert ( - event["exception"]["values"][0]["value"] - == "GraphQL request failed, name: QueryName, type: query" - ) - - -@pytest.mark.parametrize( - "httpx_client", - (httpx.Client(), httpx.AsyncClient()), -) -def test_graphql_post_client_error_captured( - sentry_init, capture_events, httpx_client, httpx_mock -): - sentry_init(send_default_pii=True, integrations=[HttpxIntegration()]) - - url = "http://example.com/graphql" - graphql_request = { - "query": dedent( - """ - mutation AddPet ($name: String!) { - addPet(name: $name) { - id - name - } - } - """ - ), - "variables": { - "name": "Lucy", - }, - } - graphql_response = { - "data": None, - "errors": [ - { - "message": "already have too many pets", - "locations": [{"line": 1, "column": 1}], - } - ], - } - httpx_mock.add_response(method="POST", json=graphql_response) - - events = capture_events() - - if asyncio.iscoroutinefunction(httpx_client.post): - response = asyncio.get_event_loop().run_until_complete( - httpx_client.post(url, json=graphql_request) - ) - else: - response = httpx_client.post(url, json=graphql_request) - - assert response.status_code == 200 - assert response.json() == graphql_response - - (event,) = events - - assert event["request"]["url"] == url - assert event["request"]["method"] == "POST" - assert event["request"]["query_string"] == "" - assert event["request"]["data"] == graphql_request - assert event["contexts"]["response"]["data"] == graphql_response - - assert event["request"]["api_target"] == "graphql" - assert event["fingerprint"] == ["AddPet", "mutation", 200] - assert ( - event["exception"]["values"][0]["value"] - == "GraphQL request failed, name: AddPet, type: mutation" - ) - - -@pytest.mark.parametrize( - "httpx_client", - (httpx.Client(), httpx.AsyncClient()), -) -def test_graphql_get_client_no_errors_returned( - sentry_init, capture_events, httpx_client, httpx_mock -): - sentry_init(send_default_pii=True, integrations=[HttpxIntegration()]) - - url = "http://example.com/graphql" - graphql_response = { - "data": None, - } - params = {"query": "query QueryName {user{name}}"} - - httpx_mock.add_response(method="GET", json=graphql_response) - - events = capture_events() - - if asyncio.iscoroutinefunction(httpx_client.get): - response = asyncio.get_event_loop().run_until_complete( - httpx_client.get(url, params=params) - ) - else: - response = httpx_client.get(url, params=params) - - assert response.status_code == 200 - assert response.json() == graphql_response - - assert not events - - -@pytest.mark.parametrize( - "httpx_client", - (httpx.Client(), httpx.AsyncClient()), -) -def test_graphql_post_client_no_errors_returned( - sentry_init, capture_events, httpx_client, httpx_mock -): - sentry_init(send_default_pii=True, integrations=[HttpxIntegration()]) - - url = "http://example.com/graphql" - graphql_request = { - "query": dedent( - """ - mutation AddPet ($name: String!) { - addPet(name: $name) { - id - name - } - } - """ - ), - "variables": { - "name": "Lucy", - }, - } - graphql_response = { - "data": None, - } - httpx_mock.add_response(method="POST", json=graphql_response) - - events = capture_events() - - if asyncio.iscoroutinefunction(httpx_client.post): - response = asyncio.get_event_loop().run_until_complete( - httpx_client.post(url, json=graphql_request) - ) - else: - response = httpx_client.post(url, json=graphql_request) - - assert response.status_code == 200 - assert response.json() == graphql_response - - assert not events - - -@pytest.mark.parametrize( - "httpx_client", - (httpx.Client(), httpx.AsyncClient()), -) -def test_graphql_no_get_errors_if_option_is_off( - sentry_init, capture_events, httpx_client, httpx_mock -): - sentry_init( - send_default_pii=True, - integrations=[HttpxIntegration(capture_graphql_errors=False)], - ) - - url = "http://example.com/graphql" - graphql_response = { - "data": None, - "errors": [ - { - "message": "some error", - "locations": [{"line": 2, "column": 3}], - "path": ["user"], - } - ], - } - params = {"query": "query QueryName {user{name}}"} - - httpx_mock.add_response(method="GET", json=graphql_response) - - events = capture_events() - - if asyncio.iscoroutinefunction(httpx_client.get): - response = asyncio.get_event_loop().run_until_complete( - httpx_client.get(url, params=params) - ) - else: - response = httpx_client.get(url, params=params) - - assert response.status_code == 200 - assert response.json() == graphql_response - - assert not events - - -@pytest.mark.parametrize( - "httpx_client", - (httpx.Client(), httpx.AsyncClient()), -) -def test_graphql_no_post_errors_if_option_is_off( - sentry_init, capture_events, httpx_client, httpx_mock -): - sentry_init( - send_default_pii=True, - integrations=[HttpxIntegration(capture_graphql_errors=False)], - ) - - url = "http://example.com/graphql" - graphql_request = { - "query": dedent( - """ - mutation AddPet ($name: String!) { - addPet(name: $name) { - id - name - } - } - """ - ), - "variables": { - "name": "Lucy", - }, - } - graphql_response = { - "data": None, - "errors": [ - { - "message": "already have too many pets", - "locations": [{"line": 1, "column": 1}], - } - ], - } - httpx_mock.add_response(method="POST", json=graphql_response) - - events = capture_events() - - if asyncio.iscoroutinefunction(httpx_client.post): - response = asyncio.get_event_loop().run_until_complete( - httpx_client.post(url, json=graphql_request) - ) - else: - response = httpx_client.post(url, json=graphql_request) - - assert response.status_code == 200 - assert response.json() == graphql_response - - assert not events - - -@pytest.mark.parametrize( - "httpx_client", - (httpx.Client(), httpx.AsyncClient()), -) -def test_graphql_non_json_response( - sentry_init, capture_events, httpx_client, httpx_mock -): - sentry_init( - send_default_pii=True, - integrations=[HttpxIntegration()], - ) - - url = "http://example.com/graphql" - graphql_request = { - "query": dedent( - """ - mutation AddPet ($name: String!) { - addPet(name: $name) { - id - name - } - } - """ - ), - "variables": { - "name": "Lucy", - }, - } - httpx_mock.add_response(method="POST") - - events = capture_events() - - if asyncio.iscoroutinefunction(httpx_client.post): - response = asyncio.get_event_loop().run_until_complete( - httpx_client.post(url, json=graphql_request) - ) - else: - response = httpx_client.post(url, json=graphql_request) - - assert response.status_code == 200 - - assert not events diff --git a/tests/integrations/requests/test_requests.py b/tests/integrations/requests/test_requests.py index c4c15e9a8d..aecf64762d 100644 --- a/tests/integrations/requests/test_requests.py +++ b/tests/integrations/requests/test_requests.py @@ -1,4 +1,3 @@ -import json import pytest import responses @@ -8,15 +7,11 @@ from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations.stdlib import StdlibIntegration -from tests.conftest import MockServerRequestHandler, create_mock_http_server - try: from unittest import mock # python 3.3 and above except ImportError: import mock # python < 3.3 -PORT = create_mock_http_server() - def test_crumb_capture(sentry_init, capture_events): sentry_init(integrations=[StdlibIntegration()]) @@ -67,22 +62,3 @@ def test_omit_url_data_if_parsing_fails(sentry_init, capture_events): "reason": response.reason, # no url related data } - - -def test_graphql_integration_doesnt_affect_responses(sentry_init, capture_events): - sentry_init(integrations=[StdlibIntegration()]) - - events = capture_events() - - msg = {"errors": [{"message": "some message"}]} - - def do_POST(self): # noqa: N802 - self.send_response(200) - self.end_headers() - self.wfile.write(json.dumps(msg).encode()) - - with mock.patch.object(MockServerRequestHandler, "do_POST", do_POST): - response = requests.post("http://localhost:{}".format(PORT) + "/graphql") - - assert len(events) == 1 - assert response.json() == msg diff --git a/tests/integrations/stdlib/test_httplib.py b/tests/integrations/stdlib/test_httplib.py index 39efe3d22f..e40f5222d7 100644 --- a/tests/integrations/stdlib/test_httplib.py +++ b/tests/integrations/stdlib/test_httplib.py @@ -1,6 +1,4 @@ -import json import random -from textwrap import dedent import pytest @@ -18,14 +16,6 @@ # py3 from http.client import HTTPConnection, HTTPSConnection -try: - # py3 - from urllib.parse import parse_qsl, urlencode -except ImportError: - # py2 - from urlparse import parse_qsl # type: ignore - from urllib import urlencode # type: ignore - try: from unittest import mock # python 3.3 and above except ImportError: @@ -37,7 +27,7 @@ from sentry_sdk.tracing import Transaction from sentry_sdk.integrations.stdlib import StdlibIntegration -from tests.conftest import MockServerRequestHandler, create_mock_http_server +from tests.conftest import create_mock_http_server PORT = create_mock_http_server() @@ -351,299 +341,3 @@ def test_option_trace_propagation_targets( else: assert "sentry-trace" not in request_headers assert "baggage" not in request_headers - - -def test_graphql_get_client_error_captured(sentry_init, capture_events): - sentry_init(send_default_pii=True, integrations=[StdlibIntegration()]) - - params = {"query": "query QueryName {user{name}}"} - graphql_response = { - "data": None, - "errors": [ - { - "message": "some error", - "locations": [{"line": 2, "column": 3}], - "path": ["user"], - } - ], - } - - events = capture_events() - - def do_GET(self): # noqa: N802 - self.send_response(200) - self.end_headers() - self.wfile.write(json.dumps(graphql_response).encode()) - - with mock.patch.object(MockServerRequestHandler, "do_GET", do_GET): - conn = HTTPConnection("localhost:{}".format(PORT)) - conn.request("GET", "/graphql?" + urlencode(params)) - response = conn.getresponse() - - # make sure the response can still be read() normally - assert response.read() == json.dumps(graphql_response).encode() - - (event,) = events - - assert event["request"]["url"] == "http://localhost:{}/graphql".format(PORT) - assert event["request"]["method"] == "GET" - assert dict(parse_qsl(event["request"]["query_string"])) == params - assert "data" not in event["request"] - assert event["contexts"]["response"]["data"] == graphql_response - - assert event["request"]["api_target"] == "graphql" - assert event["fingerprint"] == ["QueryName", "query", 200] - assert ( - event["exception"]["values"][0]["value"] - == "GraphQL request failed, name: QueryName, type: query" - ) - - -def test_graphql_post_client_error_captured(sentry_init, capture_events): - sentry_init(send_default_pii=True, integrations=[StdlibIntegration()]) - - graphql_request = { - "query": dedent( - """ - mutation AddPet ($name: String!) { - addPet(name: $name) { - id - name - } - } - """ - ), - "variables": { - "name": "Lucy", - }, - } - graphql_response = { - "data": None, - "errors": [ - { - "message": "already have too many pets", - "locations": [{"line": 1, "column": 1}], - } - ], - } - - events = capture_events() - - def do_POST(self): # noqa: N802 - self.send_response(200) - self.end_headers() - self.wfile.write(json.dumps(graphql_response).encode()) - - with mock.patch.object(MockServerRequestHandler, "do_POST", do_POST): - conn = HTTPConnection("localhost:{}".format(PORT)) - conn.request("POST", "/graphql", body=json.dumps(graphql_request).encode()) - response = conn.getresponse() - - # make sure the response can still be read() normally - assert response.read() == json.dumps(graphql_response).encode() - - (event,) = events - - assert event["request"]["url"] == "http://localhost:{}/graphql".format(PORT) - assert event["request"]["method"] == "POST" - assert event["request"]["query_string"] == "" - assert event["request"]["data"] == graphql_request - assert event["contexts"]["response"]["data"] == graphql_response - - assert event["request"]["api_target"] == "graphql" - assert event["fingerprint"] == ["AddPet", "mutation", 200] - assert ( - event["exception"]["values"][0]["value"] - == "GraphQL request failed, name: AddPet, type: mutation" - ) - - -def test_graphql_get_client_no_errors_returned(sentry_init, capture_events): - sentry_init(send_default_pii=True, integrations=[StdlibIntegration()]) - - params = {"query": "query QueryName {user{name}}"} - graphql_response = { - "data": None, - } - - events = capture_events() - - def do_GET(self): # noqa: N802 - self.send_response(200) - self.end_headers() - self.wfile.write(json.dumps(graphql_response).encode()) - - with mock.patch.object(MockServerRequestHandler, "do_GET", do_GET): - conn = HTTPConnection("localhost:{}".format(PORT)) - conn.request("GET", "/graphql?" + urlencode(params)) - response = conn.getresponse() - - # make sure the response can still be read() normally - assert response.read() == json.dumps(graphql_response).encode() - - assert not events - - -def test_graphql_post_client_no_errors_returned(sentry_init, capture_events): - sentry_init(send_default_pii=True, integrations=[StdlibIntegration()]) - - graphql_request = { - "query": dedent( - """ - mutation AddPet ($name: String!) { - addPet(name: $name) { - id - name - } - } - """ - ), - "variables": { - "name": "Lucy", - }, - } - graphql_response = { - "data": None, - } - - events = capture_events() - - def do_POST(self): # noqa: N802 - self.send_response(200) - self.end_headers() - self.wfile.write(json.dumps(graphql_response).encode()) - - with mock.patch.object(MockServerRequestHandler, "do_POST", do_POST): - conn = HTTPConnection("localhost:{}".format(PORT)) - conn.request("POST", "/graphql", body=json.dumps(graphql_request).encode()) - response = conn.getresponse() - - # make sure the response can still be read() normally - assert response.read() == json.dumps(graphql_response).encode() - - assert not events - - -def test_graphql_no_get_errors_if_option_is_off(sentry_init, capture_events): - sentry_init( - send_default_pii=True, - integrations=[StdlibIntegration(capture_graphql_errors=False)], - ) - - params = {"query": "query QueryName {user{name}}"} - graphql_response = { - "data": None, - "errors": [ - { - "message": "some error", - "locations": [{"line": 2, "column": 3}], - "path": ["user"], - } - ], - } - - events = capture_events() - - def do_GET(self): # noqa: N802 - self.send_response(200) - self.end_headers() - self.wfile.write(json.dumps(graphql_response).encode()) - - with mock.patch.object(MockServerRequestHandler, "do_GET", do_GET): - conn = HTTPConnection("localhost:{}".format(PORT)) - conn.request("GET", "/graphql?" + urlencode(params)) - response = conn.getresponse() - - # make sure the response can still be read() normally - assert response.read() == json.dumps(graphql_response).encode() - - assert not events - - -def test_graphql_no_post_errors_if_option_is_off(sentry_init, capture_events): - sentry_init( - send_default_pii=True, - integrations=[StdlibIntegration(capture_graphql_errors=False)], - ) - - graphql_request = { - "query": dedent( - """ - mutation AddPet ($name: String!) { - addPet(name: $name) { - id - name - } - } - """ - ), - "variables": { - "name": "Lucy", - }, - } - graphql_response = { - "data": None, - "errors": [ - { - "message": "already have too many pets", - "locations": [{"line": 1, "column": 1}], - } - ], - } - - events = capture_events() - - def do_POST(self): # noqa: N802 - self.send_response(200) - self.end_headers() - self.wfile.write(json.dumps(graphql_response).encode()) - - with mock.patch.object(MockServerRequestHandler, "do_POST", do_POST): - conn = HTTPConnection("localhost:{}".format(PORT)) - conn.request("POST", "/graphql", body=json.dumps(graphql_request).encode()) - response = conn.getresponse() - - # make sure the response can still be read() normally - assert response.read() == json.dumps(graphql_response).encode() - - assert not events - - -def test_graphql_non_json_response(sentry_init, capture_events): - sentry_init( - send_default_pii=True, - integrations=[StdlibIntegration()], - ) - - graphql_request = { - "query": dedent( - """ - mutation AddPet ($name: String!) { - addPet(name: $name) { - id - name - } - } - """ - ), - "variables": { - "name": "Lucy", - }, - } - - events = capture_events() - - def do_POST(self): # noqa: N802 - self.send_response(200) - self.end_headers() - self.wfile.write(b"not json") - - with mock.patch.object(MockServerRequestHandler, "do_POST", do_POST): - conn = HTTPConnection("localhost:{}".format(PORT)) - conn.request("POST", "/graphql", body=json.dumps(graphql_request).encode()) - response = conn.getresponse() - - # make sure the response can still be read() normally - assert response.read() == b"not json" - - assert not events diff --git a/tests/test_utils.py b/tests/test_utils.py index 3a5a4bd384..47460d39b0 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -11,8 +11,6 @@ parse_version, sanitize_url, serialize_frame, - _get_graphql_operation_name, - _get_graphql_operation_type, ) try: @@ -425,103 +423,3 @@ def test_match_regex_list(item, regex_list, expected_result): ) def test_parse_version(version, expected_result): assert parse_version(version) == expected_result - - -@pytest.mark.parametrize( - "query,expected_result", - [ - [{"query": '{cats(id: "7") {name}}'}, "anonymous"], - [{"query": 'query {cats(id: "7") {name}}'}, "anonymous"], - [{"query": 'query CatQuery {cats(id: "7") {name}}'}, "CatQuery"], - [ - { - "query": 'mutation {addCategory(id: 6, name: "Lily", cats: [8, 2]) {name cats {name}}}' - }, - "anonymous", - ], - [ - { - "query": 'mutation categoryAdd {addCategory(id: 6, name: "Lily", cats: [8, 2]) {name cats {name}}}' - }, - "categoryAdd", - ], - [ - { - "query": "subscription {newLink {id url description postedBy {id name email}}}" - }, - "anonymous", - ], - [ - { - "query": "subscription PostSubcription {newLink {id url description postedBy {id name email}}}" - }, - "PostSubcription", - ], - [ - { - "query": 'query CatQuery {cats(id: "7") {name}}', - "operationName": "SomeOtherOperation", - "variables": {}, - }, - "SomeOtherOperation", - ], - [ - { - "query": "mutation AddPet ($name: String!) {addPet(name: $name) {id name}}}" - }, - "AddPet", - ], - ], -) -def test_graphql_operation_name_extraction(query, expected_result): - assert _get_graphql_operation_name(query) == expected_result - - -@pytest.mark.parametrize( - "query,expected_result", - [ - [{"query": '{cats(id: "7") {name}}'}, "query"], - [{"query": 'query {cats(id: "7") {name}}'}, "query"], - [{"query": 'query CatQuery {cats(id: "7") {name}}'}, "query"], - [ - { - "query": 'mutation {addCategory(id: 6, name: "Lily", cats: [8, 2]) {name cats {name}}}' - }, - "mutation", - ], - [ - { - "query": 'mutation categoryAdd {addCategory(id: 6, name: "Lily", cats: [8, 2]) {name cats {name}}}' - }, - "mutation", - ], - [ - { - "query": "subscription {newLink {id url description postedBy {id name email}}}" - }, - "subscription", - ], - [ - { - "query": "subscription PostSubcription {newLink {id url description postedBy {id name email}}}" - }, - "subscription", - ], - [ - { - "query": 'query CatQuery {cats(id: "7") {name}}', - "operationName": "SomeOtherOperation", - "variables": {}, - }, - "query", - ], - [ - { - "query": "mutation AddPet ($name: String!) {addPet(name: $name) {id name}}}" - }, - "mutation", - ], - ], -) -def test_graphql_operation_type_extraction(query, expected_result): - assert _get_graphql_operation_type(query) == expected_result