├── .circleci └── config.yml ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── Makefile ├── README.rst ├── example ├── __init__.py ├── schema.py ├── settings.py ├── urls.py └── wsgi.py ├── graphene_django_sentry ├── __init__.py └── views.py ├── manage.py ├── requirements_dev.txt ├── setup.cfg ├── setup.py └── tests ├── __init__.py └── test_views.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | unit_tests: 4 | docker: 5 | - image: circleci/python:3.6 6 | steps: 7 | - checkout 8 | - run: 9 | name: Build Dependencies 10 | command: | 11 | python3 -m venv venv 12 | . venv/bin/activate 13 | pip install -r requirements_dev.txt 14 | - run: 15 | name: Run tests 16 | command: | 17 | . venv/bin/activate 18 | make lint 19 | make test 20 | workflows: 21 | version: 2 22 | build_and_test: 23 | jobs: 24 | - unit_tests 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * graphene_django_sentry version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | # pytest 105 | .pytest_cache 106 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Paul Hallett 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every little bit 8 | helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/phalt/graphene-django-sentry/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help 30 | wanted" is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "enhancement" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | graphene-django-sentry could always use more documentation, whether as part of the 42 | official graphene-django-sentry docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | Submit Feedback 46 | ~~~~~~~~~~~~~~~ 47 | 48 | The best way to send feedback is to file an issue at https://github.com/phalt/graphene-django-sentry/issues. 49 | 50 | If you are proposing a feature: 51 | 52 | * Explain in detail how it would work. 53 | * Keep the scope as narrow as possible, to make it easier to implement. 54 | * Remember that this is a volunteer-driven project, and that contributions 55 | are welcome :) 56 | 57 | Get Started! 58 | ------------ 59 | 60 | Ready to contribute? Here's how to set up `graphene-django-sentry` for local development. 61 | 62 | 1. Fork the `graphene-django-sentry` repo on GitHub. 63 | 2. Clone your fork locally:: 64 | 65 | $ git clone git@github.com:your_name_here/graphene-django-sentry.git 66 | 67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 68 | 69 | $ mkvirtualenv graphene-django-sentry 70 | $ cd graphene-django-sentry/ 71 | $ python setup.py develop 72 | 73 | 4. Create a branch for local development:: 74 | 75 | $ git checkout -b name-of-your-bugfix-or-feature 76 | 77 | Now you can make your changes locally. 78 | 79 | 5. When you're done making changes, check that your changes pass flake8 and the 80 | tests:: 81 | 82 | $ make test 83 | 84 | To get flake8 just pip install them into your virtualenv. 85 | 86 | 6. Commit your changes and push your branch to GitHub:: 87 | 88 | $ git add . 89 | $ git commit -m "Your detailed description of your changes." 90 | $ git push origin name-of-your-bugfix-or-feature 91 | 92 | 7. Submit a pull request through the GitHub website. 93 | 94 | Pull Request Guidelines 95 | ----------------------- 96 | 97 | Before you submit a pull request, check that it meets these guidelines: 98 | 99 | 1. The pull request should include tests. 100 | 2. If the pull request adds functionality, the docs should be updated. Put 101 | your new functionality into a function with a docstring, and add the 102 | feature to the list in README.rst. 103 | 104 | Tips 105 | ---- 106 | 107 | To run a subset of tests:: 108 | 109 | $ py.test tests.test_thing 110 | 111 | 112 | Deploying 113 | --------- 114 | 115 | A reminder for the maintainers on how to deploy. 116 | Make sure all your changes are committed (including an entry in HISTORY.rst). 117 | Then run:: 118 | 119 | $ bumpversion patch # possible: major / minor / patch 120 | $ git push 121 | $ git push --tags 122 | 123 | Travis will then deploy to PyPI if tests pass. 124 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 0.1.0 (2018-07-26) 6 | ------------------ 7 | 8 | * First release on PyPI. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018, Paul Hallett 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | 7 | try: 8 | from urllib import pathname2url 9 | except: 10 | from urllib.request import pathname2url 11 | 12 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 13 | endef 14 | export BROWSER_PYSCRIPT 15 | 16 | define PRINT_HELP_PYSCRIPT 17 | import re, sys 18 | 19 | for line in sys.stdin: 20 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 21 | if match: 22 | target, help = match.groups() 23 | print("%-20s %s" % (target, help)) 24 | endef 25 | export PRINT_HELP_PYSCRIPT 26 | 27 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 28 | 29 | help: 30 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 31 | 32 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 33 | 34 | clean-build: ## remove build artifacts 35 | rm -fr build/ 36 | rm -fr dist/ 37 | rm -fr .eggs/ 38 | find . -name '*.egg-info' -exec rm -fr {} + 39 | find . -name '*.egg' -exec rm -f {} + 40 | 41 | clean-pyc: ## remove Python file artifacts 42 | find . -name '*.pyc' -exec rm -f {} + 43 | find . -name '*.pyo' -exec rm -f {} + 44 | find . -name '*~' -exec rm -f {} + 45 | find . -name '__pycache__' -exec rm -fr {} + 46 | 47 | clean-test: ## remove test and coverage artifacts 48 | rm -f .coverage 49 | rm -fr htmlcov/ 50 | rm -fr .pytest_cache 51 | 52 | lint: ## check style with flake8 53 | flake8 graphene_django_sentry tests 54 | mypy --ignore-missing-imports graphene_django_sentry/ tests/ 55 | isort -c -rc graphene_django_sentry/ tests/ 56 | 57 | test: ## run tests quickly with the default Python 58 | pytest 59 | 60 | coverage: 61 | py.test --cov-report html --cov=graphene_django_sentry tests/ 62 | $(BROWSER) htmlcov/index.html 63 | 64 | run-docs: ## generate Sphinx HTML documentation, including API docs 65 | mkdocs serve 66 | 67 | release: dist ## package and upload a release 68 | twine upload dist/* 69 | 70 | dist: clean ## builds source and wheel package 71 | python setup.py sdist 72 | python setup.py bdist_wheel 73 | ls -l dist 74 | 75 | install: clean ## install the package to the active Python's site-packages 76 | python setup.py install 77 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 🌐⚠️ graphene-django-sentry 2 | ------- 3 | 4 | *Capture Sentry exceptions in Graphene views* 5 | 6 | When using `Graphene Django`_, you sometimes want to raise exceptions and capture them in the API. 7 | 8 | However, Graphene Django eats the raised exceptions and you won't see it in Sentry! 😭 9 | 10 | This package thinly wraps the normal GraphQLView with a handler that deals with Sentry errors properly. 11 | 12 | So the results: 13 | 14 | 1. Sentry will show the true exceptions. 15 | 2. Graphene will continue to work like normal. 16 | 17 | Works with: 18 | 19 | * Python 3.6+ 20 | * Django 2.1+ 21 | * graphene-django 2.2+ 22 | 23 | 24 | .. image:: https://img.shields.io/pypi/v/graphene-django-sentry.svg 25 | :target: https://pypi.org/project/graphene-django-sentry/ 26 | 27 | .. image:: https://img.shields.io/pypi/pyversions/graphene_django_sentry.svg 28 | :target: https://pypi.org/project/graphene_django_sentry/ 29 | 30 | .. image:: https://img.shields.io/pypi/l/graphene-django-sentry.svg 31 | :target: https://pypi.org/project/graphene-django-sentry/ 32 | 33 | .. image:: https://img.shields.io/pypi/status/graphene_django_sentry.svg 34 | :target: https://pypi.org/project/graphene_django_sentry/ 35 | 36 | .. image:: https://circleci.com/gh/phalt/graphene-django-sentry/tree/master.svg?style=svg 37 | :target: https://circleci.com/gh/phalt/graphene-django-sentry/tree/master 38 | 39 | Installing the project is easy: 40 | 41 | .. code-block:: bash 42 | 43 | pip install graphene-django-sentry 44 | 45 | Full blown example: 46 | 47 | .. code-block:: python 48 | 49 | # urls.py 50 | 51 | from .schema import schema 52 | from graphene_django_sentry.views import SentryGraphQLView 53 | 54 | urlpatterns = [ 55 | url( 56 | r'^graphql', 57 | csrf_exempt(SentryGraphQLView.as_view(schema=schema)), 58 | name='graphql', 59 | ), 60 | ] 61 | 62 | 📖 What can I do? 63 | -------- 64 | 65 | - Capture Sentry exceptions properly when they are `raise`-d in Graphene views. 66 | 67 | 🏗 Status 68 | ---------- 69 | 70 | graphene-django-sentry is currently stable and suitable for use. 71 | 72 | 🎥 Credits 73 | ----------- 74 | 75 | This package was created with Cookiecutter_. 76 | 77 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 78 | .. _Graphene Django: https://github.com/graphql-python/graphene-django 79 | -------------------------------------------------------------------------------- /example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phalt/graphene-django-sentry/49e05345d84605ddad7cec36da7410a3100658ec/example/__init__.py -------------------------------------------------------------------------------- /example/schema.py: -------------------------------------------------------------------------------- 1 | import graphene 2 | from graphene import ObjectType, Schema 3 | 4 | 5 | class QueryRoot(ObjectType): 6 | 7 | test = graphene.String(who=graphene.String()) 8 | 9 | def resolve_test(self, info): 10 | return "Hello World" 11 | 12 | 13 | schema = Schema(query=QueryRoot) 14 | -------------------------------------------------------------------------------- /example/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | ROOT_PATH = os.path.dirname(os.path.abspath(__file__)) 5 | sys.path.insert(0, ROOT_PATH + '/example/') 6 | 7 | SECRET_KEY = 1 8 | 9 | INSTALLED_APPS = [ 10 | 'graphene_django', 11 | ] 12 | 13 | DATABASES = { 14 | 'default': { 15 | 'ENGINE': 'django.db.backends.sqlite3', 16 | 'NAME': 'django_test.sqlite', 17 | } 18 | } 19 | 20 | TEMPLATES = [ 21 | { 22 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 23 | 'DIRS': [], 24 | 'APP_DIRS': True, 25 | }, 26 | ] 27 | 28 | GRAPHENE = { 29 | 'SCHEMA': 'example.schema.schema' 30 | } 31 | 32 | ROOT_URLCONF = 'example.urls' 33 | -------------------------------------------------------------------------------- /example/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from django.views.decorators.csrf import csrf_exempt 3 | 4 | from graphene_django_sentry import SentryGraphQLView 5 | 6 | from .schema import schema # Your graphQL schema 7 | 8 | urlpatterns = [ 9 | url( 10 | r'^graphql', 11 | csrf_exempt(SentryGraphQLView.as_view(schema=schema)), 12 | name='graphql', 13 | ), 14 | ] 15 | -------------------------------------------------------------------------------- /example/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for example project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_test_settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /graphene_django_sentry/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Top-level package for graphene_django_sentry.""" 4 | 5 | __author__ = """Paul Hallett""" 6 | __email__ = 'paulandrewhallett@gmail.com' 7 | __version__ = '0.1.0' 8 | 9 | from .views import SentryGraphQLView # noqa 10 | -------------------------------------------------------------------------------- /graphene_django_sentry/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Views for graphene-django-sentry.""" 4 | 5 | 6 | import sentry_sdk 7 | from graphene_django.views import GraphQLView 8 | 9 | 10 | class SentryGraphQLView(GraphQLView): 11 | def execute_graphql_request(self, *args, **kwargs): 12 | """Extract any exceptions and send them to Sentry""" 13 | result = super().execute_graphql_request(*args, **kwargs) 14 | if result.errors: 15 | self._capture_sentry_exceptions(result.errors) 16 | return result 17 | 18 | def _capture_sentry_exceptions(self, errors): 19 | for error in errors: 20 | try: 21 | sentry_sdk.capture_exception(error.original_error) 22 | except AttributeError: 23 | sentry_sdk.capture_exception(error) 24 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == '__main__': 6 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings') 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | bumpversion==0.5.3 2 | wheel==0.30.0 3 | watchdog==0.8.3 4 | flake8==3.5.0 5 | coverage==4.5.1 6 | twine==1.10.0 7 | 8 | pytest==3.6 9 | pytest-runner==2.11.1 10 | pytest-cov==2.3.1 11 | pytest-django==3.4.1 12 | 13 | mypy==0.610 14 | isort==4.3.4 15 | mkdocs==0.17.5 16 | 17 | 18 | django==2.1.6 19 | graphene_django==2.2.0 20 | sentry-sdk==0.5.2 21 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.2.0 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version='{current_version}' 8 | replace = version='{new_version}' 9 | 10 | [bumpversion:file:graphene_django_sentry/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | 14 | [bdist_wheel] 15 | universal = 1 16 | 17 | [flake8] 18 | exclude = docs 19 | 20 | [aliases] 21 | test = pytest 22 | 23 | [tool:pytest] 24 | collect_ignore = ['setup.py'] 25 | DJANGO_SETTINGS_MODULE=example.settings 26 | 27 | [isort] 28 | known_first_party = graphene_django_sentry,tests 29 | default_section = THIRDPARTY 30 | sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER 31 | multi_line_output = 3 32 | include_trailing_comma = true 33 | force_grid_wrap = 0 34 | combine_as_imports = true 35 | line_length = 79 36 | 37 | [mypy] 38 | warn_incomplete_stub = True 39 | ignore_missing_imports = True 40 | check_untyped_defs = True 41 | cache_dir = /dev/null 42 | warn_redundant_casts = True 43 | warn_unused_configs = True 44 | strict_optional = True 45 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """The setup script.""" 5 | 6 | from setuptools import find_packages, setup 7 | 8 | with open('README.rst') as readme_file: 9 | readme = readme_file.read() 10 | 11 | requirements = [ 12 | 'graphene_django==2.2.0', 13 | 'sentry-sdk==0.5.2', 14 | ] 15 | 16 | setup_requirements = ['pytest-runner', ] 17 | 18 | test_requirements = ['pytest', ] 19 | 20 | setup( 21 | author="Paul Hallett", 22 | author_email='paulandrewhallett@gmail.com', 23 | classifiers=[ 24 | 'Development Status :: 4 - Beta', 25 | 'Intended Audience :: Developers', 26 | 'License :: OSI Approved :: MIT License', 27 | 'Natural Language :: English', 28 | 'Programming Language :: Python :: 3.6', 29 | 'Programming Language :: Python :: 3.7', 30 | ], 31 | description="Capture Sentry exceptions in Graphene views", 32 | install_requires=requirements, 33 | license="MIT license", 34 | long_description=readme, 35 | include_package_data=True, 36 | keywords='GraphQL graphene django sentry', 37 | name='graphene-django-sentry', 38 | packages=find_packages(include=['graphene_django_sentry']), 39 | setup_requires=setup_requirements, 40 | test_suite='tests', 41 | tests_require=test_requirements, 42 | url='https://github.com/phalt/graphene-django-sentry', 43 | version='0.2.0', 44 | zip_safe=False, 45 | ) 46 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phalt/graphene-django-sentry/49e05345d84605ddad7cec36da7410a3100658ec/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_views.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from http.client import OK 5 | from unittest.mock import MagicMock, patch 6 | from urllib.parse import urlencode 7 | 8 | import graphene_django.views as views 9 | from django.urls import reverse 10 | from graphql import GraphQLError 11 | from graphql.error import GraphQLLocatedError 12 | 13 | 14 | class CustomException(Exception): 15 | """ Boom! """ 16 | 17 | 18 | def get_query_string(): 19 | path = reverse('graphql') 20 | query = urlencode({'query': 'query {test}'}) 21 | path = f'{path}?{query}' 22 | return path 23 | 24 | 25 | def test_view(client): 26 | result = client.get( 27 | get_query_string(), 28 | HTTP_ACCEPT="application/json;q=0.8, text/html;q=0.9", 29 | ) 30 | assert result.status_code == OK 31 | 32 | 33 | @patch.object(views.GraphQLView, 'execute_graphql_request') 34 | @patch('sentry_sdk.capture_exception') 35 | def test_execute_graphql_request( 36 | mocked_capture_exception, 37 | mocked_method, 38 | client, 39 | ): 40 | error = CustomException('Boom') 41 | errors = [GraphQLLocatedError([], error)] 42 | 43 | mocked_return_value = MagicMock() 44 | mocked_return_value.errors = errors 45 | 46 | mocked_method.return_value = mocked_return_value 47 | result = client.get( 48 | get_query_string(), 49 | HTTP_ACCEPT="application/json;q=0.8, text/html;q=0.9", 50 | ) 51 | assert result.status_code == 400 52 | assert result.json()['errors'][0]['message'] == 'Boom' 53 | mocked_capture_exception.assert_called_with(error) 54 | 55 | 56 | @patch.object(views.GraphQLView, 'execute_graphql_request') 57 | @patch('sentry_sdk.capture_exception') 58 | def test_execute_graphql_request_raises_raw_graphql_exceptions( 59 | mocked_capture_exception, 60 | mocked_method, 61 | client, 62 | ): 63 | error = GraphQLError(message='Syntax error in GraphQL query') 64 | 65 | mocked_return_value = MagicMock() 66 | mocked_return_value.errors = [error] 67 | 68 | mocked_method.return_value = mocked_return_value 69 | result = client.get( 70 | reverse('graphql'), 71 | {'query': '{__schema{types{name}}}'}, 72 | ) 73 | assert result.status_code == 400 74 | assert result.json()['errors'][0]['message'] == ( 75 | 'Syntax error in GraphQL query' 76 | ) 77 | mocked_capture_exception.assert_called_with(error) 78 | --------------------------------------------------------------------------------