diff options
author | David Cramer <dcramer@gmail.com> | 2017-02-15 13:25:08 -0800 |
---|---|---|
committer | David Cramer <dcramer@gmail.com> | 2017-02-15 13:41:09 -0800 |
commit | f901ad591299beee330e351e02d273303c176b35 (patch) | |
tree | c4d327b493e85a671a0eefbb952f6fe91254b603 | |
parent | 632c343470a668ef08dd943e5b22489b469444f5 (diff) | |
download | raven-ref/tastypie-tests.tar.gz |
[django] improve various request behaviorref/tastypie-tests
- expand tests to cover basic tastypie
- correct leaking of request local in middleware
- improve django test fundamentals
-rwxr-xr-x | ci/setup | 4 | ||||
-rw-r--r-- | conftest.py | 23 | ||||
-rw-r--r-- | raven/contrib/django/middleware/__init__.py | 22 | ||||
-rwxr-xr-x | setup.py | 7 | ||||
-rw-r--r-- | tests/contrib/django/api.py | 49 | ||||
-rw-r--r-- | tests/contrib/django/test_tastypie.py | 47 | ||||
-rw-r--r-- | tests/contrib/django/tests.py | 24 | ||||
-rw-r--r-- | tests/contrib/django/urls.py | 39 |
8 files changed, 172 insertions, 43 deletions
@@ -33,3 +33,7 @@ if [[ ${DJANGO_BITS[0]} -eq 1 && ${DJANGO_BITS[1]} -lt 8 ]]; then elif [ -n "$DJANGO" ]; then pip install "pytest-django>=3.0,<3.1" fi + +if [[ ${DJANGO_BITS[0]} -eq 1 && ${DJANGO_BITS[1]} -lt 7 && ${DJANGO_BITS[1]} -gt 5 ]]; then + pip install "django-tastypie==0.12.1" +fi diff --git a/conftest.py b/conftest.py index e80bfb9..1e330ab 100644 --- a/conftest.py +++ b/conftest.py @@ -4,29 +4,33 @@ import os.path import pytest import sys - collect_ignore = [] if sys.version_info[0] > 2: if sys.version_info[1] < 3: - collect_ignore.append("tests/contrib/flask") + collect_ignore.append('tests/contrib/flask') if sys.version_info[1] == 2: - collect_ignore.append("tests/handlers/logbook") + collect_ignore.append('tests/handlers/logbook') try: import gevent # NOQA except ImportError: - collect_ignore.append("tests/transport/gevent") + collect_ignore.append('tests/transport/gevent') try: import web # NOQA except ImportError: - collect_ignore.append("tests/contrib/webpy") + collect_ignore.append('tests/contrib/webpy') try: import django # NOQA except ImportError: django = None - collect_ignore.append("tests/contrib/django") + collect_ignore.append('tests/contrib/django') + +try: + import tastypie # NOQA +except ImportError: + collect_ignore.append('tests/contrib/django/test_tastypie.py') INSTALLED_APPS = [ @@ -88,6 +92,7 @@ def configure_django(project_root): }], ALLOWED_HOSTS=['*'], DISABLE_SENTRY_INSTRUMENTATION=True, + SENTRY_CLIENT='tests.contrib.django.tests.MockClient' ) @@ -96,6 +101,12 @@ def pytest_configure(config): configure_django(os.path.dirname(os.path.abspath(__file__))) +def pytest_runtest_teardown(item): + if django: + from raven.contrib.django.models import client + client.events = [] + + @pytest.fixture def project_root(): return os.path.dirname(os.path.abspath(__file__)) diff --git a/raven/contrib/django/middleware/__init__.py b/raven/contrib/django/middleware/__init__.py index b4c6c20..5d3f024 100644 --- a/raven/contrib/django/middleware/__init__.py +++ b/raven/contrib/django/middleware/__init__.py @@ -81,7 +81,16 @@ class SentryMiddleware(MiddlewareMixin): def process_request(self, request): self._txid = None + SentryMiddleware.thread.request = request + # we utilize request_finished as the exception gets reported + # *after* process_response is executed, and thus clearing the + # transaction there would leave it empty + # XXX(dcramer): weakref's cause a threading issue in certain + # versions of Django (e.g. 1.6). While they'd be ideal, we're under + # the assumption that Django will always call our function except + # in the situation of a process or thread dying. + request_finished.connect(self.request_finished, weak=False) def process_view(self, request, func, args, kwargs): from raven.contrib.django.models import client @@ -91,16 +100,7 @@ class SentryMiddleware(MiddlewareMixin): client.get_transaction_from_request(request) ) except Exception as exc: - client.error_logger.exception(repr(exc)) - else: - # we utilize request_finished as the exception gets reported - # *after* process_response is executed, and thus clearing the - # transaction there would leave it empty - # XXX(dcramer): weakref's cause a threading issue in certain - # versions of Django (e.g. 1.6). While they'd be ideal, we're under - # the assumption that Django will always call our function except - # in the situation of a process or thread dying. - request_finished.connect(self.request_finished, weak=False) + client.error_logger.exception(repr(exc), extra={'request': request}) return None @@ -111,6 +111,8 @@ class SentryMiddleware(MiddlewareMixin): client.transaction.pop(self._txid) self._txid = None + SentryMiddleware.thread.request = None + request_finished.disconnect(self.request_finished) @@ -39,6 +39,7 @@ install_requires = [ ] unittest2_requires = ['unittest2'] + flask_requires = [ 'Flask>=0.8', 'blinker>=1.1', @@ -79,8 +80,10 @@ tests_require = [ 'webob', 'webtest', 'anyjson', -] + (flask_requires + flask_tests_requires + - unittest2_requires + webpy_tests_requires) +] + ( + flask_requires + flask_tests_requires + + unittest2_requires + webpy_tests_requires +) class PyTest(TestCommand): diff --git a/tests/contrib/django/api.py b/tests/contrib/django/api.py new file mode 100644 index 0000000..7ef4812 --- /dev/null +++ b/tests/contrib/django/api.py @@ -0,0 +1,49 @@ +from __future__ import absolute_import + +from uuid import uuid4 +from tastypie.bundle import Bundle +from tastypie.resources import Resource + +from raven.contrib.django.models import client + + +class Item(object): + def __init__(self, pk=None, name=None): + self.pk = pk or uuid4().hex + self.name = name or '' + + +class ExampleResource(Resource): + class Meta: + resource_name = 'example' + object_class = Item + + def detail_uri_kwargs(self, bundle_or_obj): + kwargs = {} + + if isinstance(bundle_or_obj, Bundle): + kwargs['pk'] = bundle_or_obj.obj.pk + else: + kwargs['pk'] = bundle_or_obj.pk + + return kwargs + + def obj_get_list(self, bundle, **kwargs): + try: + raise Exception('oops') + except: + client.captureException() + return [] + + def obj_create(self, bundle, **kwargs): + try: + raise Exception('oops') + except: + client.captureException() + + bundle.obj = Item(**kwargs) + bundle = self.full_hydrate(bundle) + return bundle + + def obj_update(self, bundle, **kwargs): + return self.obj_create(bundle, **kwargs) diff --git a/tests/contrib/django/test_tastypie.py b/tests/contrib/django/test_tastypie.py new file mode 100644 index 0000000..df143af --- /dev/null +++ b/tests/contrib/django/test_tastypie.py @@ -0,0 +1,47 @@ +from __future__ import absolute_import + +from tastypie.test import ResourceTestCase + +from raven.contrib.django.models import client + + +class TastypieTest(ResourceTestCase): + def setUp(self): + super(TastypieTest, self).setUp() + self.path = '/api/v1/example/' + + def test_list_break(self): + self.api_client.get(self.path) + + assert len(client.events) == 1 + event = client.events.pop(0) + assert 'exception' in event + exc = event['exception']['values'][-1] + assert exc['type'] == 'Exception' + assert exc['value'] == 'oops' + assert 'request' in event + assert event['request']['url'] == 'http://testserver/api/v1/example/' + + def test_create_break(self): + self.api_client.post('/api/v1/example/') + + assert len(client.events) == 1 + event = client.events.pop(0) + assert 'exception' in event + exc = event['exception']['values'][-1] + assert exc['type'] == 'Exception' + assert exc['value'] == 'oops' + assert 'request' in event + assert event['request']['url'] == 'http://testserver/api/v1/example/' + + def test_update_break(self): + self.api_client.put('/api/v1/example/foo/', data={'name': 'bar'}) + + assert len(client.events) == 1 + event = client.events.pop(0) + assert 'exception' in event + exc = event['exception']['values'][-1] + assert exc['type'] == 'Exception' + assert exc['value'] == 'oops' + assert 'request' in event + assert event['request']['url'] == 'http://testserver/api/v1/example/foo/' diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index bfff82a..0adf7c6 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -19,6 +19,7 @@ from django.core.handlers.wsgi import WSGIRequest from django.http import QueryDict from django.template import TemplateSyntaxError from django.test import TestCase +from django.test.client import Client as DjangoTestClient, ClientHandler as DjangoTestClientHandler from django.utils.translation import gettext_lazy from exam import fixture @@ -36,11 +37,8 @@ from raven.contrib.django.views import is_valid_origin from raven.transport import HTTPTransport from raven.utils.serializer import transform -from django.test.client import Client as DjangoTestClient, ClientHandler as DjangoTestClientHandler from .models import MyTestModel -settings.SENTRY_CLIENT = 'tests.contrib.django.tests.TempStoreClient' - DJANGO_15 = django.VERSION >= (1, 5, 0) DJANGO_18 = django.VERSION >= (1, 8, 0) DJANGO_110 = django.VERSION >= (1, 10, 0) @@ -69,10 +67,10 @@ class MockSentryMiddleware(Sentry): return list(super(MockSentryMiddleware, self).__call__(environ, start_response)) -class TempStoreClient(DjangoClient): +class MockClient(DjangoClient): def __init__(self, *args, **kwargs): self.events = [] - super(TempStoreClient, self).__init__(*args, **kwargs) + super(MockClient, self).__init__(*args, **kwargs) def send(self, **kwargs): self.events.append(kwargs) @@ -81,7 +79,7 @@ class TempStoreClient(DjangoClient): return True -class DisabledTempStoreClient(TempStoreClient): +class DisabledMockClient(MockClient): def is_enabled(self, **kwargs): return False @@ -117,7 +115,7 @@ class ClientProxyTest(TestCase): def test_proxy_responds_as_client(self): assert get_client() == client - @mock.patch.object(TempStoreClient, 'captureMessage') + @mock.patch.object(MockClient, 'captureMessage') def test_basic(self, captureMessage): client.captureMessage(message='foo') captureMessage.assert_called_once_with(message='foo') @@ -397,7 +395,7 @@ class DjangoClientTest(TestCase): def test_404_middleware_when_disabled(self): extra_settings = { 'MIDDLEWARE_CLASSES': ['raven.contrib.django.middleware.Sentry404CatchMiddleware'], - 'SENTRY_CLIENT': 'tests.contrib.django.tests.DisabledTempStoreClient', + 'SENTRY_CLIENT': 'tests.contrib.django.tests.DisabledMockClient', } with Settings(**extra_settings): resp = self.client.get('/non-existent-page') @@ -408,7 +406,7 @@ class DjangoClientTest(TestCase): extra_settings = { 'SENTRY_CLIENT': 'raven.contrib.django.DjangoClient', # default } - # Should return fallback client (TempStoreClient) + # Should return fallback client (MockClient) client = get_client('nonexistent.and.invalid') # client should be valid, and the same as with the next call. @@ -796,7 +794,7 @@ class SentryExceptionHandlerTest(TestCase): self.client = get_client() self.handler = SentryDjangoHandler(self.client) - @mock.patch.object(TempStoreClient, 'captureException') + @mock.patch.object(MockClient, 'captureException') @mock.patch('sys.exc_info') def test_does_capture_exception(self, exc_info, captureException): exc_info.return_value = self.exc_info @@ -804,7 +802,7 @@ class SentryExceptionHandlerTest(TestCase): captureException.assert_called_once_with(exc_info=self.exc_info, request=self.request) - @mock.patch.object(TempStoreClient, 'send') + @mock.patch.object(MockClient, 'send') @mock.patch('sys.exc_info') def test_does_exclude_filtered_types(self, exc_info, mock_send): exc_info.return_value = self.exc_info @@ -817,7 +815,7 @@ class SentryExceptionHandlerTest(TestCase): assert not mock_send.called - @mock.patch.object(TempStoreClient, 'send') + @mock.patch.object(MockClient, 'send') @mock.patch('sys.exc_info') def test_ignore_exceptions_with_expression_match(self, exc_info, mock_send): exc_info.return_value = self.exc_info @@ -833,7 +831,7 @@ class SentryExceptionHandlerTest(TestCase): assert not mock_send.called - @mock.patch.object(TempStoreClient, 'send') + @mock.patch.object(MockClient, 'send') @mock.patch('sys.exc_info') def test_ignore_exceptions_with_module_match(self, exc_info, mock_send): exc_info.return_value = self.exc_info diff --git a/tests/contrib/django/urls.py b/tests/contrib/django/urls.py index 86ebdf7..7afd543 100644 --- a/tests/contrib/django/urls.py +++ b/tests/contrib/django/urls.py @@ -2,13 +2,15 @@ from __future__ import absolute_import from django.conf import settings try: - from django.conf.urls import url + from django.conf.urls import url, include except ImportError: # for Django version less than 1.4 - from django.conf.urls.defaults import url # NOQA + from django.conf.urls.defaults import url, include # NOQA from django.http import HttpResponse +from tests.contrib.django import views + def handler404(request): return HttpResponse('', status=404) @@ -19,16 +21,29 @@ def handler500(request): raise ValueError('handler500') return HttpResponse('', status=500) -import tests.contrib.django.views urlpatterns = ( - url(r'^no-error$', tests.contrib.django.views.no_error, name='sentry-no-error'), - url(r'^fake-login$', tests.contrib.django.views.fake_login, name='sentry-fake-login'), - url(r'^trigger-500$', tests.contrib.django.views.raise_exc, name='sentry-raise-exc'), - url(r'^trigger-500-ioerror$', tests.contrib.django.views.raise_ioerror, name='sentry-raise-ioerror'), - url(r'^trigger-500-decorated$', tests.contrib.django.views.decorated_raise_exc, name='sentry-raise-exc-decor'), - url(r'^trigger-500-django$', tests.contrib.django.views.django_exc, name='sentry-django-exc'), - url(r'^trigger-500-template$', tests.contrib.django.views.template_exc, name='sentry-template-exc'), - url(r'^trigger-500-log-request$', tests.contrib.django.views.logging_request_exc, name='sentry-log-request-exc'), - url(r'^trigger-event$', tests.contrib.django.views.capture_event, name='sentry-trigger-event'), + url(r'^no-error$', views.no_error, name='sentry-no-error'), + url(r'^fake-login$', views.fake_login, name='sentry-fake-login'), + url(r'^trigger-500$', views.raise_exc, name='sentry-raise-exc'), + url(r'^trigger-500-ioerror$', views.raise_ioerror, name='sentry-raise-ioerror'), + url(r'^trigger-500-decorated$', views.decorated_raise_exc, name='sentry-raise-exc-decor'), + url(r'^trigger-500-django$', views.django_exc, name='sentry-django-exc'), + url(r'^trigger-500-template$', views.template_exc, name='sentry-template-exc'), + url(r'^trigger-500-log-request$', views.logging_request_exc, name='sentry-log-request-exc'), + url(r'^trigger-event$', views.capture_event, name='sentry-trigger-event'), ) + +try: + from tastypie.api import Api +except ImportError: + pass +else: + from tests.contrib.django.api import ExampleResource + + v1_api = Api(api_name='v1') + v1_api.register(ExampleResource()) + + urlpatterns += ( + url(r'^api/', include(v1_api.urls)), + ) |