aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/tests
diff options
context:
space:
mode:
authorTom Christie2013-06-28 09:07:09 +0100
committerTom Christie2013-06-28 09:07:09 +0100
commit58d38d694eba94d4c6c20edcbefb5f4f91e6dd0f (patch)
treeba72bed1f1f20bed9d023fb076e0adcf7e92abf1 /rest_framework/tests
parentde00ec95c3007dd90b5b01f7486b430699ea63c1 (diff)
parent1f6a59d76da286e7a89e8e41317beb16a4aab7c7 (diff)
downloaddjango-rest-framework-58d38d694eba94d4c6c20edcbefb5f4f91e6dd0f.tar.bz2
Merge branch 'master' into writable-nested-modelserializer
Diffstat (limited to 'rest_framework/tests')
-rw-r--r--rest_framework/tests/description.py26
-rw-r--r--rest_framework/tests/test_authentication.py41
-rw-r--r--rest_framework/tests/test_description.py13
-rw-r--r--rest_framework/tests/test_fields.py30
-rw-r--r--rest_framework/tests/test_hyperlinkedserializers.py27
-rw-r--r--rest_framework/tests/test_routers.py73
-rw-r--r--rest_framework/tests/test_throttling.py109
7 files changed, 306 insertions, 13 deletions
diff --git a/rest_framework/tests/description.py b/rest_framework/tests/description.py
new file mode 100644
index 00000000..b46d7f54
--- /dev/null
+++ b/rest_framework/tests/description.py
@@ -0,0 +1,26 @@
+# -- coding: utf-8 --
+
+# Apparently there is a python 2.6 issue where docstrings of imported view classes
+# do not retain their encoding information even if a module has a proper
+# encoding declaration at the top of its source file. Therefore for tests
+# to catch unicode related errors, a mock view has to be declared in a separate
+# module.
+
+from rest_framework.views import APIView
+
+
+# test strings snatched from http://www.columbia.edu/~fdc/utf8/,
+# http://winrus.com/utf8-jap.htm and memory
+UTF8_TEST_DOCSTRING = (
+ 'zażółć gęślą jaźń'
+ 'Sîne klâwen durh die wolken sint geslagen'
+ 'Τη γλώσσα μου έδωσαν ελληνική'
+ 'யாமறிந்த மொழிகளிலே தமிழ்மொழி'
+ 'На берегу пустынных волн'
+ 'てすと'
+ 'アイウエオカキクケコサシスセソタチツテ'
+)
+
+
+class ViewWithNonASCIICharactersInDocstring(APIView):
+ __doc__ = UTF8_TEST_DOCSTRING
diff --git a/rest_framework/tests/test_authentication.py b/rest_framework/tests/test_authentication.py
index d46ac079..6a50be06 100644
--- a/rest_framework/tests/test_authentication.py
+++ b/rest_framework/tests/test_authentication.py
@@ -428,6 +428,47 @@ class OAuthTests(TestCase):
response = self.csrf_client.post('/oauth-with-scope/', params)
self.assertEqual(response.status_code, 200)
+ @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed')
+ @unittest.skipUnless(oauth, 'oauth2 not installed')
+ def test_bad_consumer_key(self):
+ """Ensure POSTing using HMAC_SHA1 signature method passes"""
+ params = {
+ 'oauth_version': "1.0",
+ 'oauth_nonce': oauth.generate_nonce(),
+ 'oauth_timestamp': int(time.time()),
+ 'oauth_token': self.token.key,
+ 'oauth_consumer_key': 'badconsumerkey'
+ }
+
+ req = oauth.Request(method="POST", url="http://testserver/oauth/", parameters=params)
+
+ signature_method = oauth.SignatureMethod_HMAC_SHA1()
+ req.sign_request(signature_method, self.consumer, self.token)
+ auth = req.to_header()["Authorization"]
+
+ response = self.csrf_client.post('/oauth/', HTTP_AUTHORIZATION=auth)
+ self.assertEqual(response.status_code, 401)
+
+ @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed')
+ @unittest.skipUnless(oauth, 'oauth2 not installed')
+ def test_bad_token_key(self):
+ """Ensure POSTing using HMAC_SHA1 signature method passes"""
+ params = {
+ 'oauth_version': "1.0",
+ 'oauth_nonce': oauth.generate_nonce(),
+ 'oauth_timestamp': int(time.time()),
+ 'oauth_token': 'badtokenkey',
+ 'oauth_consumer_key': self.consumer.key
+ }
+
+ req = oauth.Request(method="POST", url="http://testserver/oauth/", parameters=params)
+
+ signature_method = oauth.SignatureMethod_HMAC_SHA1()
+ req.sign_request(signature_method, self.consumer, self.token)
+ auth = req.to_header()["Authorization"]
+
+ response = self.csrf_client.post('/oauth/', HTTP_AUTHORIZATION=auth)
+ self.assertEqual(response.status_code, 401)
class OAuth2Tests(TestCase):
"""OAuth 2.0 authentication"""
diff --git a/rest_framework/tests/test_description.py b/rest_framework/tests/test_description.py
index 52c1a34c..8019f5ec 100644
--- a/rest_framework/tests/test_description.py
+++ b/rest_framework/tests/test_description.py
@@ -2,8 +2,10 @@
from __future__ import unicode_literals
from django.test import TestCase
+from rest_framework.compat import apply_markdown, smart_text
from rest_framework.views import APIView
-from rest_framework.compat import apply_markdown
+from rest_framework.tests.description import ViewWithNonASCIICharactersInDocstring
+from rest_framework.tests.description import UTF8_TEST_DOCSTRING
from rest_framework.utils.formatting import get_view_name, get_view_description
# We check that docstrings get nicely un-indented.
@@ -83,11 +85,10 @@ class TestViewNamesAndDescriptions(TestCase):
Unicode in docstrings should be respected.
"""
- class MockView(APIView):
- """Проверка"""
- pass
-
- self.assertEqual(get_view_description(MockView), "Проверка")
+ self.assertEqual(
+ get_view_description(ViewWithNonASCIICharactersInDocstring),
+ smart_text(UTF8_TEST_DOCSTRING)
+ )
def test_view_description_can_be_empty(self):
"""
diff --git a/rest_framework/tests/test_fields.py b/rest_framework/tests/test_fields.py
index 69a0468e..6836ec86 100644
--- a/rest_framework/tests/test_fields.py
+++ b/rest_framework/tests/test_fields.py
@@ -866,3 +866,33 @@ class FieldCallableDefault(TestCase):
into = {}
field.field_from_native({}, {}, 'field', into)
self.assertEqual(into, {'field': 'foo bar'})
+
+
+class CustomIntegerField(TestCase):
+ """
+ Test that custom fields apply min_value and max_value constraints
+ """
+ def test_custom_fields_can_be_validated_for_value(self):
+
+ class MoneyField(models.PositiveIntegerField):
+ pass
+
+ class EntryModel(models.Model):
+ bank = MoneyField(validators=[validators.MaxValueValidator(100)])
+
+ class EntrySerializer(serializers.ModelSerializer):
+ class Meta:
+ model = EntryModel
+
+ entry = EntryModel(bank=1)
+
+ serializer = EntrySerializer(entry, data={"bank": 11})
+ self.assertTrue(serializer.is_valid())
+
+ serializer = EntrySerializer(entry, data={"bank": -1})
+ self.assertFalse(serializer.is_valid())
+
+ serializer = EntrySerializer(entry, data={"bank": 101})
+ self.assertFalse(serializer.is_valid())
+
+
diff --git a/rest_framework/tests/test_hyperlinkedserializers.py b/rest_framework/tests/test_hyperlinkedserializers.py
index 1894ddb2..129600cb 100644
--- a/rest_framework/tests/test_hyperlinkedserializers.py
+++ b/rest_framework/tests/test_hyperlinkedserializers.py
@@ -301,3 +301,30 @@ class TestOptionalRelationHyperlinkedView(TestCase):
data=json.dumps(self.data),
content_type='application/json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+
+class TestOverriddenURLField(TestCase):
+ def setUp(self):
+ class OverriddenURLSerializer(serializers.HyperlinkedModelSerializer):
+ url = serializers.SerializerMethodField('get_url')
+
+ class Meta:
+ model = BlogPost
+ fields = ('title', 'url')
+
+ def get_url(self, obj):
+ return 'foo bar'
+
+ self.Serializer = OverriddenURLSerializer
+ self.obj = BlogPost.objects.create(title='New blog post')
+
+ def test_overridden_url_field(self):
+ """
+ The 'url' field should respect overriding.
+ Regression test for #936.
+ """
+ serializer = self.Serializer(self.obj)
+ self.assertEqual(
+ serializer.data,
+ {'title': 'New blog post', 'url': 'foo bar'}
+ )
diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py
index a7534f70..d375f4a8 100644
--- a/rest_framework/tests/test_routers.py
+++ b/rest_framework/tests/test_routers.py
@@ -2,11 +2,12 @@ from __future__ import unicode_literals
from django.db import models
from django.test import TestCase
from django.test.client import RequestFactory
-from rest_framework import serializers, viewsets
+from django.core.exceptions import ImproperlyConfigured
+from rest_framework import serializers, viewsets, permissions
from rest_framework.compat import include, patterns, url
from rest_framework.decorators import link, action
from rest_framework.response import Response
-from rest_framework.routers import SimpleRouter
+from rest_framework.routers import SimpleRouter, DefaultRouter
factory = RequestFactory()
@@ -120,7 +121,7 @@ class TestCustomLookupFields(TestCase):
)
-class TestTrailingSlash(TestCase):
+class TestTrailingSlashIncluded(TestCase):
def setUp(self):
class NoteViewSet(viewsets.ModelViewSet):
model = RouterTestModel
@@ -135,7 +136,7 @@ class TestTrailingSlash(TestCase):
self.assertEqual(expected[idx], self.urls[idx].regex.pattern)
-class TestTrailingSlash(TestCase):
+class TestTrailingSlashRemoved(TestCase):
def setUp(self):
class NoteViewSet(viewsets.ModelViewSet):
model = RouterTestModel
@@ -148,3 +149,67 @@ class TestTrailingSlash(TestCase):
expected = ['^notes$', '^notes/(?P<pk>[^/]+)$']
for idx in range(len(expected)):
self.assertEqual(expected[idx], self.urls[idx].regex.pattern)
+
+
+class TestNameableRoot(TestCase):
+ def setUp(self):
+ class NoteViewSet(viewsets.ModelViewSet):
+ model = RouterTestModel
+ self.router = DefaultRouter()
+ self.router.root_view_name = 'nameable-root'
+ self.router.register(r'notes', NoteViewSet)
+ self.urls = self.router.urls
+
+ def test_router_has_custom_name(self):
+ expected = 'nameable-root'
+ self.assertEqual(expected, self.urls[0].name)
+
+
+class TestActionKeywordArgs(TestCase):
+ """
+ Ensure keyword arguments passed in the `@action` decorator
+ are properly handled. Refs #940.
+ """
+
+ def setUp(self):
+ class TestViewSet(viewsets.ModelViewSet):
+ permission_classes = []
+
+ @action(permission_classes=[permissions.AllowAny])
+ def custom(self, request, *args, **kwargs):
+ return Response({
+ 'permission_classes': self.permission_classes
+ })
+
+ self.router = SimpleRouter()
+ self.router.register(r'test', TestViewSet, base_name='test')
+ self.view = self.router.urls[-1].callback
+
+ def test_action_kwargs(self):
+ request = factory.post('/test/0/custom/')
+ response = self.view(request)
+ self.assertEqual(
+ response.data,
+ {'permission_classes': [permissions.AllowAny]}
+ )
+
+class TestActionAppliedToExistingRoute(TestCase):
+ """
+ Ensure `@action` decorator raises an except when applied
+ to an existing route
+ """
+
+ def test_exception_raised_when_action_applied_to_existing_route(self):
+ class TestViewSet(viewsets.ModelViewSet):
+
+ @action()
+ def retrieve(self, request, *args, **kwargs):
+ return Response({
+ 'hello': 'world'
+ })
+
+ self.router = SimpleRouter()
+ self.router.register(r'test', TestViewSet, base_name='test')
+
+ with self.assertRaises(ImproperlyConfigured):
+ self.router.urls
diff --git a/rest_framework/tests/test_throttling.py b/rest_framework/tests/test_throttling.py
index da400b2f..d35d3709 100644
--- a/rest_framework/tests/test_throttling.py
+++ b/rest_framework/tests/test_throttling.py
@@ -7,7 +7,7 @@ from django.contrib.auth.models import User
from django.core.cache import cache
from django.test.client import RequestFactory
from rest_framework.views import APIView
-from rest_framework.throttling import UserRateThrottle
+from rest_framework.throttling import UserRateThrottle, ScopedRateThrottle
from rest_framework.response import Response
@@ -36,8 +36,6 @@ class MockView_MinuteThrottling(APIView):
class ThrottlingTests(TestCase):
- urls = 'rest_framework.tests.test_throttling'
-
def setUp(self):
"""
Reset the cache so that no throttles will be active
@@ -141,3 +139,108 @@ class ThrottlingTests(TestCase):
(60, None),
(80, None)
))
+
+
+class ScopedRateThrottleTests(TestCase):
+ """
+ Tests for ScopedRateThrottle.
+ """
+
+ def setUp(self):
+ class XYScopedRateThrottle(ScopedRateThrottle):
+ TIMER_SECONDS = 0
+ THROTTLE_RATES = {'x': '3/min', 'y': '1/min'}
+ timer = lambda self: self.TIMER_SECONDS
+
+ class XView(APIView):
+ throttle_classes = (XYScopedRateThrottle,)
+ throttle_scope = 'x'
+
+ def get(self, request):
+ return Response('x')
+
+ class YView(APIView):
+ throttle_classes = (XYScopedRateThrottle,)
+ throttle_scope = 'y'
+
+ def get(self, request):
+ return Response('y')
+
+ class UnscopedView(APIView):
+ throttle_classes = (XYScopedRateThrottle,)
+
+ def get(self, request):
+ return Response('y')
+
+ self.throttle_class = XYScopedRateThrottle
+ self.factory = RequestFactory()
+ self.x_view = XView.as_view()
+ self.y_view = YView.as_view()
+ self.unscoped_view = UnscopedView.as_view()
+
+ def increment_timer(self, seconds=1):
+ self.throttle_class.TIMER_SECONDS += seconds
+
+ def test_scoped_rate_throttle(self):
+ request = self.factory.get('/')
+
+ # Should be able to hit x view 3 times per minute.
+ response = self.x_view(request)
+ self.assertEqual(200, response.status_code)
+
+ self.increment_timer()
+ response = self.x_view(request)
+ self.assertEqual(200, response.status_code)
+
+ self.increment_timer()
+ response = self.x_view(request)
+ self.assertEqual(200, response.status_code)
+
+ self.increment_timer()
+ response = self.x_view(request)
+ self.assertEqual(429, response.status_code)
+
+ # Should be able to hit y view 1 time per minute.
+ self.increment_timer()
+ response = self.y_view(request)
+ self.assertEqual(200, response.status_code)
+
+ self.increment_timer()
+ response = self.y_view(request)
+ self.assertEqual(429, response.status_code)
+
+ # Ensure throttles properly reset by advancing the rest of the minute
+ self.increment_timer(55)
+
+ # Should still be able to hit x view 3 times per minute.
+ response = self.x_view(request)
+ self.assertEqual(200, response.status_code)
+
+ self.increment_timer()
+ response = self.x_view(request)
+ self.assertEqual(200, response.status_code)
+
+ self.increment_timer()
+ response = self.x_view(request)
+ self.assertEqual(200, response.status_code)
+
+ self.increment_timer()
+ response = self.x_view(request)
+ self.assertEqual(429, response.status_code)
+
+ # Should still be able to hit y view 1 time per minute.
+ self.increment_timer()
+ response = self.y_view(request)
+ self.assertEqual(200, response.status_code)
+
+ self.increment_timer()
+ response = self.y_view(request)
+ self.assertEqual(429, response.status_code)
+
+ def test_unscoped_view_not_throttled(self):
+ request = self.factory.get('/')
+
+ for idx in range(10):
+ self.increment_timer()
+ response = self.unscoped_view(request)
+ self.assertEqual(200, response.status_code)