summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Cramer <dcramer@gmail.com>2017-02-15 13:25:08 -0800
committerDavid Cramer <dcramer@gmail.com>2017-02-15 13:41:09 -0800
commitf901ad591299beee330e351e02d273303c176b35 (patch)
treec4d327b493e85a671a0eefbb952f6fe91254b603
parent632c343470a668ef08dd943e5b22489b469444f5 (diff)
downloadraven-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-xci/setup4
-rw-r--r--conftest.py23
-rw-r--r--raven/contrib/django/middleware/__init__.py22
-rwxr-xr-xsetup.py7
-rw-r--r--tests/contrib/django/api.py49
-rw-r--r--tests/contrib/django/test_tastypie.py47
-rw-r--r--tests/contrib/django/tests.py24
-rw-r--r--tests/contrib/django/urls.py39
8 files changed, 172 insertions, 43 deletions
diff --git a/ci/setup b/ci/setup
index 20134a3..f4afff1 100755
--- a/ci/setup
+++ b/ci/setup
@@ -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)
diff --git a/setup.py b/setup.py
index 7881fdd..887ab2a 100755
--- a/setup.py
+++ b/setup.py
@@ -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)),
+ )