From baa518cd890103173dd18857c609432bd47c6be4 Mon Sep 17 00:00:00 2001
From: Jharrod LaFon
Date: Fri, 5 Sep 2014 15:30:01 -0700
Subject: Moved OAuth support out of DRF and into a separate package, per #1767
---
 tests/conftest.py            |  20 --
 tests/settings.py            |  21 ---
 tests/test_authentication.py | 430 +------------------------------------------
 3 files changed, 1 insertion(+), 470 deletions(-)
(limited to 'tests')
diff --git a/tests/conftest.py b/tests/conftest.py
index 4b33e19c..67986621 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -47,26 +47,6 @@ def pytest_configure():
         ),
     )
 
-    try:
-        import oauth_provider  # NOQA
-        import oauth2  # NOQA
-    except ImportError:
-        pass
-    else:
-        settings.INSTALLED_APPS += (
-            'oauth_provider',
-        )
-
-    try:
-        import provider  # NOQA
-    except ImportError:
-        pass
-    else:
-        settings.INSTALLED_APPS += (
-            'provider',
-            'provider.oauth2',
-        )
-
     # guardian is optional
     try:
         import guardian  # NOQA
diff --git a/tests/settings.py b/tests/settings.py
index 91c9ed09..6a01669c 100644
--- a/tests/settings.py
+++ b/tests/settings.py
@@ -101,27 +101,6 @@ INSTALLED_APPS = (
     'tests.users',
 )
 
-# OAuth is optional and won't work if there is no oauth_provider & oauth2
-try:
-    import oauth_provider  # NOQA
-    import oauth2  # NOQA
-except ImportError:
-    pass
-else:
-    INSTALLED_APPS += (
-        'oauth_provider',
-    )
-
-try:
-    import provider  # NOQA
-except ImportError:
-    pass
-else:
-    INSTALLED_APPS += (
-        'provider',
-        'provider.oauth2',
-    )
-
 # guardian is optional
 try:
     import guardian  # NOQA
diff --git a/tests/test_authentication.py b/tests/test_authentication.py
index 32041f9c..ece6eff5 100644
--- a/tests/test_authentication.py
+++ b/tests/test_authentication.py
@@ -3,8 +3,7 @@ from django.conf.urls import patterns, url, include
 from django.contrib.auth.models import User
 from django.http import HttpResponse
 from django.test import TestCase
-from django.utils import six, unittest
-from django.utils.http import urlencode
+from django.utils import six
 from rest_framework import HTTP_HEADER_ENCODING
 from rest_framework import exceptions
 from rest_framework import permissions
@@ -16,17 +15,11 @@ from rest_framework.authentication import (
     TokenAuthentication,
     BasicAuthentication,
     SessionAuthentication,
-    OAuthAuthentication,
-    OAuth2Authentication
 )
 from rest_framework.authtoken.models import Token
-from rest_framework.compat import oauth2_provider, oauth2_provider_scope
-from rest_framework.compat import oauth, oauth_provider
 from rest_framework.test import APIRequestFactory, APIClient
 from rest_framework.views import APIView
 import base64
-import time
-import datetime
 
 factory = APIRequestFactory()
 
@@ -50,37 +43,10 @@ urlpatterns = patterns(
     (r'^basic/$', MockView.as_view(authentication_classes=[BasicAuthentication])),
     (r'^token/$', MockView.as_view(authentication_classes=[TokenAuthentication])),
     (r'^auth-token/$', 'rest_framework.authtoken.views.obtain_auth_token'),
-    (r'^oauth/$', MockView.as_view(authentication_classes=[OAuthAuthentication])),
-    (
-        r'^oauth-with-scope/$',
-        MockView.as_view(
-            authentication_classes=[OAuthAuthentication],
-            permission_classes=[permissions.TokenHasReadWriteScope]
-        )
-    ),
     url(r'^auth/', include('rest_framework.urls', namespace='rest_framework'))
 )
 
 
-class OAuth2AuthenticationDebug(OAuth2Authentication):
-    allow_query_params_token = True
-
-if oauth2_provider is not None:
-    urlpatterns += patterns(
-        '',
-        url(r'^oauth2/', include('provider.oauth2.urls', namespace='oauth2')),
-        url(r'^oauth2-test/$', MockView.as_view(authentication_classes=[OAuth2Authentication])),
-        url(r'^oauth2-test-debug/$', MockView.as_view(authentication_classes=[OAuth2AuthenticationDebug])),
-        url(
-            r'^oauth2-with-scope-test/$',
-            MockView.as_view(
-                authentication_classes=[OAuth2Authentication],
-                permission_classes=[permissions.TokenHasReadWriteScope]
-            )
-        )
-    )
-
-
 class BasicAuthTests(TestCase):
     """Basic authentication"""
     urls = 'tests.test_authentication'
@@ -276,400 +242,6 @@ class IncorrectCredentialsTests(TestCase):
         self.assertEqual(response.data, {'detail': 'Bad credentials'})
 
 
-class OAuthTests(TestCase):
-    """OAuth 1.0a authentication"""
-    urls = 'tests.test_authentication'
-
-    def setUp(self):
-        # these imports are here because oauth is optional and hiding them in try..except block or compat
-        # could obscure problems if something breaks
-        from oauth_provider.models import Consumer, Scope
-        from oauth_provider.models import Token as OAuthToken
-        from oauth_provider import consts
-
-        self.consts = consts
-
-        self.csrf_client = APIClient(enforce_csrf_checks=True)
-        self.username = 'john'
-        self.email = 'lennon@thebeatles.com'
-        self.password = 'password'
-        self.user = User.objects.create_user(self.username, self.email, self.password)
-
-        self.CONSUMER_KEY = 'consumer_key'
-        self.CONSUMER_SECRET = 'consumer_secret'
-        self.TOKEN_KEY = "token_key"
-        self.TOKEN_SECRET = "token_secret"
-
-        self.consumer = Consumer.objects.create(
-            key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET,
-            name='example', user=self.user, status=self.consts.ACCEPTED
-        )
-
-        self.scope = Scope.objects.create(name="resource name", url="api/")
-        self.token = OAuthToken.objects.create(
-            user=self.user, consumer=self.consumer, scope=self.scope,
-            token_type=OAuthToken.ACCESS, key=self.TOKEN_KEY, secret=self.TOKEN_SECRET,
-            is_approved=True
-        )
-
-    def _create_authorization_header(self):
-        params = {
-            'oauth_version': "1.0",
-            'oauth_nonce': oauth.generate_nonce(),
-            'oauth_timestamp': int(time.time()),
-            'oauth_token': self.token.key,
-            'oauth_consumer_key': self.consumer.key
-        }
-
-        req = oauth.Request(method="GET", url="http://example.com", parameters=params)
-
-        signature_method = oauth.SignatureMethod_PLAINTEXT()
-        req.sign_request(signature_method, self.consumer, self.token)
-
-        return req.to_header()["Authorization"]
-
-    def _create_authorization_url_parameters(self):
-        params = {
-            'oauth_version': "1.0",
-            'oauth_nonce': oauth.generate_nonce(),
-            'oauth_timestamp': int(time.time()),
-            'oauth_token': self.token.key,
-            'oauth_consumer_key': self.consumer.key
-        }
-
-        req = oauth.Request(method="GET", url="http://example.com", parameters=params)
-
-        signature_method = oauth.SignatureMethod_PLAINTEXT()
-        req.sign_request(signature_method, self.consumer, self.token)
-        return dict(req)
-
-    @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed')
-    @unittest.skipUnless(oauth, 'oauth2 not installed')
-    def test_post_form_passing_oauth(self):
-        """Ensure POSTing form over OAuth with correct credentials passes and does not require CSRF"""
-        auth = self._create_authorization_header()
-        response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth)
-        self.assertEqual(response.status_code, 200)
-
-    @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed')
-    @unittest.skipUnless(oauth, 'oauth2 not installed')
-    def test_post_form_repeated_nonce_failing_oauth(self):
-        """Ensure POSTing form over OAuth with repeated auth (same nonces and timestamp) credentials fails"""
-        auth = self._create_authorization_header()
-        response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth)
-        self.assertEqual(response.status_code, 200)
-
-        # simulate reply attack auth header containes already used (nonce, timestamp) pair
-        response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth)
-        self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN))
-
-    @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed')
-    @unittest.skipUnless(oauth, 'oauth2 not installed')
-    def test_post_form_token_removed_failing_oauth(self):
-        """Ensure POSTing when there is no OAuth access token in db fails"""
-        self.token.delete()
-        auth = self._create_authorization_header()
-        response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth)
-        self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN))
-
-    @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed')
-    @unittest.skipUnless(oauth, 'oauth2 not installed')
-    def test_post_form_consumer_status_not_accepted_failing_oauth(self):
-        """Ensure POSTing when consumer status is anything other than ACCEPTED fails"""
-        for consumer_status in (self.consts.CANCELED, self.consts.PENDING, self.consts.REJECTED):
-            self.consumer.status = consumer_status
-            self.consumer.save()
-
-            auth = self._create_authorization_header()
-            response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth)
-            self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN))
-
-    @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed')
-    @unittest.skipUnless(oauth, 'oauth2 not installed')
-    def test_post_form_with_request_token_failing_oauth(self):
-        """Ensure POSTing with unauthorized request token instead of access token fails"""
-        self.token.token_type = self.token.REQUEST
-        self.token.save()
-
-        auth = self._create_authorization_header()
-        response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth)
-        self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN))
-
-    @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed')
-    @unittest.skipUnless(oauth, 'oauth2 not installed')
-    def test_post_form_with_urlencoded_parameters(self):
-        """Ensure POSTing with x-www-form-urlencoded auth parameters passes"""
-        params = self._create_authorization_url_parameters()
-        auth = self._create_authorization_header()
-        response = self.csrf_client.post('/oauth/', params, HTTP_AUTHORIZATION=auth)
-        self.assertEqual(response.status_code, 200)
-
-    @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed')
-    @unittest.skipUnless(oauth, 'oauth2 not installed')
-    def test_get_form_with_url_parameters(self):
-        """Ensure GETing with auth in url parameters passes"""
-        params = self._create_authorization_url_parameters()
-        response = self.csrf_client.get('/oauth/', params)
-        self.assertEqual(response.status_code, 200)
-
-    @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed')
-    @unittest.skipUnless(oauth, 'oauth2 not installed')
-    def test_post_hmac_sha1_signature_passes(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': 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, 200)
-
-    @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed')
-    @unittest.skipUnless(oauth, 'oauth2 not installed')
-    def test_get_form_with_readonly_resource_passing_auth(self):
-        """Ensure POSTing with a readonly scope instead of a write scope fails"""
-        read_only_access_token = self.token
-        read_only_access_token.scope.is_readonly = True
-        read_only_access_token.scope.save()
-        params = self._create_authorization_url_parameters()
-        response = self.csrf_client.get('/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_post_form_with_readonly_resource_failing_auth(self):
-        """Ensure POSTing with a readonly resource instead of a write scope fails"""
-        read_only_access_token = self.token
-        read_only_access_token.scope.is_readonly = True
-        read_only_access_token.scope.save()
-        params = self._create_authorization_url_parameters()
-        response = self.csrf_client.post('/oauth-with-scope/', params)
-        self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN))
-
-    @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed')
-    @unittest.skipUnless(oauth, 'oauth2 not installed')
-    def test_post_form_with_write_resource_passing_auth(self):
-        """Ensure POSTing with a write resource succeed"""
-        read_write_access_token = self.token
-        read_write_access_token.scope.is_readonly = False
-        read_write_access_token.scope.save()
-        params = self._create_authorization_url_parameters()
-        auth = self._create_authorization_header()
-        response = self.csrf_client.post('/oauth-with-scope/', params, HTTP_AUTHORIZATION=auth)
-        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"""
-    urls = 'tests.test_authentication'
-
-    def setUp(self):
-        self.csrf_client = APIClient(enforce_csrf_checks=True)
-        self.username = 'john'
-        self.email = 'lennon@thebeatles.com'
-        self.password = 'password'
-        self.user = User.objects.create_user(self.username, self.email, self.password)
-
-        self.CLIENT_ID = 'client_key'
-        self.CLIENT_SECRET = 'client_secret'
-        self.ACCESS_TOKEN = "access_token"
-        self.REFRESH_TOKEN = "refresh_token"
-
-        self.oauth2_client = oauth2_provider.oauth2.models.Client.objects.create(
-            client_id=self.CLIENT_ID,
-            client_secret=self.CLIENT_SECRET,
-            redirect_uri='',
-            client_type=0,
-            name='example',
-            user=None,
-        )
-
-        self.access_token = oauth2_provider.oauth2.models.AccessToken.objects.create(
-            token=self.ACCESS_TOKEN,
-            client=self.oauth2_client,
-            user=self.user,
-        )
-        self.refresh_token = oauth2_provider.oauth2.models.RefreshToken.objects.create(
-            user=self.user,
-            access_token=self.access_token,
-            client=self.oauth2_client
-        )
-
-    def _create_authorization_header(self, token=None):
-        return "Bearer {0}".format(token or self.access_token.token)
-
-    @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
-    def test_get_form_with_wrong_authorization_header_token_type_failing(self):
-        """Ensure that a wrong token type lead to the correct HTTP error status code"""
-        auth = "Wrong token-type-obsviously"
-        response = self.csrf_client.get('/oauth2-test/', {}, HTTP_AUTHORIZATION=auth)
-        self.assertEqual(response.status_code, 401)
-        response = self.csrf_client.get('/oauth2-test/', HTTP_AUTHORIZATION=auth)
-        self.assertEqual(response.status_code, 401)
-
-    @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
-    def test_get_form_with_wrong_authorization_header_token_format_failing(self):
-        """Ensure that a wrong token format lead to the correct HTTP error status code"""
-        auth = "Bearer wrong token format"
-        response = self.csrf_client.get('/oauth2-test/', {}, HTTP_AUTHORIZATION=auth)
-        self.assertEqual(response.status_code, 401)
-        response = self.csrf_client.get('/oauth2-test/', HTTP_AUTHORIZATION=auth)
-        self.assertEqual(response.status_code, 401)
-
-    @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
-    def test_get_form_with_wrong_authorization_header_token_failing(self):
-        """Ensure that a wrong token lead to the correct HTTP error status code"""
-        auth = "Bearer wrong-token"
-        response = self.csrf_client.get('/oauth2-test/', {}, HTTP_AUTHORIZATION=auth)
-        self.assertEqual(response.status_code, 401)
-        response = self.csrf_client.get('/oauth2-test/', HTTP_AUTHORIZATION=auth)
-        self.assertEqual(response.status_code, 401)
-
-    @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
-    def test_get_form_with_wrong_authorization_header_token_missing(self):
-        """Ensure that a missing token lead to the correct HTTP error status code"""
-        auth = "Bearer"
-        response = self.csrf_client.get('/oauth2-test/', {}, HTTP_AUTHORIZATION=auth)
-        self.assertEqual(response.status_code, 401)
-        response = self.csrf_client.get('/oauth2-test/', HTTP_AUTHORIZATION=auth)
-        self.assertEqual(response.status_code, 401)
-
-    @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
-    def test_get_form_passing_auth(self):
-        """Ensure GETing form over OAuth with correct client credentials succeed"""
-        auth = self._create_authorization_header()
-        response = self.csrf_client.get('/oauth2-test/', HTTP_AUTHORIZATION=auth)
-        self.assertEqual(response.status_code, 200)
-
-    @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
-    def test_post_form_passing_auth_url_transport(self):
-        """Ensure GETing form over OAuth with correct client credentials in form data succeed"""
-        response = self.csrf_client.post(
-            '/oauth2-test/',
-            data={'access_token': self.access_token.token}
-        )
-        self.assertEqual(response.status_code, 200)
-
-    @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
-    def test_get_form_passing_auth_url_transport(self):
-        """Ensure GETing form over OAuth with correct client credentials in query succeed when DEBUG is True"""
-        query = urlencode({'access_token': self.access_token.token})
-        response = self.csrf_client.get('/oauth2-test-debug/?%s' % query)
-        self.assertEqual(response.status_code, 200)
-
-    @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
-    def test_get_form_failing_auth_url_transport(self):
-        """Ensure GETing form over OAuth with correct client credentials in query fails when DEBUG is False"""
-        query = urlencode({'access_token': self.access_token.token})
-        response = self.csrf_client.get('/oauth2-test/?%s' % query)
-        self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN))
-
-    @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
-    def test_post_form_passing_auth(self):
-        """Ensure POSTing form over OAuth with correct credentials passes and does not require CSRF"""
-        auth = self._create_authorization_header()
-        response = self.csrf_client.post('/oauth2-test/', HTTP_AUTHORIZATION=auth)
-        self.assertEqual(response.status_code, 200)
-
-    @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
-    def test_post_form_token_removed_failing_auth(self):
-        """Ensure POSTing when there is no OAuth access token in db fails"""
-        self.access_token.delete()
-        auth = self._create_authorization_header()
-        response = self.csrf_client.post('/oauth2-test/', HTTP_AUTHORIZATION=auth)
-        self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN))
-
-    @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
-    def test_post_form_with_refresh_token_failing_auth(self):
-        """Ensure POSTing with refresh token instead of access token fails"""
-        auth = self._create_authorization_header(token=self.refresh_token.token)
-        response = self.csrf_client.post('/oauth2-test/', HTTP_AUTHORIZATION=auth)
-        self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN))
-
-    @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
-    def test_post_form_with_expired_access_token_failing_auth(self):
-        """Ensure POSTing with expired access token fails with an 'Invalid token' error"""
-        self.access_token.expires = datetime.datetime.now() - datetime.timedelta(seconds=10)  # 10 seconds late
-        self.access_token.save()
-        auth = self._create_authorization_header()
-        response = self.csrf_client.post('/oauth2-test/', HTTP_AUTHORIZATION=auth)
-        self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN))
-        self.assertIn('Invalid token', response.content)
-
-    @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
-    def test_post_form_with_invalid_scope_failing_auth(self):
-        """Ensure POSTing with a readonly scope instead of a write scope fails"""
-        read_only_access_token = self.access_token
-        read_only_access_token.scope = oauth2_provider_scope.SCOPE_NAME_DICT['read']
-        read_only_access_token.save()
-        auth = self._create_authorization_header(token=read_only_access_token.token)
-        response = self.csrf_client.get('/oauth2-with-scope-test/', HTTP_AUTHORIZATION=auth)
-        self.assertEqual(response.status_code, 200)
-        response = self.csrf_client.post('/oauth2-with-scope-test/', HTTP_AUTHORIZATION=auth)
-        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-
-    @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
-    def test_post_form_with_valid_scope_passing_auth(self):
-        """Ensure POSTing with a write scope succeed"""
-        read_write_access_token = self.access_token
-        read_write_access_token.scope = oauth2_provider_scope.SCOPE_NAME_DICT['write']
-        read_write_access_token.save()
-        auth = self._create_authorization_header(token=read_write_access_token.token)
-        response = self.csrf_client.post('/oauth2-with-scope-test/', HTTP_AUTHORIZATION=auth)
-        self.assertEqual(response.status_code, 200)
-
-
 class FailingAuthAccessedInRenderer(TestCase):
     def setUp(self):
         class AuthAccessingRenderer(renderers.BaseRenderer):
-- 
cgit v1.2.3
From 731c8421afe3093a78cdabb9c3cc28fa52cd1c8e Mon Sep 17 00:00:00 2001
From: José Padilla
Date: Sat, 29 Nov 2014 14:43:05 -0400
Subject: Remove YAML support from core
---
 tests/test_renderers.py    | 57 ++++------------------------------------------
 tests/test_templatetags.py | 13 +----------
 2 files changed, 5 insertions(+), 65 deletions(-)
(limited to 'tests')
diff --git a/tests/test_renderers.py b/tests/test_renderers.py
index 416d7f22..0603f800 100644
--- a/tests/test_renderers.py
+++ b/tests/test_renderers.py
@@ -9,12 +9,12 @@ from django.test import TestCase
 from django.utils import six, unittest
 from django.utils.translation import ugettext_lazy as _
 from rest_framework import status, permissions
-from rest_framework.compat import yaml, etree, StringIO, BytesIO
+from rest_framework.compat import etree, StringIO
 from rest_framework.response import Response
 from rest_framework.views import APIView
-from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
-    XMLRenderer, JSONPRenderer, BrowsableAPIRenderer
-from rest_framework.parsers import YAMLParser, XMLParser
+from rest_framework.renderers import BaseRenderer, JSONRenderer, XMLRenderer, \
+    JSONPRenderer, BrowsableAPIRenderer
+from rest_framework.parsers import XMLParser
 from rest_framework.settings import api_settings
 from rest_framework.test import APIRequestFactory
 from collections import MutableMapping
@@ -452,55 +452,6 @@ class JSONPRendererTests(TestCase):
         )
 
 
-if yaml:
-    _yaml_repr = 'foo: [bar, baz]\n'
-
-    class YAMLRendererTests(TestCase):
-        """
-        Tests specific to the YAML Renderer
-        """
-
-        def test_render(self):
-            """
-            Test basic YAML rendering.
-            """
-            obj = {'foo': ['bar', 'baz']}
-            renderer = YAMLRenderer()
-            content = renderer.render(obj, 'application/yaml')
-            self.assertEqual(content.decode('utf-8'), _yaml_repr)
-
-        def test_render_and_parse(self):
-            """
-            Test rendering and then parsing returns the original object.
-            IE obj -> render -> parse -> obj.
-            """
-            obj = {'foo': ['bar', 'baz']}
-
-            renderer = YAMLRenderer()
-            parser = YAMLParser()
-
-            content = renderer.render(obj, 'application/yaml')
-            data = parser.parse(BytesIO(content))
-            self.assertEqual(obj, data)
-
-        def test_render_decimal(self):
-            """
-            Test YAML decimal rendering.
-            """
-            renderer = YAMLRenderer()
-            content = renderer.render({'field': Decimal('111.2')}, 'application/yaml')
-            self.assertYAMLContains(content.decode('utf-8'), "field: '111.2'")
-
-        def assertYAMLContains(self, content, string):
-            self.assertTrue(string in content, '%r not in %r' % (string, content))
-
-        def test_proper_encoding(self):
-            obj = {'countries': ['United Kingdom', 'France', 'España']}
-            renderer = YAMLRenderer()
-            content = renderer.render(obj, 'application/yaml')
-            self.assertEqual(content.strip(), 'countries: [United Kingdom, France, España]'.encode('utf-8'))
-
-
 class XMLRendererTestCase(TestCase):
     """
     Tests specific to the XML Renderer
diff --git a/tests/test_templatetags.py b/tests/test_templatetags.py
index b04a937e..0cee91f1 100644
--- a/tests/test_templatetags.py
+++ b/tests/test_templatetags.py
@@ -54,7 +54,7 @@ class Issue1386Tests(TestCase):
 
 class URLizerTests(TestCase):
     """
-    Test if both JSON and YAML URLs are transformed into links well
+    Test if JSON URLs are transformed into links well
     """
     def _urlize_dict_check(self, data):
         """
@@ -73,14 +73,3 @@ class URLizerTests(TestCase):
         data['"foo_set": [\n    "http://api/foos/1/"\n], '] = \
             '"foo_set": [\n    "http://api/foos/1/"\n], '
         self._urlize_dict_check(data)
-
-    def test_yaml_with_url(self):
-        """
-        Test if YAML URLs are transformed into links well
-        """
-        data = {}
-        data['''{users: 'http://api/users/'}'''] = \
-            '''{users: 'http://api/users/'}'''
-        data['''foo_set: ['http://api/foos/1/']'''] = \
-            '''foo_set: ['http://api/foos/1/']'''
-        self._urlize_dict_check(data)
-- 
cgit v1.2.3
From fe745b96163282e492f17a6b003418b81350333f Mon Sep 17 00:00:00 2001
From: José Padilla
Date: Sat, 29 Nov 2014 14:55:33 -0400
Subject: Remove JSONP support from core
---
 tests/test_renderers.py | 58 +------------------------------------------------
 1 file changed, 1 insertion(+), 57 deletions(-)
(limited to 'tests')
diff --git a/tests/test_renderers.py b/tests/test_renderers.py
index 416d7f22..15f15dcd 100644
--- a/tests/test_renderers.py
+++ b/tests/test_renderers.py
@@ -13,7 +13,7 @@ from rest_framework.compat import yaml, etree, StringIO, BytesIO
 from rest_framework.response import Response
 from rest_framework.views import APIView
 from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
-    XMLRenderer, JSONPRenderer, BrowsableAPIRenderer
+    XMLRenderer, BrowsableAPIRenderer
 from rest_framework.parsers import YAMLParser, XMLParser
 from rest_framework.settings import api_settings
 from rest_framework.test import APIRequestFactory
@@ -106,8 +106,6 @@ urlpatterns = patterns(
     url(r'^.*\.(?P.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
     url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
     url(r'^cache$', MockGETView.as_view()),
-    url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderer_classes=[JSONRenderer, JSONPRenderer])),
-    url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderer_classes=[JSONPRenderer])),
     url(r'^parseerror$', MockPOSTView.as_view(renderer_classes=[JSONRenderer, BrowsableAPIRenderer])),
     url(r'^html$', HTMLView.as_view()),
     url(r'^html1$', HTMLView1.as_view()),
@@ -398,60 +396,6 @@ class AsciiJSONRendererTests(TestCase):
         self.assertEqual(content, '{"countries":["United Kingdom","France","Espa\\u00f1a"]}'.encode('utf-8'))
 
 
-class JSONPRendererTests(TestCase):
-    """
-    Tests specific to the JSONP Renderer
-    """
-
-    urls = 'tests.test_renderers'
-
-    def test_without_callback_with_json_renderer(self):
-        """
-        Test JSONP rendering with View JSON Renderer.
-        """
-        resp = self.client.get(
-            '/jsonp/jsonrenderer',
-            HTTP_ACCEPT='application/javascript'
-        )
-        self.assertEqual(resp.status_code, status.HTTP_200_OK)
-        self.assertEqual(resp['Content-Type'], 'application/javascript; charset=utf-8')
-        self.assertEqual(
-            resp.content,
-            ('callback(%s);' % _flat_repr).encode('ascii')
-        )
-
-    def test_without_callback_without_json_renderer(self):
-        """
-        Test JSONP rendering without View JSON Renderer.
-        """
-        resp = self.client.get(
-            '/jsonp/nojsonrenderer',
-            HTTP_ACCEPT='application/javascript'
-        )
-        self.assertEqual(resp.status_code, status.HTTP_200_OK)
-        self.assertEqual(resp['Content-Type'], 'application/javascript; charset=utf-8')
-        self.assertEqual(
-            resp.content,
-            ('callback(%s);' % _flat_repr).encode('ascii')
-        )
-
-    def test_with_callback(self):
-        """
-        Test JSONP rendering with callback function name.
-        """
-        callback_func = 'myjsonpcallback'
-        resp = self.client.get(
-            '/jsonp/nojsonrenderer?callback=' + callback_func,
-            HTTP_ACCEPT='application/javascript'
-        )
-        self.assertEqual(resp.status_code, status.HTTP_200_OK)
-        self.assertEqual(resp['Content-Type'], 'application/javascript; charset=utf-8')
-        self.assertEqual(
-            resp.content,
-            ('%s(%s);' % (callback_func, _flat_repr)).encode('ascii')
-        )
-
-
 if yaml:
     _yaml_repr = 'foo: [bar, baz]\n'
 
-- 
cgit v1.2.3
From 7f9dc736728baf92a3198a7f90bd302fff240373 Mon Sep 17 00:00:00 2001
From: José Padilla
Date: Sat, 29 Nov 2014 14:50:51 -0400
Subject: Remove XML support from core
---
 tests/test_parsers.py   |  62 +---------------------------
 tests/test_renderers.py | 107 ++----------------------------------------------
 2 files changed, 5 insertions(+), 164 deletions(-)
(limited to 'tests')
diff --git a/tests/test_parsers.py b/tests/test_parsers.py
index 3f2672df..32fb0595 100644
--- a/tests/test_parsers.py
+++ b/tests/test_parsers.py
@@ -1,15 +1,11 @@
 # -*- coding: utf-8 -*-
 
 from __future__ import unicode_literals
-from rest_framework.compat import StringIO
 from django import forms
 from django.core.files.uploadhandler import MemoryFileUploadHandler
 from django.test import TestCase
-from django.utils import unittest
-from rest_framework.compat import etree
+from rest_framework.compat import StringIO
 from rest_framework.parsers import FormParser, FileUploadParser
-from rest_framework.parsers import XMLParser
-import datetime
 
 
 class Form(forms.Form):
@@ -31,62 +27,6 @@ class TestFormParser(TestCase):
         self.assertEqual(Form(data).is_valid(), True)
 
 
-class TestXMLParser(TestCase):
-    def setUp(self):
-        self._input = StringIO(
-            ''
-            ''
-            '121.0'
-            'dasd'
-            ''
-            '2011-12-25 12:45:00'
-            ''
-        )
-        self._data = {
-            'field_a': 121,
-            'field_b': 'dasd',
-            'field_c': None,
-            'field_d': datetime.datetime(2011, 12, 25, 12, 45, 00)
-        }
-        self._complex_data_input = StringIO(
-            ''
-            ''
-            '2011-12-25 12:45:00'
-            ''
-            '1first'
-            '2second'
-            ''
-            'name'
-            ''
-        )
-        self._complex_data = {
-            "creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00),
-            "name": "name",
-            "sub_data_list": [
-                {
-                    "sub_id": 1,
-                    "sub_name": "first"
-                },
-                {
-                    "sub_id": 2,
-                    "sub_name": "second"
-                }
-            ]
-        }
-
-    @unittest.skipUnless(etree, 'defusedxml not installed')
-    def test_parse(self):
-        parser = XMLParser()
-        data = parser.parse(self._input)
-        self.assertEqual(data, self._data)
-
-    @unittest.skipUnless(etree, 'defusedxml not installed')
-    def test_complex_data_parse(self):
-        parser = XMLParser()
-        data = parser.parse(self._complex_data_input)
-        self.assertEqual(data, self._complex_data)
-
-
 class TestFileUploadParser(TestCase):
     def setUp(self):
         class MockRequest(object):
diff --git a/tests/test_renderers.py b/tests/test_renderers.py
index 416d7f22..1eec37dc 100644
--- a/tests/test_renderers.py
+++ b/tests/test_renderers.py
@@ -6,19 +6,18 @@ from django.conf.urls import patterns, url, include
 from django.core.cache import cache
 from django.db import models
 from django.test import TestCase
-from django.utils import six, unittest
+from django.utils import six
 from django.utils.translation import ugettext_lazy as _
 from rest_framework import status, permissions
-from rest_framework.compat import yaml, etree, StringIO, BytesIO
+from rest_framework.compat import yaml, BytesIO
 from rest_framework.response import Response
 from rest_framework.views import APIView
 from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
-    XMLRenderer, JSONPRenderer, BrowsableAPIRenderer
-from rest_framework.parsers import YAMLParser, XMLParser
+    JSONPRenderer, BrowsableAPIRenderer
+from rest_framework.parsers import YAMLParser
 from rest_framework.settings import api_settings
 from rest_framework.test import APIRequestFactory
 from collections import MutableMapping
-import datetime
 import json
 import pickle
 import re
@@ -501,104 +500,6 @@ if yaml:
             self.assertEqual(content.strip(), 'countries: [United Kingdom, France, España]'.encode('utf-8'))
 
 
-class XMLRendererTestCase(TestCase):
-    """
-    Tests specific to the XML Renderer
-    """
-
-    _complex_data = {
-        "creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00),
-        "name": "name",
-        "sub_data_list": [
-            {
-                "sub_id": 1,
-                "sub_name": "first"
-            },
-            {
-                "sub_id": 2,
-                "sub_name": "second"
-            }
-        ]
-    }
-
-    def test_render_string(self):
-        """
-        Test XML rendering.
-        """
-        renderer = XMLRenderer()
-        content = renderer.render({'field': 'astring'}, 'application/xml')
-        self.assertXMLContains(content, 'astring')
-
-    def test_render_integer(self):
-        """
-        Test XML rendering.
-        """
-        renderer = XMLRenderer()
-        content = renderer.render({'field': 111}, 'application/xml')
-        self.assertXMLContains(content, '111')
-
-    def test_render_datetime(self):
-        """
-        Test XML rendering.
-        """
-        renderer = XMLRenderer()
-        content = renderer.render({
-            'field': datetime.datetime(2011, 12, 25, 12, 45, 00)
-        }, 'application/xml')
-        self.assertXMLContains(content, '2011-12-25 12:45:00')
-
-    def test_render_float(self):
-        """
-        Test XML rendering.
-        """
-        renderer = XMLRenderer()
-        content = renderer.render({'field': 123.4}, 'application/xml')
-        self.assertXMLContains(content, '123.4')
-
-    def test_render_decimal(self):
-        """
-        Test XML rendering.
-        """
-        renderer = XMLRenderer()
-        content = renderer.render({'field': Decimal('111.2')}, 'application/xml')
-        self.assertXMLContains(content, '111.2')
-
-    def test_render_none(self):
-        """
-        Test XML rendering.
-        """
-        renderer = XMLRenderer()
-        content = renderer.render({'field': None}, 'application/xml')
-        self.assertXMLContains(content, '')
-
-    def test_render_complex_data(self):
-        """
-        Test XML rendering.
-        """
-        renderer = XMLRenderer()
-        content = renderer.render(self._complex_data, 'application/xml')
-        self.assertXMLContains(content, 'first')
-        self.assertXMLContains(content, 'second')
-
-    @unittest.skipUnless(etree, 'defusedxml not installed')
-    def test_render_and_parse_complex_data(self):
-        """
-        Test XML rendering.
-        """
-        renderer = XMLRenderer()
-        content = StringIO(renderer.render(self._complex_data, 'application/xml'))
-
-        parser = XMLParser()
-        complex_data_out = parser.parse(content)
-        error_msg = "complex data differs!IN:\n %s \n\n OUT:\n %s" % (repr(self._complex_data), repr(complex_data_out))
-        self.assertEqual(self._complex_data, complex_data_out, error_msg)
-
-    def assertXMLContains(self, xml, string):
-        self.assertTrue(xml.startswith('\n'))
-        self.assertTrue(xml.endswith(''))
-        self.assertTrue(string in xml, '%r not in %r' % (string, xml))
-
-
 # Tests for caching issue, #346
 class CacheRenderTest(TestCase):
     """
-- 
cgit v1.2.3
From 5e7c9687c7e11b6adfe2fc534eb0504e67ca9fc9 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 15 Dec 2014 09:13:02 +0000
Subject: First pass at serializer repr bug
---
 tests/test_serializer.py | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)
(limited to 'tests')
diff --git a/tests/test_serializer.py b/tests/test_serializer.py
index 56b39095..48fcc83b 100644
--- a/tests/test_serializer.py
+++ b/tests/test_serializer.py
@@ -1,3 +1,4 @@
+# coding: utf-8
 from __future__ import unicode_literals
 from rest_framework import serializers
 import pytest
@@ -197,3 +198,19 @@ class TestIncorrectlyConfigured:
             "The serializer field might be named incorrectly and not match any attribute or key on the `ExampleObject` instance.\n"
             "Original exception text was:"
         )
+
+
+class TestUnicodeRepr:
+    def test_unicode_repr(self):
+        class ExampleSerializer(serializers.Serializer):
+            example = serializers.CharField()
+
+        class ExampleObject:
+            def __init__(self):
+                self.example = '한국'
+            def __repr__(self):
+                return self.example.encode('utf8')
+
+        instance = ExampleObject()
+        serializer = ExampleSerializer(instance)
+        repr(serializer)
-- 
cgit v1.2.3
From 6e51e4f5cdec4f4580360a487d7bf5ebdef08709 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 16 Dec 2014 15:34:19 +0000
Subject: Versioning first pass
---
 tests/test_versioning.py | 104 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 104 insertions(+)
 create mode 100644 tests/test_versioning.py
(limited to 'tests')
diff --git a/tests/test_versioning.py b/tests/test_versioning.py
new file mode 100644
index 00000000..d90b29a1
--- /dev/null
+++ b/tests/test_versioning.py
@@ -0,0 +1,104 @@
+from django.conf.urls import url
+from rest_framework import versioning
+from rest_framework.decorators import APIView
+from rest_framework.response import Response
+from rest_framework.reverse import reverse
+from rest_framework.test import APIRequestFactory, APITestCase
+
+
+class RequestVersionView(APIView):
+    def get(self, request, *args, **kwargs):
+        return Response({'version': request.version})
+
+class ReverseView(APIView):
+    def get(self, request, *args, **kwargs):
+        return Response({'url': reverse('another', request=request)})
+
+
+factory = APIRequestFactory()
+
+mock_view = lambda request: None
+
+urlpatterns = [
+    url(r'^another/$', mock_view, name='another')
+]
+
+
+class TestRequestVersion:
+    def test_unversioned(self):
+        view = RequestVersionView.as_view()
+
+        request = factory.get('/endpoint/')
+        response = view(request)
+        assert response.data == {'version': None}
+
+    def test_query_param_versioning(self):
+        scheme = versioning.QueryParameterVersioning
+        view = RequestVersionView.as_view(versioning_class=scheme)
+
+        request = factory.get('/endpoint/?version=1.2.3')
+        response = view(request)
+        assert response.data == {'version': '1.2.3'}
+
+        request = factory.get('/endpoint/')
+        response = view(request)
+        assert response.data == {'version': None}
+
+    def test_host_name_versioning(self):
+        scheme = versioning.HostNameVersioning
+        view = RequestVersionView.as_view(versioning_class=scheme)
+
+        request = factory.get('/endpoint/', HTTP_HOST='v1.example.org')
+        response = view(request)
+        assert response.data == {'version': 'v1'}
+
+        request = factory.get('/endpoint/')
+        response = view(request)
+        assert response.data == {'version': None}
+
+    def test_accept_header_versioning(self):
+        scheme = versioning.AcceptHeaderVersioning
+        view = RequestVersionView.as_view(versioning_class=scheme)
+
+        request = factory.get('/endpoint/', HTTP_ACCEPT='application/json; version=1.2.3')
+        response = view(request)
+        assert response.data == {'version': '1.2.3'}
+
+        request = factory.get('/endpoint/', HTTP_ACCEPT='application/json')
+        response = view(request)
+        assert response.data == {'version': None}
+
+    def test_url_path_versioning(self):
+        scheme = versioning.URLPathVersioning
+        view = RequestVersionView.as_view(versioning_class=scheme)
+
+        request = factory.get('/1.2.3/endpoint/')
+        response = view(request, version='1.2.3')
+        assert response.data == {'version': '1.2.3'}
+
+        request = factory.get('/endpoint/')
+        response = view(request)
+        assert response.data == {'version': None}
+
+
+class TestURLReversing(APITestCase):
+    urls = 'tests.test_versioning'
+
+    def test_reverse_unversioned(self):
+        view = ReverseView.as_view()
+
+        request = factory.get('/endpoint/')
+        response = view(request)
+        assert response.data == {'url': 'http://testserver/another/'}
+
+    def test_reverse_host_name_versioning(self):
+        scheme = versioning.HostNameVersioning
+        view = ReverseView.as_view(versioning_class=scheme)
+
+        request = factory.get('/endpoint/', HTTP_HOST='v1.example.org')
+        response = view(request)
+        assert response.data == {'url': 'http://v1.example.org/another/'}
+
+        request = factory.get('/endpoint/')
+        response = view(request)
+        assert response.data == {'url': 'http://testserver/another/'}
-- 
cgit v1.2.3
From 4e91ec61339838426e246e20ef062c963a78c4e1 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 16 Dec 2014 16:14:08 +0000
Subject: Added NamespaceVersioning
---
 tests/test_versioning.py | 67 ++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 65 insertions(+), 2 deletions(-)
(limited to 'tests')
diff --git a/tests/test_versioning.py b/tests/test_versioning.py
index d90b29a1..eaac5dfb 100644
--- a/tests/test_versioning.py
+++ b/tests/test_versioning.py
@@ -1,4 +1,4 @@
-from django.conf.urls import url
+from django.conf.urls import include, url
 from rest_framework import versioning
 from rest_framework.decorators import APIView
 from rest_framework.response import Response
@@ -10,6 +10,7 @@ class RequestVersionView(APIView):
     def get(self, request, *args, **kwargs):
         return Response({'version': request.version})
 
+
 class ReverseView(APIView):
     def get(self, request, *args, **kwargs):
         return Response({'url': reverse('another', request=request)})
@@ -19,8 +20,14 @@ factory = APIRequestFactory()
 
 mock_view = lambda request: None
 
+included_patterns = [
+    url(r'^namespaced/$', mock_view, name='another'),
+]
+
 urlpatterns = [
-    url(r'^another/$', mock_view, name='another')
+    url(r'^v1/', include(included_patterns, namespace='v1')),
+    url(r'^another/$', mock_view, name='another'),
+    url(r'^(?P[^/]+)/another/$', mock_view, name='another')
 ]
 
 
@@ -80,6 +87,22 @@ class TestRequestVersion:
         response = view(request)
         assert response.data == {'version': None}
 
+    def test_namespace_versioning(self):
+        class FakeResolverMatch:
+            namespace = 'v1'
+
+        scheme = versioning.NamespaceVersioning
+        view = RequestVersionView.as_view(versioning_class=scheme)
+
+        request = factory.get('/v1/endpoint/')
+        request.resolver_match = FakeResolverMatch
+        response = view(request, version='v1')
+        assert response.data == {'version': 'v1'}
+
+        request = factory.get('/endpoint/')
+        response = view(request)
+        assert response.data == {'version': None}
+
 
 class TestURLReversing(APITestCase):
     urls = 'tests.test_versioning'
@@ -91,6 +114,18 @@ class TestURLReversing(APITestCase):
         response = view(request)
         assert response.data == {'url': 'http://testserver/another/'}
 
+    def test_reverse_query_param_versioning(self):
+        scheme = versioning.QueryParameterVersioning
+        view = ReverseView.as_view(versioning_class=scheme)
+
+        request = factory.get('/endpoint/?version=v1')
+        response = view(request)
+        assert response.data == {'url': 'http://testserver/another/?version=v1'}
+
+        request = factory.get('/endpoint/')
+        response = view(request)
+        assert response.data == {'url': 'http://testserver/another/'}
+
     def test_reverse_host_name_versioning(self):
         scheme = versioning.HostNameVersioning
         view = ReverseView.as_view(versioning_class=scheme)
@@ -102,3 +137,31 @@ class TestURLReversing(APITestCase):
         request = factory.get('/endpoint/')
         response = view(request)
         assert response.data == {'url': 'http://testserver/another/'}
+
+    def test_reverse_url_path_versioning(self):
+        scheme = versioning.URLPathVersioning
+        view = ReverseView.as_view(versioning_class=scheme)
+
+        request = factory.get('/v1/endpoint/')
+        response = view(request, version='v1')
+        assert response.data == {'url': 'http://testserver/v1/another/'}
+
+        request = factory.get('/endpoint/')
+        response = view(request)
+        assert response.data == {'url': 'http://testserver/another/'}
+
+    def test_namespace_versioning(self):
+        class FakeResolverMatch:
+            namespace = 'v1'
+
+        scheme = versioning.NamespaceVersioning
+        view = ReverseView.as_view(versioning_class=scheme)
+
+        request = factory.get('/v1/endpoint/')
+        request.resolver_match = FakeResolverMatch
+        response = view(request, version='v1')
+        assert response.data == {'url': 'http://testserver/v1/namespaced/'}
+
+        request = factory.get('/endpoint/')
+        response = view(request)
+        assert response.data == {'url': 'http://testserver/another/'}
-- 
cgit v1.2.3
From 05a6eaec8aebdca2248b9e1069a15769fd85a480 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Wed, 17 Dec 2014 12:41:46 +0000
Subject: More docs, plus 'ALLOWED_VERSIONS' setting.
---
 tests/test_versioning.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 58 insertions(+), 2 deletions(-)
(limited to 'tests')
diff --git a/tests/test_versioning.py b/tests/test_versioning.py
index eaac5dfb..c44f727d 100644
--- a/tests/test_versioning.py
+++ b/tests/test_versioning.py
@@ -1,5 +1,5 @@
 from django.conf.urls import include, url
-from rest_framework import versioning
+from rest_framework import status, versioning
 from rest_framework.decorators import APIView
 from rest_framework.response import Response
 from rest_framework.reverse import reverse
@@ -16,6 +16,16 @@ class ReverseView(APIView):
         return Response({'url': reverse('another', request=request)})
 
 
+class RequestInvalidVersionView(APIView):
+    def determine_version(self, request, *args, **kwargs):
+        scheme = self.versioning_class()
+        scheme.allowed_versions = ('v1', 'v2')
+        return (scheme.determine_version(request, *args, **kwargs), scheme)
+
+    def get(self, request, *args, **kwargs):
+        return Response({'version': request.version})
+
+
 factory = APIRequestFactory()
 
 mock_view = lambda request: None
@@ -150,7 +160,7 @@ class TestURLReversing(APITestCase):
         response = view(request)
         assert response.data == {'url': 'http://testserver/another/'}
 
-    def test_namespace_versioning(self):
+    def test_reverse_namespace_versioning(self):
         class FakeResolverMatch:
             namespace = 'v1'
 
@@ -165,3 +175,49 @@ class TestURLReversing(APITestCase):
         request = factory.get('/endpoint/')
         response = view(request)
         assert response.data == {'url': 'http://testserver/another/'}
+
+
+class TestInvalidVersion:
+    def test_invalid_query_param_versioning(self):
+        scheme = versioning.QueryParameterVersioning
+        view = RequestInvalidVersionView.as_view(versioning_class=scheme)
+
+        request = factory.get('/endpoint/?version=v3')
+        response = view(request)
+        assert response.status_code == status.HTTP_404_NOT_FOUND
+
+    def test_invalid_host_name_versioning(self):
+        scheme = versioning.HostNameVersioning
+        view = RequestInvalidVersionView.as_view(versioning_class=scheme)
+
+        request = factory.get('/endpoint/', HTTP_HOST='v3.example.org')
+        response = view(request)
+        assert response.status_code == status.HTTP_404_NOT_FOUND
+
+    def test_invalid_accept_header_versioning(self):
+        scheme = versioning.AcceptHeaderVersioning
+        view = RequestInvalidVersionView.as_view(versioning_class=scheme)
+
+        request = factory.get('/endpoint/', HTTP_ACCEPT='application/json; version=v3')
+        response = view(request)
+        assert response.status_code == status.HTTP_406_NOT_ACCEPTABLE
+
+    def test_invalid_url_path_versioning(self):
+        scheme = versioning.URLPathVersioning
+        view = RequestInvalidVersionView.as_view(versioning_class=scheme)
+
+        request = factory.get('/v3/endpoint/')
+        response = view(request, version='v3')
+        assert response.status_code == status.HTTP_404_NOT_FOUND
+
+    def test_invalid_namespace_versioning(self):
+        class FakeResolverMatch:
+            namespace = 'v3'
+
+        scheme = versioning.NamespaceVersioning
+        view = RequestInvalidVersionView.as_view(versioning_class=scheme)
+
+        request = factory.get('/v3/endpoint/')
+        request.resolver_match = FakeResolverMatch
+        response = view(request, version='v3')
+        assert response.status_code == status.HTTP_404_NOT_FOUND
-- 
cgit v1.2.3
From 6d907cde9a90aad76acb00482a1d70550bb95ccd Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 19 Dec 2014 12:18:40 +0000
Subject: get_field_names, get_default_field_names
---
 tests/test_model_serializer.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
(limited to 'tests')
diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py
index da79164a..5c56c8db 100644
--- a/tests/test_model_serializer.py
+++ b/tests/test_model_serializer.py
@@ -221,11 +221,11 @@ class TestRegularFieldMappings(TestCase):
                 model = RegularFieldsModel
                 fields = ('auto_field',)
 
-        with self.assertRaises(ImproperlyConfigured) as excinfo:
+        with self.assertRaises(AssertionError) as excinfo:
             TestSerializer().fields
         expected = (
-            'Field `missing` has been declared on serializer '
-            '`TestSerializer`, but is missing from `Meta.fields`.'
+            "The field 'missing' was declared on serializer TestSerializer, "
+            "but has not been included in the 'fields' option."
         )
         assert str(excinfo.exception) == expected
 
@@ -607,5 +607,5 @@ class TestSerializerMetaClass(TestCase):
         exception = result.exception
         self.assertEqual(
             str(exception),
-            "Cannot set both 'fields' and 'exclude'."
+            "Cannot set both 'fields' and 'exclude' options on serializer ExampleSerializer."
         )
-- 
cgit v1.2.3
From 2a1485e00943b8280245d19e1e1f8514b1ef18ea Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 19 Dec 2014 21:32:43 +0000
Subject: Final bits of docs for ModelSerializer fields API
---
 tests/test_model_serializer.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'tests')
diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py
index 5c56c8db..603faf47 100644
--- a/tests/test_model_serializer.py
+++ b/tests/test_model_serializer.py
@@ -206,7 +206,7 @@ class TestRegularFieldMappings(TestCase):
 
         with self.assertRaises(ImproperlyConfigured) as excinfo:
             TestSerializer().fields
-        expected = 'Field name `invalid` is not valid for model `ModelBase`.'
+        expected = 'Field name `invalid` is not valid for model `RegularFieldsModel`.'
         assert str(excinfo.exception) == expected
 
     def test_missing_field(self):
-- 
cgit v1.2.3
From 32506e20756c84677abb5ae49706446a0d250371 Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Wed, 31 Dec 2014 13:14:09 +0000
Subject: update expected error messages in tests
---
 tests/test_fields.py | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)
(limited to 'tests')
diff --git a/tests/test_fields.py b/tests/test_fields.py
index 04c721d3..61d39aff 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -640,8 +640,8 @@ class TestDateField(FieldValues):
         datetime.date(2001, 1, 1): datetime.date(2001, 1, 1),
     }
     invalid_inputs = {
-        'abc': ['Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]]'],
-        '2001-99-99': ['Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]]'],
+        'abc': ['Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]].'],
+        '2001-99-99': ['Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]].'],
         datetime.datetime(2001, 1, 1, 12, 00): ['Expected a date but got a datetime.'],
     }
     outputs = {
@@ -658,7 +658,7 @@ class TestCustomInputFormatDateField(FieldValues):
         '1 Jan 2001': datetime.date(2001, 1, 1),
     }
     invalid_inputs = {
-        '2001-01-01': ['Date has wrong format. Use one of these formats instead: DD [Jan-Dec] YYYY']
+        '2001-01-01': ['Date has wrong format. Use one of these formats instead: DD [Jan-Dec] YYYY.']
     }
     outputs = {}
     field = serializers.DateField(input_formats=['%d %b %Y'])
@@ -702,8 +702,8 @@ class TestDateTimeField(FieldValues):
         '2001-01-01T14:00+01:00' if (django.VERSION > (1, 4)) else '2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC())
     }
     invalid_inputs = {
-        'abc': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]'],
-        '2001-99-99T99:00': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]'],
+        'abc': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].'],
+        '2001-99-99T99:00': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].'],
         datetime.date(2001, 1, 1): ['Expected a datetime but got a date.'],
     }
     outputs = {
@@ -721,7 +721,7 @@ class TestCustomInputFormatDateTimeField(FieldValues):
         '1:35pm, 1 Jan 2001': datetime.datetime(2001, 1, 1, 13, 35, tzinfo=timezone.UTC()),
     }
     invalid_inputs = {
-        '2001-01-01T20:50': ['Datetime has wrong format. Use one of these formats instead: hh:mm[AM|PM], DD [Jan-Dec] YYYY']
+        '2001-01-01T20:50': ['Datetime has wrong format. Use one of these formats instead: hh:mm[AM|PM], DD [Jan-Dec] YYYY.']
     }
     outputs = {}
     field = serializers.DateTimeField(default_timezone=timezone.UTC(), input_formats=['%I:%M%p, %d %b %Y'])
@@ -773,8 +773,8 @@ class TestTimeField(FieldValues):
         datetime.time(13, 00): datetime.time(13, 00),
     }
     invalid_inputs = {
-        'abc': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]]'],
-        '99:99': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]]'],
+        'abc': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]].'],
+        '99:99': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]].'],
     }
     outputs = {
         datetime.time(13, 00): '13:00:00'
@@ -790,7 +790,7 @@ class TestCustomInputFormatTimeField(FieldValues):
         '1:00pm': datetime.time(13, 00),
     }
     invalid_inputs = {
-        '13:00': ['Time has wrong format. Use one of these formats instead: hh:mm[AM|PM]'],
+        '13:00': ['Time has wrong format. Use one of these formats instead: hh:mm[AM|PM].'],
     }
     outputs = {}
     field = serializers.TimeField(input_formats=['%I:%M%p'])
@@ -1028,7 +1028,7 @@ class TestListField(FieldValues):
         (['1', '2', '3'], [1, 2, 3])
     ]
     invalid_inputs = [
-        ('not a list', ['Expected a list of items but got type `str`']),
+        ('not a list', ['Expected a list of items but got type `str`.']),
         ([1, 2, 'error'], ['A valid integer is required.'])
     ]
     outputs = [
-- 
cgit v1.2.3
From 91e316f7810157474d6246cd0024bd7f7cc31ff7 Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Wed, 7 Jan 2015 12:46:23 +0000
Subject: prefer single quotes in source and double quotes in user visible
 strings; add some missing full stops to user visible strings
---
 tests/test_fields.py    | 2 +-
 tests/test_generics.py  | 6 +++---
 tests/test_relations.py | 2 +-
 3 files changed, 5 insertions(+), 5 deletions(-)
(limited to 'tests')
diff --git a/tests/test_fields.py b/tests/test_fields.py
index 61d39aff..5ecb9857 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -439,7 +439,7 @@ class TestSlugField(FieldValues):
         'slug-99': 'slug-99',
     }
     invalid_inputs = {
-        'slug 99': ["Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."]
+        'slug 99': ['Enter a valid "slug" consisting of letters, numbers, underscores or hyphens.']
     }
     outputs = {}
     field = serializers.SlugField()
diff --git a/tests/test_generics.py b/tests/test_generics.py
index 94023c30..fba8718f 100644
--- a/tests/test_generics.py
+++ b/tests/test_generics.py
@@ -117,7 +117,7 @@ class TestRootView(TestCase):
         with self.assertNumQueries(0):
             response = self.view(request).render()
         self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
-        self.assertEqual(response.data, {"detail": "Method 'PUT' not allowed."})
+        self.assertEqual(response.data, {"detail": 'Method "PUT" not allowed.'})
 
     def test_delete_root_view(self):
         """
@@ -127,7 +127,7 @@ class TestRootView(TestCase):
         with self.assertNumQueries(0):
             response = self.view(request).render()
         self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
-        self.assertEqual(response.data, {"detail": "Method 'DELETE' not allowed."})
+        self.assertEqual(response.data, {"detail": 'Method "DELETE" not allowed.'})
 
     def test_post_cannot_set_id(self):
         """
@@ -181,7 +181,7 @@ class TestInstanceView(TestCase):
         with self.assertNumQueries(0):
             response = self.view(request).render()
         self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
-        self.assertEqual(response.data, {"detail": "Method 'POST' not allowed."})
+        self.assertEqual(response.data, {"detail": 'Method "POST" not allowed.'})
 
     def test_put_instance_view(self):
         """
diff --git a/tests/test_relations.py b/tests/test_relations.py
index 62353dc2..08c92242 100644
--- a/tests/test_relations.py
+++ b/tests/test_relations.py
@@ -33,7 +33,7 @@ class TestPrimaryKeyRelatedField(APISimpleTestCase):
         with pytest.raises(serializers.ValidationError) as excinfo:
             self.field.to_internal_value(4)
         msg = excinfo.value.detail[0]
-        assert msg == "Invalid pk '4' - object does not exist."
+        assert msg == 'Invalid pk "4" - object does not exist.'
 
     def test_pk_related_lookup_invalid_type(self):
         with pytest.raises(serializers.ValidationError) as excinfo:
-- 
cgit v1.2.3
From 7f8d314101c4e6e059b00ac12658f0e1055da8f7 Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Thu, 8 Jan 2015 17:16:47 +0000
Subject: update tests to expect new error messages
---
 tests/test_fields.py                 | 18 +++++++++---------
 tests/test_serializer_bulk_update.py |  4 ++--
 2 files changed, 11 insertions(+), 11 deletions(-)
(limited to 'tests')
diff --git a/tests/test_fields.py b/tests/test_fields.py
index 5ecb9857..240827ee 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -338,7 +338,7 @@ class TestBooleanField(FieldValues):
         False: False,
     }
     invalid_inputs = {
-        'foo': ['`foo` is not a valid boolean.'],
+        'foo': ['"foo" is not a valid boolean.'],
         None: ['This field may not be null.']
     }
     outputs = {
@@ -368,7 +368,7 @@ class TestNullBooleanField(FieldValues):
         None: None
     }
     invalid_inputs = {
-        'foo': ['`foo` is not a valid boolean.'],
+        'foo': ['"foo" is not a valid boolean.'],
     }
     outputs = {
         'true': True,
@@ -832,7 +832,7 @@ class TestChoiceField(FieldValues):
         'good': 'good',
     }
     invalid_inputs = {
-        'amazing': ['`amazing` is not a valid choice.']
+        'amazing': ['"amazing" is not a valid choice.']
     }
     outputs = {
         'good': 'good',
@@ -872,8 +872,8 @@ class TestChoiceFieldWithType(FieldValues):
         3: 3,
     }
     invalid_inputs = {
-        5: ['`5` is not a valid choice.'],
-        'abc': ['`abc` is not a valid choice.']
+        5: ['"5" is not a valid choice.'],
+        'abc': ['"abc" is not a valid choice.']
     }
     outputs = {
         '1': 1,
@@ -899,7 +899,7 @@ class TestChoiceFieldWithListChoices(FieldValues):
         'good': 'good',
     }
     invalid_inputs = {
-        'awful': ['`awful` is not a valid choice.']
+        'awful': ['"awful" is not a valid choice.']
     }
     outputs = {
         'good': 'good'
@@ -917,8 +917,8 @@ class TestMultipleChoiceField(FieldValues):
         ('aircon', 'manual'): set(['aircon', 'manual']),
     }
     invalid_inputs = {
-        'abc': ['Expected a list of items but got type `str`.'],
-        ('aircon', 'incorrect'): ['`incorrect` is not a valid choice.']
+        'abc': ['Expected a list of items but got type "str".'],
+        ('aircon', 'incorrect'): ['"incorrect" is not a valid choice.']
     }
     outputs = [
         (['aircon', 'manual'], set(['aircon', 'manual']))
@@ -1028,7 +1028,7 @@ class TestListField(FieldValues):
         (['1', '2', '3'], [1, 2, 3])
     ]
     invalid_inputs = [
-        ('not a list', ['Expected a list of items but got type `str`.']),
+        ('not a list', ['Expected a list of items but got type "str".']),
         ([1, 2, 'error'], ['A valid integer is required.'])
     ]
     outputs = [
diff --git a/tests/test_serializer_bulk_update.py b/tests/test_serializer_bulk_update.py
index fb881a75..bc955b2e 100644
--- a/tests/test_serializer_bulk_update.py
+++ b/tests/test_serializer_bulk_update.py
@@ -101,7 +101,7 @@ class BulkCreateSerializerTests(TestCase):
         serializer = self.BookSerializer(data=data, many=True)
         self.assertEqual(serializer.is_valid(), False)
 
-        expected_errors = {'non_field_errors': ['Expected a list of items but got type `int`.']}
+        expected_errors = {'non_field_errors': ['Expected a list of items but got type "int".']}
 
         self.assertEqual(serializer.errors, expected_errors)
 
@@ -118,6 +118,6 @@ class BulkCreateSerializerTests(TestCase):
         serializer = self.BookSerializer(data=data, many=True)
         self.assertEqual(serializer.is_valid(), False)
 
-        expected_errors = {'non_field_errors': ['Expected a list of items but got type `dict`.']}
+        expected_errors = {'non_field_errors': ['Expected a list of items but got type "dict".']}
 
         self.assertEqual(serializer.errors, expected_errors)
-- 
cgit v1.2.3
From 73feaf6299827607eab94ce96b77b73671880626 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 9 Jan 2015 15:30:36 +0000
Subject: First pass at 3.1 pagination API
---
 tests/test_pagination.py | 216 +----------------------------------------------
 1 file changed, 1 insertion(+), 215 deletions(-)
(limited to 'tests')
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
index 1fd9cf9c..d410cd5e 100644
--- a/tests/test_pagination.py
+++ b/tests/test_pagination.py
@@ -1,10 +1,9 @@
 from __future__ import unicode_literals
 import datetime
 from decimal import Decimal
-from django.core.paginator import Paginator
 from django.test import TestCase
 from django.utils import unittest
-from rest_framework import generics, serializers, status, pagination, filters
+from rest_framework import generics, serializers, status, filters
 from rest_framework.compat import django_filters
 from rest_framework.test import APIRequestFactory
 from .models import BasicModel, FilterableItem
@@ -238,45 +237,6 @@ class IntegrationTestPaginationAndFiltering(TestCase):
         self.assertEqual(response.data['previous'], None)
 
 
-class PassOnContextPaginationSerializer(pagination.PaginationSerializer):
-    class Meta:
-        object_serializer_class = serializers.Serializer
-
-
-class UnitTestPagination(TestCase):
-    """
-    Unit tests for pagination of primitive objects.
-    """
-
-    def setUp(self):
-        self.objects = [char * 3 for char in 'abcdefghijklmnopqrstuvwxyz']
-        paginator = Paginator(self.objects, 10)
-        self.first_page = paginator.page(1)
-        self.last_page = paginator.page(3)
-
-    def test_native_pagination(self):
-        serializer = pagination.PaginationSerializer(self.first_page)
-        self.assertEqual(serializer.data['count'], 26)
-        self.assertEqual(serializer.data['next'], '?page=2')
-        self.assertEqual(serializer.data['previous'], None)
-        self.assertEqual(serializer.data['results'], self.objects[:10])
-
-        serializer = pagination.PaginationSerializer(self.last_page)
-        self.assertEqual(serializer.data['count'], 26)
-        self.assertEqual(serializer.data['next'], None)
-        self.assertEqual(serializer.data['previous'], '?page=2')
-        self.assertEqual(serializer.data['results'], self.objects[20:])
-
-    def test_context_available_in_result(self):
-        """
-        Ensure context gets passed through to the object serializer.
-        """
-        serializer = PassOnContextPaginationSerializer(self.first_page, context={'foo': 'bar'})
-        serializer.data
-        results = serializer.fields[serializer.results_field]
-        self.assertEqual(serializer.context, results.context)
-
-
 class TestUnpaginated(TestCase):
     """
     Tests for list views without pagination.
@@ -377,177 +337,3 @@ class TestMaxPaginateByParam(TestCase):
         request = factory.get('/')
         response = self.view(request).render()
         self.assertEqual(response.data['results'], self.data[:3])
-
-
-# Tests for context in pagination serializers
-
-class CustomField(serializers.ReadOnlyField):
-    def to_native(self, value):
-        if 'view' not in self.context:
-            raise RuntimeError("context isn't getting passed into custom field")
-        return "value"
-
-
-class BasicModelSerializer(serializers.Serializer):
-    text = CustomField()
-
-    def to_native(self, value):
-        if 'view' not in self.context:
-            raise RuntimeError("context isn't getting passed into serializer")
-        return super(BasicSerializer, self).to_native(value)
-
-
-class TestContextPassedToCustomField(TestCase):
-    def setUp(self):
-        BasicModel.objects.create(text='ala ma kota')
-
-    def test_with_pagination(self):
-        class ListView(generics.ListCreateAPIView):
-            queryset = BasicModel.objects.all()
-            serializer_class = BasicModelSerializer
-            paginate_by = 1
-
-        self.view = ListView.as_view()
-        request = factory.get('/')
-        response = self.view(request).render()
-
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-
-
-# Tests for custom pagination serializers
-
-class LinksSerializer(serializers.Serializer):
-    next = pagination.NextPageField(source='*')
-    prev = pagination.PreviousPageField(source='*')
-
-
-class CustomPaginationSerializer(pagination.BasePaginationSerializer):
-    links = LinksSerializer(source='*')  # Takes the page object as the source
-    total_results = serializers.ReadOnlyField(source='paginator.count')
-
-    results_field = 'objects'
-
-
-class CustomFooSerializer(serializers.Serializer):
-    foo = serializers.CharField()
-
-
-class CustomFooPaginationSerializer(pagination.PaginationSerializer):
-    class Meta:
-        object_serializer_class = CustomFooSerializer
-
-
-class TestCustomPaginationSerializer(TestCase):
-    def setUp(self):
-        objects = ['john', 'paul', 'george', 'ringo']
-        paginator = Paginator(objects, 2)
-        self.page = paginator.page(1)
-
-    def test_custom_pagination_serializer(self):
-        request = APIRequestFactory().get('/foobar')
-        serializer = CustomPaginationSerializer(
-            instance=self.page,
-            context={'request': request}
-        )
-        expected = {
-            'links': {
-                'next': 'http://testserver/foobar?page=2',
-                'prev': None
-            },
-            'total_results': 4,
-            'objects': ['john', 'paul']
-        }
-        self.assertEqual(serializer.data, expected)
-
-    def test_custom_pagination_serializer_with_custom_object_serializer(self):
-        objects = [
-            {'foo': 'bar'},
-            {'foo': 'spam'}
-        ]
-        paginator = Paginator(objects, 1)
-        page = paginator.page(1)
-        serializer = CustomFooPaginationSerializer(page)
-        serializer.data
-
-
-class NonIntegerPage(object):
-
-    def __init__(self, paginator, object_list, prev_token, token, next_token):
-        self.paginator = paginator
-        self.object_list = object_list
-        self.prev_token = prev_token
-        self.token = token
-        self.next_token = next_token
-
-    def has_next(self):
-        return not not self.next_token
-
-    def next_page_number(self):
-        return self.next_token
-
-    def has_previous(self):
-        return not not self.prev_token
-
-    def previous_page_number(self):
-        return self.prev_token
-
-
-class NonIntegerPaginator(object):
-
-    def __init__(self, object_list, per_page):
-        self.object_list = object_list
-        self.per_page = per_page
-
-    def count(self):
-        # pretend like we don't know how many pages we have
-        return None
-
-    def page(self, token=None):
-        if token:
-            try:
-                first = self.object_list.index(token)
-            except ValueError:
-                first = 0
-        else:
-            first = 0
-        n = len(self.object_list)
-        last = min(first + self.per_page, n)
-        prev_token = self.object_list[last - (2 * self.per_page)] if first else None
-        next_token = self.object_list[last] if last < n else None
-        return NonIntegerPage(self, self.object_list[first:last], prev_token, token, next_token)
-
-
-class TestNonIntegerPagination(TestCase):
-    def test_custom_pagination_serializer(self):
-        objects = ['john', 'paul', 'george', 'ringo']
-        paginator = NonIntegerPaginator(objects, 2)
-
-        request = APIRequestFactory().get('/foobar')
-        serializer = CustomPaginationSerializer(
-            instance=paginator.page(),
-            context={'request': request}
-        )
-        expected = {
-            'links': {
-                'next': 'http://testserver/foobar?page={0}'.format(objects[2]),
-                'prev': None
-            },
-            'total_results': None,
-            'objects': objects[:2]
-        }
-        self.assertEqual(serializer.data, expected)
-
-        request = APIRequestFactory().get('/foobar')
-        serializer = CustomPaginationSerializer(
-            instance=paginator.page('george'),
-            context={'request': request}
-        )
-        expected = {
-            'links': {
-                'next': None,
-                'prev': 'http://testserver/foobar?page={0}'.format(objects[0]),
-            },
-            'total_results': None,
-            'objects': objects[2:]
-        }
-        self.assertEqual(serializer.data, expected)
-- 
cgit v1.2.3
From 53edd37df5aa0ac29dbe7824db2e33da1d901f98 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 15 Jan 2015 21:07:05 +0000
Subject: Tests for LimitOffsetPagination
---
 tests/test_pagination.py | 117 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 116 insertions(+), 1 deletion(-)
(limited to 'tests')
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
index d410cd5e..32fe7a66 100644
--- a/tests/test_pagination.py
+++ b/tests/test_pagination.py
@@ -3,8 +3,10 @@ import datetime
 from decimal import Decimal
 from django.test import TestCase
 from django.utils import unittest
-from rest_framework import generics, serializers, status, filters
+from rest_framework import generics, pagination, serializers, status, filters
 from rest_framework.compat import django_filters
+from rest_framework.request import Request
+from rest_framework.pagination import PageLink, PAGE_BREAK
 from rest_framework.test import APIRequestFactory
 from .models import BasicModel, FilterableItem
 
@@ -337,3 +339,116 @@ class TestMaxPaginateByParam(TestCase):
         request = factory.get('/')
         response = self.view(request).render()
         self.assertEqual(response.data['results'], self.data[:3])
+
+
+class TestLimitOffset:
+    def setup(self):
+        self.pagination = pagination.LimitOffsetPagination()
+        self.queryset = range(1, 101)
+
+    def paginate_queryset(self, request):
+        return self.pagination.paginate_queryset(self.queryset, request)
+
+    def get_paginated_content(self, queryset):
+        response = self.pagination.get_paginated_response(queryset)
+        return response.data
+
+    def get_html_context(self):
+        return self.pagination.get_html_context()
+
+    def test_no_offset(self):
+        request = Request(factory.get('/', {'limit': 5}))
+        queryset = self.paginate_queryset(request)
+        content = self.get_paginated_content(queryset)
+        context = self.get_html_context()
+        assert queryset == [1, 2, 3, 4, 5]
+        assert content == {
+            'results': [1, 2, 3, 4, 5],
+            'previous': None,
+            'next': 'http://testserver/?limit=5&offset=5',
+            'count': 100
+        }
+        assert context == {
+            'previous_url': None,
+            'next_url': 'http://testserver/?limit=5&offset=5',
+            'page_links': [
+                PageLink('http://testserver/?limit=5', 1, True, False),
+                PageLink('http://testserver/?limit=5&offset=5', 2, False, False),
+                PageLink('http://testserver/?limit=5&offset=10', 3, False, False),
+                PAGE_BREAK,
+                PageLink('http://testserver/?limit=5&offset=95', 20, False, False),
+            ]
+        }
+
+    def test_first_offset(self):
+        request = Request(factory.get('/', {'limit': 5, 'offset': 5}))
+        queryset = self.paginate_queryset(request)
+        content = self.get_paginated_content(queryset)
+        context = self.get_html_context()
+        assert queryset == [6, 7, 8, 9, 10]
+        assert content == {
+            'results': [6, 7, 8, 9, 10],
+            'previous': 'http://testserver/?limit=5',
+            'next': 'http://testserver/?limit=5&offset=10',
+            'count': 100
+        }
+        assert context == {
+            'previous_url': 'http://testserver/?limit=5',
+            'next_url': 'http://testserver/?limit=5&offset=10',
+            'page_links': [
+                PageLink('http://testserver/?limit=5', 1, False, False),
+                PageLink('http://testserver/?limit=5&offset=5', 2, True, False),
+                PageLink('http://testserver/?limit=5&offset=10', 3, False, False),
+                PAGE_BREAK,
+                PageLink('http://testserver/?limit=5&offset=95', 20, False, False),
+            ]
+        }
+
+    def test_middle_offset(self):
+        request = Request(factory.get('/', {'limit': 5, 'offset': 10}))
+        queryset = self.paginate_queryset(request)
+        content = self.get_paginated_content(queryset)
+        context = self.get_html_context()
+        assert queryset == [11, 12, 13, 14, 15]
+        assert content == {
+            'results': [11, 12, 13, 14, 15],
+            'previous': 'http://testserver/?limit=5&offset=5',
+            'next': 'http://testserver/?limit=5&offset=15',
+            'count': 100
+        }
+        assert context == {
+            'previous_url': 'http://testserver/?limit=5&offset=5',
+            'next_url': 'http://testserver/?limit=5&offset=15',
+            'page_links': [
+                PageLink('http://testserver/?limit=5', 1, False, False),
+                PageLink('http://testserver/?limit=5&offset=5', 2, False, False),
+                PageLink('http://testserver/?limit=5&offset=10', 3, True, False),
+                PageLink('http://testserver/?limit=5&offset=15', 4, False, False),
+                PAGE_BREAK,
+                PageLink('http://testserver/?limit=5&offset=95', 20, False, False),
+            ]
+        }
+
+    def test_ending_offset(self):
+        request = Request(factory.get('/', {'limit': 5, 'offset': 95}))
+        queryset = self.paginate_queryset(request)
+        content = self.get_paginated_content(queryset)
+        context = self.get_html_context()
+        assert queryset == [96, 97, 98, 99, 100]
+        assert content == {
+            'results': [96, 97, 98, 99, 100],
+            'previous': 'http://testserver/?limit=5&offset=90',
+            'next': None,
+            'count': 100
+        }
+        assert context == {
+            'previous_url': 'http://testserver/?limit=5&offset=90',
+            'next_url': None,
+            'page_links': [
+                PageLink('http://testserver/?limit=5', 1, False, False),
+                PAGE_BREAK,
+                PageLink('http://testserver/?limit=5&offset=85', 18, False, False),
+                PageLink('http://testserver/?limit=5&offset=90', 19, False, False),
+                PageLink('http://testserver/?limit=5&offset=95', 20, True, False),
+            ]
+        }
-- 
cgit v1.2.3
From 50db8c092ab51a5eb94e2bb495c317097fceeb59 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 16 Jan 2015 16:55:28 +0000
Subject: Minor test cleanup
---
 tests/test_metadata.py | 30 +++++++++++++++++-------------
 1 file changed, 17 insertions(+), 13 deletions(-)
(limited to 'tests')
diff --git a/tests/test_metadata.py b/tests/test_metadata.py
index 5ff59c72..972a896a 100644
--- a/tests/test_metadata.py
+++ b/tests/test_metadata.py
@@ -1,9 +1,7 @@
 from __future__ import unicode_literals
-
-from rest_framework import exceptions, serializers, views
+from rest_framework import exceptions, serializers, status, views
 from rest_framework.request import Request
 from rest_framework.test import APIRequestFactory
-import pytest
 
 request = Request(APIRequestFactory().options('/'))
 
@@ -17,7 +15,8 @@ class TestMetadata:
             """Example view."""
             pass
 
-        response = ExampleView().options(request=request)
+        view = ExampleView.as_view()
+        response = view(request=request)
         expected = {
             'name': 'Example',
             'description': 'Example view.',
@@ -31,7 +30,7 @@ class TestMetadata:
                 'multipart/form-data'
             ]
         }
-        assert response.status_code == 200
+        assert response.status_code == status.HTTP_200_OK
         assert response.data == expected
 
     def test_none_metadata(self):
@@ -42,8 +41,10 @@ class TestMetadata:
         class ExampleView(views.APIView):
             metadata_class = None
 
-        with pytest.raises(exceptions.MethodNotAllowed):
-            ExampleView().options(request=request)
+        view = ExampleView.as_view()
+        response = view(request=request)
+        assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
+        assert response.data == {'detail': 'Method "OPTIONS" not allowed.'}
 
     def test_actions(self):
         """
@@ -63,7 +64,8 @@ class TestMetadata:
             def get_serializer(self):
                 return ExampleSerializer()
 
-        response = ExampleView().options(request=request)
+        view = ExampleView.as_view()
+        response = view(request=request)
         expected = {
             'name': 'Example',
             'description': 'Example view.',
@@ -104,7 +106,7 @@ class TestMetadata:
                 }
             }
         }
-        assert response.status_code == 200
+        assert response.status_code == status.HTTP_200_OK
         assert response.data == expected
 
     def test_global_permissions(self):
@@ -132,8 +134,9 @@ class TestMetadata:
                 if request.method == 'POST':
                     raise exceptions.PermissionDenied()
 
-        response = ExampleView().options(request=request)
-        assert response.status_code == 200
+        view = ExampleView.as_view()
+        response = view(request=request)
+        assert response.status_code == status.HTTP_200_OK
         assert list(response.data['actions'].keys()) == ['PUT']
 
     def test_object_permissions(self):
@@ -161,6 +164,7 @@ class TestMetadata:
                 if self.request.method == 'PUT':
                     raise exceptions.PermissionDenied()
 
-        response = ExampleView().options(request=request)
-        assert response.status_code == 200
+        view = ExampleView.as_view()
+        response = view(request=request)
+        assert response.status_code == status.HTTP_200_OK
         assert list(response.data['actions'].keys()) == ['POST']
-- 
cgit v1.2.3
From 8b0f25aa0a91cb7b56f9ce4dde4330fe5daaad9b Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 16 Jan 2015 16:55:46 +0000
Subject: More pagination tests & cleanup
---
 tests/test_pagination.py | 629 ++++++++++++++++++++++++-----------------------
 1 file changed, 325 insertions(+), 304 deletions(-)
(limited to 'tests')
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
index 32fe7a66..b3436b35 100644
--- a/tests/test_pagination.py
+++ b/tests/test_pagination.py
@@ -1,349 +1,270 @@
 from __future__ import unicode_literals
-import datetime
-from decimal import Decimal
-from django.test import TestCase
-from django.utils import unittest
-from rest_framework import generics, pagination, serializers, status, filters
-from rest_framework.compat import django_filters
+from rest_framework import exceptions, generics, pagination, serializers, status, filters
 from rest_framework.request import Request
 from rest_framework.pagination import PageLink, PAGE_BREAK
 from rest_framework.test import APIRequestFactory
-from .models import BasicModel, FilterableItem
+import pytest
 
 factory = APIRequestFactory()
 
 
-# Helper function to split arguments out of an url
-def split_arguments_from_url(url):
-    if '?' not in url:
-        return url
-
-    path, args = url.split('?')
-    args = dict(r.split('=') for r in args.split('&'))
-    return path, args
+class TestPaginationIntegration:
+    """
+    Integration tests.
+    """
 
+    def setup(self):
+        class PassThroughSerializer(serializers.BaseSerializer):
+            def to_representation(self, item):
+                return item
 
-class BasicSerializer(serializers.ModelSerializer):
-    class Meta:
-        model = BasicModel
+        class EvenItemsOnly(filters.BaseFilterBackend):
+            def filter_queryset(self, request, queryset, view):
+                return [item for item in queryset if item % 2 == 0]
+
+        class BasicPagination(pagination.PageNumberPagination):
+            paginate_by = 5
+            paginate_by_param = 'page_size'
+            max_paginate_by = 20
+
+        self.view = generics.ListAPIView.as_view(
+            serializer_class=PassThroughSerializer,
+            queryset=range(1, 101),
+            filter_backends=[EvenItemsOnly],
+            pagination_class=BasicPagination
+        )
+
+    def test_filtered_items_are_paginated(self):
+        request = factory.get('/', {'page': 2})
+        response = self.view(request)
+        assert response.status_code == status.HTTP_200_OK
+        assert response.data == {
+            'results': [12, 14, 16, 18, 20],
+            'previous': 'http://testserver/',
+            'next': 'http://testserver/?page=3',
+            'count': 50
+        }
 
+    def test_setting_page_size(self):
+        """
+        When 'paginate_by_param' is set, the client may choose a page size.
+        """
+        request = factory.get('/', {'page_size': 10})
+        response = self.view(request)
+        assert response.status_code == status.HTTP_200_OK
+        assert response.data == {
+            'results': [2, 4, 6, 8, 10, 12, 14, 16, 18, 20],
+            'previous': None,
+            'next': 'http://testserver/?page=2&page_size=10',
+            'count': 50
+        }
 
-class FilterableItemSerializer(serializers.ModelSerializer):
-    class Meta:
-        model = FilterableItem
+    def test_setting_page_size_over_maximum(self):
+        """
+        When page_size parameter exceeds maxiumum allowable,
+        then it should be capped to the maxiumum.
+        """
+        request = factory.get('/', {'page_size': 1000})
+        response = self.view(request)
+        assert response.status_code == status.HTTP_200_OK
+        assert response.data == {
+            'results': [
+                2, 4, 6, 8, 10, 12, 14, 16, 18, 20,
+                22, 24, 26, 28, 30, 32, 34, 36, 38, 40
+            ],
+            'previous': None,
+            'next': 'http://testserver/?page=2&page_size=1000',
+            'count': 50
+        }
 
+    def test_additional_query_params_are_preserved(self):
+        request = factory.get('/', {'page': 2, 'filter': 'even'})
+        response = self.view(request)
+        assert response.status_code == status.HTTP_200_OK
+        assert response.data == {
+            'results': [12, 14, 16, 18, 20],
+            'previous': 'http://testserver/?filter=even',
+            'next': 'http://testserver/?filter=even&page=3',
+            'count': 50
+        }
 
-class RootView(generics.ListCreateAPIView):
-    """
-    Example description for OPTIONS.
-    """
-    queryset = BasicModel.objects.all()
-    serializer_class = BasicSerializer
-    paginate_by = 10
+    def test_404_not_found_for_invalid_page(self):
+        request = factory.get('/', {'page': 'invalid'})
+        response = self.view(request)
+        assert response.status_code == status.HTTP_404_NOT_FOUND
+        assert response.data == {
+            'detail': 'Invalid page "invalid": That page number is not an integer.'
+        }
 
 
-class DefaultPageSizeKwargView(generics.ListAPIView):
+class TestPaginationDisabledIntegration:
     """
-    View for testing default paginate_by_param usage
+    Integration tests for disabled pagination.
     """
-    queryset = BasicModel.objects.all()
-    serializer_class = BasicSerializer
-
 
-class PaginateByParamView(generics.ListAPIView):
-    """
-    View for testing custom paginate_by_param usage
-    """
-    queryset = BasicModel.objects.all()
-    serializer_class = BasicSerializer
-    paginate_by_param = 'page_size'
+    def setup(self):
+        class PassThroughSerializer(serializers.BaseSerializer):
+            def to_representation(self, item):
+                return item
+
+        self.view = generics.ListAPIView.as_view(
+            serializer_class=PassThroughSerializer,
+            queryset=range(1, 101),
+            pagination_class=None
+        )
+
+    def test_unpaginated_list(self):
+        request = factory.get('/', {'page': 2})
+        response = self.view(request)
+        assert response.status_code == status.HTTP_200_OK
+        assert response.data == range(1, 101)
 
 
-class MaxPaginateByView(generics.ListAPIView):
+class TestDeprecatedStylePagination:
     """
-    View for testing custom max_paginate_by usage
+    Integration tests for deprecated style of setting pagination
+    attributes on the view.
     """
-    queryset = BasicModel.objects.all()
-    serializer_class = BasicSerializer
-    paginate_by = 3
-    max_paginate_by = 5
-    paginate_by_param = 'page_size'
-
 
-class IntegrationTestPagination(TestCase):
-    """
-    Integration tests for paginated list views.
-    """
+    def setup(self):
+        class PassThroughSerializer(serializers.BaseSerializer):
+            def to_representation(self, item):
+                return item
 
-    def setUp(self):
-        """
-        Create 26 BasicModel instances.
-        """
-        for char in 'abcdefghijklmnopqrstuvwxyz':
-            BasicModel(text=char * 3).save()
-        self.objects = BasicModel.objects
-        self.data = [
-            {'id': obj.id, 'text': obj.text}
-            for obj in self.objects.all()
-        ]
-        self.view = RootView.as_view()
-
-    def test_get_paginated_root_view(self):
-        """
-        GET requests to paginated ListCreateAPIView should return paginated results.
-        """
-        request = factory.get('/')
-        # Note: Database queries are a `SELECT COUNT`, and `SELECT `
-        with self.assertNumQueries(2):
-            response = self.view(request).render()
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-        self.assertEqual(response.data['count'], 26)
-        self.assertEqual(response.data['results'], self.data[:10])
-        self.assertNotEqual(response.data['next'], None)
-        self.assertEqual(response.data['previous'], None)
-
-        request = factory.get(*split_arguments_from_url(response.data['next']))
-        with self.assertNumQueries(2):
-            response = self.view(request).render()
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-        self.assertEqual(response.data['count'], 26)
-        self.assertEqual(response.data['results'], self.data[10:20])
-        self.assertNotEqual(response.data['next'], None)
-        self.assertNotEqual(response.data['previous'], None)
-
-        request = factory.get(*split_arguments_from_url(response.data['next']))
-        with self.assertNumQueries(2):
-            response = self.view(request).render()
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-        self.assertEqual(response.data['count'], 26)
-        self.assertEqual(response.data['results'], self.data[20:])
-        self.assertEqual(response.data['next'], None)
-        self.assertNotEqual(response.data['previous'], None)
-
-
-class IntegrationTestPaginationAndFiltering(TestCase):
-
-    def setUp(self):
-        """
-        Create 50 FilterableItem instances.
-        """
-        base_data = ('a', Decimal('0.25'), datetime.date(2012, 10, 8))
-        for i in range(26):
-            text = chr(i + ord(base_data[0])) * 3  # Produces string 'aaa', 'bbb', etc.
-            decimal = base_data[1] + i
-            date = base_data[2] - datetime.timedelta(days=i * 2)
-            FilterableItem(text=text, decimal=decimal, date=date).save()
-
-        self.objects = FilterableItem.objects
-        self.data = [
-            {'id': obj.id, 'text': obj.text, 'decimal': str(obj.decimal), 'date': obj.date.isoformat()}
-            for obj in self.objects.all()
-        ]
-
-    @unittest.skipUnless(django_filters, 'django-filter not installed')
-    def test_get_django_filter_paginated_filtered_root_view(self):
-        """
-        GET requests to paginated filtered ListCreateAPIView should return
-        paginated results. The next and previous links should preserve the
-        filtered parameters.
-        """
-        class DecimalFilter(django_filters.FilterSet):
-            decimal = django_filters.NumberFilter(lookup_type='lt')
-
-            class Meta:
-                model = FilterableItem
-                fields = ['text', 'decimal', 'date']
-
-        class FilterFieldsRootView(generics.ListCreateAPIView):
-            queryset = FilterableItem.objects.all()
-            serializer_class = FilterableItemSerializer
-            paginate_by = 10
-            filter_class = DecimalFilter
-            filter_backends = (filters.DjangoFilterBackend,)
-
-        view = FilterFieldsRootView.as_view()
-
-        EXPECTED_NUM_QUERIES = 2
-
-        request = factory.get('/', {'decimal': '15.20'})
-        with self.assertNumQueries(EXPECTED_NUM_QUERIES):
-            response = view(request).render()
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-        self.assertEqual(response.data['count'], 15)
-        self.assertEqual(response.data['results'], self.data[:10])
-        self.assertNotEqual(response.data['next'], None)
-        self.assertEqual(response.data['previous'], None)
-
-        request = factory.get(*split_arguments_from_url(response.data['next']))
-        with self.assertNumQueries(EXPECTED_NUM_QUERIES):
-            response = view(request).render()
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-        self.assertEqual(response.data['count'], 15)
-        self.assertEqual(response.data['results'], self.data[10:15])
-        self.assertEqual(response.data['next'], None)
-        self.assertNotEqual(response.data['previous'], None)
-
-        request = factory.get(*split_arguments_from_url(response.data['previous']))
-        with self.assertNumQueries(EXPECTED_NUM_QUERIES):
-            response = view(request).render()
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-        self.assertEqual(response.data['count'], 15)
-        self.assertEqual(response.data['results'], self.data[:10])
-        self.assertNotEqual(response.data['next'], None)
-        self.assertEqual(response.data['previous'], None)
-
-    def test_get_basic_paginated_filtered_root_view(self):
-        """
-        Same as `test_get_django_filter_paginated_filtered_root_view`,
-        except using a custom filter backend instead of the django-filter
-        backend,
-        """
+        class ExampleView(generics.ListAPIView):
+            serializer_class = PassThroughSerializer
+            queryset = range(1, 101)
+            pagination_class = pagination.PageNumberPagination
+            paginate_by = 20
+            page_query_param = 'page_number'
 
-        class DecimalFilterBackend(filters.BaseFilterBackend):
-            def filter_queryset(self, request, queryset, view):
-                return queryset.filter(decimal__lt=Decimal(request.GET['decimal']))
-
-        class BasicFilterFieldsRootView(generics.ListCreateAPIView):
-            queryset = FilterableItem.objects.all()
-            serializer_class = FilterableItemSerializer
-            paginate_by = 10
-            filter_backends = (DecimalFilterBackend,)
-
-        view = BasicFilterFieldsRootView.as_view()
-
-        request = factory.get('/', {'decimal': '15.20'})
-        with self.assertNumQueries(2):
-            response = view(request).render()
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-        self.assertEqual(response.data['count'], 15)
-        self.assertEqual(response.data['results'], self.data[:10])
-        self.assertNotEqual(response.data['next'], None)
-        self.assertEqual(response.data['previous'], None)
-
-        request = factory.get(*split_arguments_from_url(response.data['next']))
-        with self.assertNumQueries(2):
-            response = view(request).render()
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-        self.assertEqual(response.data['count'], 15)
-        self.assertEqual(response.data['results'], self.data[10:15])
-        self.assertEqual(response.data['next'], None)
-        self.assertNotEqual(response.data['previous'], None)
-
-        request = factory.get(*split_arguments_from_url(response.data['previous']))
-        with self.assertNumQueries(2):
-            response = view(request).render()
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-        self.assertEqual(response.data['count'], 15)
-        self.assertEqual(response.data['results'], self.data[:10])
-        self.assertNotEqual(response.data['next'], None)
-        self.assertEqual(response.data['previous'], None)
-
-
-class TestUnpaginated(TestCase):
-    """
-    Tests for list views without pagination.
-    """
+        self.view = ExampleView.as_view()
 
-    def setUp(self):
-        """
-        Create 13 BasicModel instances.
-        """
-        for i in range(13):
-            BasicModel(text=i).save()
-        self.objects = BasicModel.objects
-        self.data = [
-            {'id': obj.id, 'text': obj.text}
-            for obj in self.objects.all()
-        ]
-        self.view = DefaultPageSizeKwargView.as_view()
-
-    def test_unpaginated(self):
-        """
-        Tests the default page size for this view.
-        no page size --> no limit --> no meta data
-        """
-        request = factory.get('/')
+    def test_paginate_by_attribute_on_view(self):
+        request = factory.get('/?page_number=2')
         response = self.view(request)
-        self.assertEqual(response.data, self.data)
+        assert response.status_code == status.HTTP_200_OK
+        assert response.data == {
+            'results': [
+                21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
+                31, 32, 33, 34, 35, 36, 37, 38, 39, 40
+            ],
+            'previous': 'http://testserver/',
+            'next': 'http://testserver/?page_number=3',
+            'count': 100
+        }
 
 
-class TestCustomPaginateByParam(TestCase):
+class TestPageNumberPagination:
     """
-    Tests for list views with default page size kwarg
+    Unit tests for `pagination.PageNumberPagination`.
     """
 
-    def setUp(self):
-        """
-        Create 13 BasicModel instances.
-        """
-        for i in range(13):
-            BasicModel(text=i).save()
-        self.objects = BasicModel.objects
-        self.data = [
-            {'id': obj.id, 'text': obj.text}
-            for obj in self.objects.all()
-        ]
-        self.view = PaginateByParamView.as_view()
-
-    def test_default_page_size(self):
-        """
-        Tests the default page size for this view.
-        no page size --> no limit --> no meta data
-        """
-        request = factory.get('/')
-        response = self.view(request).render()
-        self.assertEqual(response.data, self.data)
+    def setup(self):
+        class ExamplePagination(pagination.PageNumberPagination):
+            paginate_by = 5
+        self.pagination = ExamplePagination()
+        self.queryset = range(1, 101)
 
-    def test_paginate_by_param(self):
-        """
-        If paginate_by_param is set, the new kwarg should limit per view requests.
-        """
-        request = factory.get('/', {'page_size': 5})
-        response = self.view(request).render()
-        self.assertEqual(response.data['count'], 13)
-        self.assertEqual(response.data['results'], self.data[:5])
+    def paginate_queryset(self, request):
+        return list(self.pagination.paginate_queryset(self.queryset, request))
 
+    def get_paginated_content(self, queryset):
+        response = self.pagination.get_paginated_response(queryset)
+        return response.data
 
-class TestMaxPaginateByParam(TestCase):
-    """
-    Tests for list views with max_paginate_by kwarg
-    """
+    def get_html_context(self):
+        return self.pagination.get_html_context()
 
-    def setUp(self):
-        """
-        Create 13 BasicModel instances.
-        """
-        for i in range(13):
-            BasicModel(text=i).save()
-        self.objects = BasicModel.objects
-        self.data = [
-            {'id': obj.id, 'text': obj.text}
-            for obj in self.objects.all()
-        ]
-        self.view = MaxPaginateByView.as_view()
-
-    def test_max_paginate_by(self):
-        """
-        If max_paginate_by is set, it should limit page size for the view.
-        """
-        request = factory.get('/', data={'page_size': 10})
-        response = self.view(request).render()
-        self.assertEqual(response.data['count'], 13)
-        self.assertEqual(response.data['results'], self.data[:5])
+    def test_no_page_number(self):
+        request = Request(factory.get('/'))
+        queryset = self.paginate_queryset(request)
+        content = self.get_paginated_content(queryset)
+        context = self.get_html_context()
+        assert queryset == [1, 2, 3, 4, 5]
+        assert content == {
+            'results': [1, 2, 3, 4, 5],
+            'previous': None,
+            'next': 'http://testserver/?page=2',
+            'count': 100
+        }
+        assert context == {
+            'previous_url': None,
+            'next_url': 'http://testserver/?page=2',
+            'page_links': [
+                PageLink('http://testserver/', 1, True, False),
+                PageLink('http://testserver/?page=2', 2, False, False),
+                PageLink('http://testserver/?page=3', 3, False, False),
+                PAGE_BREAK,
+                PageLink('http://testserver/?page=20', 20, False, False),
+            ]
+        }
+        assert self.pagination.display_page_controls
+        assert isinstance(self.pagination.to_html(), type(''))
 
-    def test_max_paginate_by_without_page_size_param(self):
-        """
-        If max_paginate_by is set, but client does not specifiy page_size,
-        standard `paginate_by` behavior should be used.
-        """
-        request = factory.get('/')
-        response = self.view(request).render()
-        self.assertEqual(response.data['results'], self.data[:3])
+    def test_second_page(self):
+        request = Request(factory.get('/', {'page': 2}))
+        queryset = self.paginate_queryset(request)
+        content = self.get_paginated_content(queryset)
+        context = self.get_html_context()
+        assert queryset == [6, 7, 8, 9, 10]
+        assert content == {
+            'results': [6, 7, 8, 9, 10],
+            'previous': 'http://testserver/',
+            'next': 'http://testserver/?page=3',
+            'count': 100
+        }
+        assert context == {
+            'previous_url': 'http://testserver/',
+            'next_url': 'http://testserver/?page=3',
+            'page_links': [
+                PageLink('http://testserver/', 1, False, False),
+                PageLink('http://testserver/?page=2', 2, True, False),
+                PageLink('http://testserver/?page=3', 3, False, False),
+                PAGE_BREAK,
+                PageLink('http://testserver/?page=20', 20, False, False),
+            ]
+        }
+
+    def test_last_page(self):
+        request = Request(factory.get('/', {'page': 'last'}))
+        queryset = self.paginate_queryset(request)
+        content = self.get_paginated_content(queryset)
+        context = self.get_html_context()
+        assert queryset == [96, 97, 98, 99, 100]
+        assert content == {
+            'results': [96, 97, 98, 99, 100],
+            'previous': 'http://testserver/?page=19',
+            'next': None,
+            'count': 100
+        }
+        assert context == {
+            'previous_url': 'http://testserver/?page=19',
+            'next_url': None,
+            'page_links': [
+                PageLink('http://testserver/', 1, False, False),
+                PAGE_BREAK,
+                PageLink('http://testserver/?page=18', 18, False, False),
+                PageLink('http://testserver/?page=19', 19, False, False),
+                PageLink('http://testserver/?page=20', 20, True, False),
+            ]
+        }
+
+    def test_invalid_page(self):
+        request = Request(factory.get('/', {'page': 'invalid'}))
+        with pytest.raises(exceptions.NotFound):
+            self.paginate_queryset(request)
 
 
 class TestLimitOffset:
+    """
+    Unit tests for `pagination.LimitOffsetPagination`.
+    """
+
     def setup(self):
-        self.pagination = pagination.LimitOffsetPagination()
+        class ExamplePagination(pagination.LimitOffsetPagination):
+            default_limit = 10
+        self.pagination = ExamplePagination()
         self.queryset = range(1, 101)
 
     def paginate_queryset(self, request):
@@ -379,6 +300,37 @@ class TestLimitOffset:
                 PageLink('http://testserver/?limit=5&offset=95', 20, False, False),
             ]
         }
+        assert self.pagination.display_page_controls
+        assert isinstance(self.pagination.to_html(), type(''))
+
+    def test_single_offset(self):
+        """
+        When the offset is not a multiple of the limit we get some edge cases:
+        * The first page should still be offset zero.
+        * We may end up displaying an extra page in the pagination control.
+        """
+        request = Request(factory.get('/', {'limit': 5, 'offset': 1}))
+        queryset = self.paginate_queryset(request)
+        content = self.get_paginated_content(queryset)
+        context = self.get_html_context()
+        assert queryset == [2, 3, 4, 5, 6]
+        assert content == {
+            'results': [2, 3, 4, 5, 6],
+            'previous': 'http://testserver/?limit=5',
+            'next': 'http://testserver/?limit=5&offset=6',
+            'count': 100
+        }
+        assert context == {
+            'previous_url': 'http://testserver/?limit=5',
+            'next_url': 'http://testserver/?limit=5&offset=6',
+            'page_links': [
+                PageLink('http://testserver/?limit=5', 1, False, False),
+                PageLink('http://testserver/?limit=5&offset=1', 2, True, False),
+                PageLink('http://testserver/?limit=5&offset=6', 3, False, False),
+                PAGE_BREAK,
+                PageLink('http://testserver/?limit=5&offset=96', 21, False, False),
+            ]
+        }
 
     def test_first_offset(self):
         request = Request(factory.get('/', {'limit': 5, 'offset': 5}))
@@ -452,3 +404,72 @@ class TestLimitOffset:
                 PageLink('http://testserver/?limit=5&offset=95', 20, True, False),
             ]
         }
+
+    def test_invalid_offset(self):
+        """
+        An invalid offset query param should be treated as 0.
+        """
+        request = Request(factory.get('/', {'limit': 5, 'offset': 'invalid'}))
+        queryset = self.paginate_queryset(request)
+        assert queryset == [1, 2, 3, 4, 5]
+
+    def test_invalid_limit(self):
+        """
+        An invalid limit query param should be ignored in favor of the default.
+        """
+        request = Request(factory.get('/', {'limit': 'invalid', 'offset': 0}))
+        queryset = self.paginate_queryset(request)
+        assert queryset == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
+
+def test_get_displayed_page_numbers():
+    """
+    Test our contextual page display function.
+
+    This determines which pages to display in a pagination control,
+    given the current page and the last page.
+    """
+    displayed_page_numbers = pagination._get_displayed_page_numbers
+
+    # At five pages or less, all pages are displayed, always.
+    assert displayed_page_numbers(1, 5) == [1, 2, 3, 4, 5]
+    assert displayed_page_numbers(2, 5) == [1, 2, 3, 4, 5]
+    assert displayed_page_numbers(3, 5) == [1, 2, 3, 4, 5]
+    assert displayed_page_numbers(4, 5) == [1, 2, 3, 4, 5]
+    assert displayed_page_numbers(5, 5) == [1, 2, 3, 4, 5]
+
+    # Between six and either pages we may have a single page break.
+    assert displayed_page_numbers(1, 6) == [1, 2, 3, None, 6]
+    assert displayed_page_numbers(2, 6) == [1, 2, 3, None, 6]
+    assert displayed_page_numbers(3, 6) == [1, 2, 3, 4, 5, 6]
+    assert displayed_page_numbers(4, 6) == [1, 2, 3, 4, 5, 6]
+    assert displayed_page_numbers(5, 6) == [1, None, 4, 5, 6]
+    assert displayed_page_numbers(6, 6) == [1, None, 4, 5, 6]
+
+    assert displayed_page_numbers(1, 7) == [1, 2, 3, None, 7]
+    assert displayed_page_numbers(2, 7) == [1, 2, 3, None, 7]
+    assert displayed_page_numbers(3, 7) == [1, 2, 3, 4, None, 7]
+    assert displayed_page_numbers(4, 7) == [1, 2, 3, 4, 5, 6, 7]
+    assert displayed_page_numbers(5, 7) == [1, None, 4, 5, 6, 7]
+    assert displayed_page_numbers(6, 7) == [1, None, 5, 6, 7]
+    assert displayed_page_numbers(7, 7) == [1, None, 5, 6, 7]
+
+    assert displayed_page_numbers(1, 8) == [1, 2, 3, None, 8]
+    assert displayed_page_numbers(2, 8) == [1, 2, 3, None, 8]
+    assert displayed_page_numbers(3, 8) == [1, 2, 3, 4, None, 8]
+    assert displayed_page_numbers(4, 8) == [1, 2, 3, 4, 5, None, 8]
+    assert displayed_page_numbers(5, 8) == [1, None, 4, 5, 6, 7, 8]
+    assert displayed_page_numbers(6, 8) == [1, None, 5, 6, 7, 8]
+    assert displayed_page_numbers(7, 8) == [1, None, 6, 7, 8]
+    assert displayed_page_numbers(8, 8) == [1, None, 6, 7, 8]
+
+    # At nine or more pages we may have two page breaks, one on each side.
+    assert displayed_page_numbers(1, 9) == [1, 2, 3, None, 9]
+    assert displayed_page_numbers(2, 9) == [1, 2, 3, None, 9]
+    assert displayed_page_numbers(3, 9) == [1, 2, 3, 4, None, 9]
+    assert displayed_page_numbers(4, 9) == [1, 2, 3, 4, 5, None, 9]
+    assert displayed_page_numbers(5, 9) == [1, None, 4, 5, 6, None, 9]
+    assert displayed_page_numbers(6, 9) == [1, None, 5, 6, 7, 8, 9]
+    assert displayed_page_numbers(7, 9) == [1, None, 6, 7, 8, 9]
+    assert displayed_page_numbers(8, 9) == [1, None, 7, 8, 9]
+    assert displayed_page_numbers(9, 9) == [1, None, 7, 8, 9]
-- 
cgit v1.2.3
From 86d2774cf30351fd4174e97501532056ed0d8f95 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 16 Jan 2015 20:30:46 +0000
Subject: Fix compat issues
---
 tests/test_pagination.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
(limited to 'tests')
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
index b3436b35..7cc92347 100644
--- a/tests/test_pagination.py
+++ b/tests/test_pagination.py
@@ -117,7 +117,7 @@ class TestPaginationDisabledIntegration:
         request = factory.get('/', {'page': 2})
         response = self.view(request)
         assert response.status_code == status.HTTP_200_OK
-        assert response.data == range(1, 101)
+        assert response.data == list(range(1, 101))
 
 
 class TestDeprecatedStylePagination:
@@ -268,7 +268,7 @@ class TestLimitOffset:
         self.queryset = range(1, 101)
 
     def paginate_queryset(self, request):
-        return self.pagination.paginate_queryset(self.queryset, request)
+        return list(self.pagination.paginate_queryset(self.queryset, request))
 
     def get_paginated_content(self, queryset):
         response = self.pagination.get_paginated_response(queryset)
-- 
cgit v1.2.3
From 4919492582547d227a22852ad2339fa73739cc94 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Sat, 17 Jan 2015 00:10:43 +0000
Subject: First pass at cursor pagination
---
 tests/test_pagination.py | 88 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 88 insertions(+)
(limited to 'tests')
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
index 7cc92347..7f18b446 100644
--- a/tests/test_pagination.py
+++ b/tests/test_pagination.py
@@ -422,6 +422,94 @@ class TestLimitOffset:
         assert queryset == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
 
 
+class TestCursorPagination:
+    """
+    Unit tests for `pagination.CursorPagination`.
+    """
+
+    def setup(self):
+        class MockObject(object):
+            def __init__(self, idx):
+                self.created = idx
+
+        class MockQuerySet(object):
+            def __init__(self, items):
+                self.items = items
+
+            def filter(self, created__gt):
+                return [
+                    item for item in self.items
+                    if item.created > int(created__gt)
+                ]
+
+            def __getitem__(self, sliced):
+                return self.items[sliced]
+
+        self.pagination = pagination.CursorPagination()
+        self.queryset = MockQuerySet(
+            [MockObject(idx) for idx in range(1, 21)]
+        )
+
+    def paginate_queryset(self, request):
+        return list(self.pagination.paginate_queryset(self.queryset, request))
+
+    # def get_paginated_content(self, queryset):
+    #     response = self.pagination.get_paginated_response(queryset)
+    #     return response.data
+
+    # def get_html_context(self):
+    #     return self.pagination.get_html_context()
+
+    def test_following_cursor(self):
+        request = Request(factory.get('/'))
+        queryset = self.paginate_queryset(request)
+        assert [item.created for item in queryset] == [1, 2, 3, 4, 5]
+
+        next_url = self.pagination.get_next_link()
+        assert next_url
+
+        request = Request(factory.get(next_url))
+        queryset = self.paginate_queryset(request)
+        assert [item.created for item in queryset] == [6, 7, 8, 9, 10]
+
+        next_url = self.pagination.get_next_link()
+        assert next_url
+
+        request = Request(factory.get(next_url))
+        queryset = self.paginate_queryset(request)
+        assert [item.created for item in queryset] == [11, 12, 13, 14, 15]
+
+        next_url = self.pagination.get_next_link()
+        assert next_url
+
+        request = Request(factory.get(next_url))
+        queryset = self.paginate_queryset(request)
+        assert [item.created for item in queryset] == [16, 17, 18, 19, 20]
+
+        next_url = self.pagination.get_next_link()
+        assert next_url is None
+
+        # assert content == {
+        #     'results': [1, 2, 3, 4, 5],
+        #     'previous': None,
+        #     'next': 'http://testserver/?limit=5&offset=5',
+        #     'count': 100
+        # }
+        # assert context == {
+        #     'previous_url': None,
+        #     'next_url': 'http://testserver/?limit=5&offset=5',
+        #     'page_links': [
+        #         PageLink('http://testserver/?limit=5', 1, True, False),
+        #         PageLink('http://testserver/?limit=5&offset=5', 2, False, False),
+        #         PageLink('http://testserver/?limit=5&offset=10', 3, False, False),
+        #         PAGE_BREAK,
+        #         PageLink('http://testserver/?limit=5&offset=95', 20, False, False),
+        #     ]
+        # }
+        # assert self.pagination.display_page_controls
+        # assert isinstance(self.pagination.to_html(), type(''))
+
+
 def test_get_displayed_page_numbers():
     """
     Test our contextual page display function.
-- 
cgit v1.2.3
From dbb684117f6fe0f9c34f98d5e914fc106090cdbc Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 19 Jan 2015 09:24:42 +0000
Subject: Add offset support for cursor pagination
---
 tests/test_pagination.py | 64 +++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 61 insertions(+), 3 deletions(-)
(limited to 'tests')
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
index 7f18b446..f04079a7 100644
--- a/tests/test_pagination.py
+++ b/tests/test_pagination.py
@@ -447,7 +447,7 @@ class TestCursorPagination:
 
         self.pagination = pagination.CursorPagination()
         self.queryset = MockQuerySet(
-            [MockObject(idx) for idx in range(1, 21)]
+            [MockObject(idx) for idx in range(1, 16)]
         )
 
     def paginate_queryset(self, request):
@@ -479,16 +479,74 @@ class TestCursorPagination:
         queryset = self.paginate_queryset(request)
         assert [item.created for item in queryset] == [11, 12, 13, 14, 15]
 
+        next_url = self.pagination.get_next_link()
+        assert next_url is None
+
+
+class TestCrazyCursorPagination:
+    """
+    Unit tests for `pagination.CursorPagination`.
+    """
+
+    def setup(self):
+        class MockObject(object):
+            def __init__(self, idx):
+                self.created = idx
+
+        class MockQuerySet(object):
+            def __init__(self, items):
+                self.items = items
+
+            def filter(self, created__gt):
+                return [
+                    item for item in self.items
+                    if item.created > int(created__gt)
+                ]
+
+            def __getitem__(self, sliced):
+                return self.items[sliced]
+
+        self.pagination = pagination.CursorPagination()
+        self.queryset = MockQuerySet([
+            MockObject(idx) for idx in [
+                1, 1, 1, 1, 1,
+                1, 1, 1, 1, 1,
+                1, 1, 2, 3, 4,
+                5, 6, 7, 8, 9
+            ]
+        ])
+
+    def paginate_queryset(self, request):
+        return list(self.pagination.paginate_queryset(self.queryset, request))
+
+    def test_following_cursor_identical_items(self):
+        request = Request(factory.get('/'))
+        queryset = self.paginate_queryset(request)
+        assert [item.created for item in queryset] == [1, 1, 1, 1, 1]
+
         next_url = self.pagination.get_next_link()
         assert next_url
 
         request = Request(factory.get(next_url))
         queryset = self.paginate_queryset(request)
-        assert [item.created for item in queryset] == [16, 17, 18, 19, 20]
+        assert [item.created for item in queryset] == [1, 1, 1, 1, 1]
 
         next_url = self.pagination.get_next_link()
-        assert next_url is None
+        assert next_url
+
+        request = Request(factory.get(next_url))
+        queryset = self.paginate_queryset(request)
+        assert [item.created for item in queryset] == [1, 1, 2, 3, 4]
+
+        next_url = self.pagination.get_next_link()
+        assert next_url
+
+        request = Request(factory.get(next_url))
+        queryset = self.paginate_queryset(request)
+        assert [item.created for item in queryset] == [5, 6, 7, 8, 9]
 
+        next_url = self.pagination.get_next_link()
+        assert next_url is None
         # assert content == {
         #     'results': [1, 2, 3, 4, 5],
         #     'previous': None,
-- 
cgit v1.2.3
From 4f3c3a06cfc0ea2dfbf46da2d98546664343ce93 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 19 Jan 2015 14:41:10 +0000
Subject: Drop trailing whitespace on indented JSON output. Closes #2429.
---
 tests/test_renderers.py | 24 +++++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)
(limited to 'tests')
diff --git a/tests/test_renderers.py b/tests/test_renderers.py
index 7b78f7ba..3e64d8fe 100644
--- a/tests/test_renderers.py
+++ b/tests/test_renderers.py
@@ -1,6 +1,5 @@
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
-
 from django.conf.urls import patterns, url, include
 from django.core.cache import cache
 from django.db import models
@@ -8,6 +7,7 @@ from django.test import TestCase
 from django.utils import six
 from django.utils.translation import ugettext_lazy as _
 from rest_framework import status, permissions
+from rest_framework.compat import OrderedDict
 from rest_framework.response import Response
 from rest_framework.views import APIView
 from rest_framework.renderers import BaseRenderer, JSONRenderer, BrowsableAPIRenderer
@@ -489,3 +489,25 @@ class CacheRenderTest(TestCase):
         cached_resp = cache.get(self.cache_key)
         self.assertIsInstance(cached_resp, Response)
         self.assertEqual(cached_resp.content, resp.content)
+
+
+class TestJSONIndentationStyles:
+    def test_indented(self):
+        renderer = JSONRenderer()
+        data = OrderedDict([('a', 1), ('b', 2)])
+        assert renderer.render(data) == b'{"a":1,"b":2}'
+
+    def test_compact(self):
+        renderer = JSONRenderer()
+        data = OrderedDict([('a', 1), ('b', 2)])
+        context = {'indent': 4}
+        assert (
+            renderer.render(data, renderer_context=context) ==
+            b'{\n    "a": 1,\n    "b": 2\n}'
+        )
+
+    def test_long_form(self):
+        renderer = JSONRenderer()
+        renderer.compact = False
+        data = OrderedDict([('a', 1), ('b', 2)])
+        assert renderer.render(data) == b'{"a": 1, "b": 2}'
-- 
cgit v1.2.3
From cae9528c54ea13863ea056d40168e8d8df68b276 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 22 Jan 2015 10:28:19 +0000
Subject: Add support for reverse cursors
---
 tests/test_pagination.py | 6 ++++++
 1 file changed, 6 insertions(+)
(limited to 'tests')
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
index f04079a7..47019671 100644
--- a/tests/test_pagination.py
+++ b/tests/test_pagination.py
@@ -442,6 +442,9 @@ class TestCursorPagination:
                     if item.created > int(created__gt)
                 ]
 
+            def order_by(self, ordering):
+                return self
+
             def __getitem__(self, sliced):
                 return self.items[sliced]
 
@@ -503,6 +506,9 @@ class TestCrazyCursorPagination:
                     if item.created > int(created__gt)
                 ]
 
+            def order_by(self, ordering):
+                return self
+
             def __getitem__(self, sliced):
                 return self.items[sliced]
 
-- 
cgit v1.2.3
From f1af603fb05fce236a4258e18df8af8888043247 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 22 Jan 2015 10:51:04 +0000
Subject: Tests for reverse pagination
---
 tests/test_pagination.py | 98 +++++++++++++++++++++++++++++++++++-------------
 1 file changed, 71 insertions(+), 27 deletions(-)
(limited to 'tests')
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
index 47019671..4907a080 100644
--- a/tests/test_pagination.py
+++ b/tests/test_pagination.py
@@ -436,13 +436,22 @@ class TestCursorPagination:
             def __init__(self, items):
                 self.items = items
 
-            def filter(self, created__gt):
-                return [
+            def filter(self, created__gt=None, created__lt=None):
+                if created__gt is not None:
+                    return MockQuerySet([
+                        item for item in self.items
+                        if item.created > int(created__gt)
+                    ])
+
+                assert created__lt is not None
+                return MockQuerySet([
                     item for item in self.items
-                    if item.created > int(created__gt)
-                ]
+                    if item.created < int(created__lt)
+                ])
 
             def order_by(self, ordering):
+                if ordering.startswith('-'):
+                    return MockQuerySet(reversed(self.items))
                 return self
 
             def __getitem__(self, sliced):
@@ -485,6 +494,25 @@ class TestCursorPagination:
         next_url = self.pagination.get_next_link()
         assert next_url is None
 
+        # Now page back again
+
+        previous_url = self.pagination.get_previous_link()
+        assert previous_url
+
+        request = Request(factory.get(previous_url))
+        queryset = self.paginate_queryset(request)
+        assert [item.created for item in queryset] == [6, 7, 8, 9, 10]
+
+        previous_url = self.pagination.get_previous_link()
+        assert previous_url
+
+        request = Request(factory.get(previous_url))
+        queryset = self.paginate_queryset(request)
+        assert [item.created for item in queryset] == [1, 2, 3, 4, 5]
+
+        previous_url = self.pagination.get_previous_link()
+        assert previous_url is None
+
 
 class TestCrazyCursorPagination:
     """
@@ -500,13 +528,22 @@ class TestCrazyCursorPagination:
             def __init__(self, items):
                 self.items = items
 
-            def filter(self, created__gt):
-                return [
+            def filter(self, created__gt=None, created__lt=None):
+                if created__gt is not None:
+                    return MockQuerySet([
+                        item for item in self.items
+                        if item.created > int(created__gt)
+                    ])
+
+                assert created__lt is not None
+                return MockQuerySet([
                     item for item in self.items
-                    if item.created > int(created__gt)
-                ]
+                    if item.created < int(created__lt)
+                ])
 
             def order_by(self, ordering):
+                if ordering.startswith('-'):
+                    return MockQuerySet(reversed(self.items))
                 return self
 
             def __getitem__(self, sliced):
@@ -553,25 +590,32 @@ class TestCrazyCursorPagination:
 
         next_url = self.pagination.get_next_link()
         assert next_url is None
-        # assert content == {
-        #     'results': [1, 2, 3, 4, 5],
-        #     'previous': None,
-        #     'next': 'http://testserver/?limit=5&offset=5',
-        #     'count': 100
-        # }
-        # assert context == {
-        #     'previous_url': None,
-        #     'next_url': 'http://testserver/?limit=5&offset=5',
-        #     'page_links': [
-        #         PageLink('http://testserver/?limit=5', 1, True, False),
-        #         PageLink('http://testserver/?limit=5&offset=5', 2, False, False),
-        #         PageLink('http://testserver/?limit=5&offset=10', 3, False, False),
-        #         PAGE_BREAK,
-        #         PageLink('http://testserver/?limit=5&offset=95', 20, False, False),
-        #     ]
-        # }
-        # assert self.pagination.display_page_controls
-        # assert isinstance(self.pagination.to_html(), type(''))
+
+        # Now page back again
+
+        previous_url = self.pagination.get_previous_link()
+        assert previous_url
+
+        request = Request(factory.get(previous_url))
+        queryset = self.paginate_queryset(request)
+        assert [item.created for item in queryset] == [1, 1, 2, 3, 4]
+
+        previous_url = self.pagination.get_previous_link()
+        assert previous_url
+
+        request = Request(factory.get(previous_url))
+        queryset = self.paginate_queryset(request)
+        assert [item.created for item in queryset] == [1, 1, 1, 1, 1]
+
+        previous_url = self.pagination.get_previous_link()
+        assert previous_url
+
+        request = Request(factory.get(previous_url))
+        queryset = self.paginate_queryset(request)
+        assert [item.created for item in queryset] == [1, 1, 1, 1, 1]
+
+        previous_url = self.pagination.get_previous_link()
+        assert previous_url is None
 
 
 def test_get_displayed_page_numbers():
-- 
cgit v1.2.3
From 94b5f7a86e401e46f14fb8982afaa7a8c61847c9 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 22 Jan 2015 12:14:52 +0000
Subject: Tidy up cursor tests and make more comprehensive
---
 tests/test_pagination.py | 212 +++++++++++++++++++----------------------------
 1 file changed, 84 insertions(+), 128 deletions(-)
(limited to 'tests')
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
index 4907a080..e32dd028 100644
--- a/tests/test_pagination.py
+++ b/tests/test_pagination.py
@@ -451,171 +451,127 @@ class TestCursorPagination:
 
             def order_by(self, ordering):
                 if ordering.startswith('-'):
-                    return MockQuerySet(reversed(self.items))
+                    return MockQuerySet(list(reversed(self.items)))
                 return self
 
             def __getitem__(self, sliced):
                 return self.items[sliced]
 
-        self.pagination = pagination.CursorPagination()
-        self.queryset = MockQuerySet(
-            [MockObject(idx) for idx in range(1, 16)]
-        )
-
-    def paginate_queryset(self, request):
-        return list(self.pagination.paginate_queryset(self.queryset, request))
-
-    # def get_paginated_content(self, queryset):
-    #     response = self.pagination.get_paginated_response(queryset)
-    #     return response.data
-
-    # def get_html_context(self):
-    #     return self.pagination.get_html_context()
-
-    def test_following_cursor(self):
-        request = Request(factory.get('/'))
-        queryset = self.paginate_queryset(request)
-        assert [item.created for item in queryset] == [1, 2, 3, 4, 5]
-
-        next_url = self.pagination.get_next_link()
-        assert next_url
+        class ExamplePagination(pagination.CursorPagination):
+            page_size = 5
 
-        request = Request(factory.get(next_url))
-        queryset = self.paginate_queryset(request)
-        assert [item.created for item in queryset] == [6, 7, 8, 9, 10]
+        self.pagination = ExamplePagination()
+        self.queryset = MockQuerySet([
+            MockObject(idx) for idx in [
+                1, 1, 1, 1, 1,
+                1, 2, 3, 4, 4,
+                4, 4, 5, 6, 7,
+                7, 7, 7, 7, 7,
+                7, 7, 7, 8, 9,
+                9, 9, 9, 9, 9
+            ]
+        ])
 
-        next_url = self.pagination.get_next_link()
-        assert next_url
+    def get_pages(self, url):
+        """
+        Given a URL return a tuple of:
 
-        request = Request(factory.get(next_url))
-        queryset = self.paginate_queryset(request)
-        assert [item.created for item in queryset] == [11, 12, 13, 14, 15]
+        (previous page, current page, next page, previous url, next url)
+        """
+        request = Request(factory.get(url))
+        queryset = self.pagination.paginate_queryset(self.queryset, request)
+        current = [item.created for item in queryset]
 
         next_url = self.pagination.get_next_link()
-        assert next_url is None
-
-        # Now page back again
-
         previous_url = self.pagination.get_previous_link()
-        assert previous_url
 
-        request = Request(factory.get(previous_url))
-        queryset = self.paginate_queryset(request)
-        assert [item.created for item in queryset] == [6, 7, 8, 9, 10]
+        if next_url is not None:
+            request = Request(factory.get(next_url))
+            queryset = self.pagination.paginate_queryset(self.queryset, request)
+            next = [item.created for item in queryset]
+        else:
+            next = None
 
-        previous_url = self.pagination.get_previous_link()
-        assert previous_url
+        if previous_url is not None:
+            request = Request(factory.get(previous_url))
+            queryset = self.pagination.paginate_queryset(self.queryset, request)
+            previous = [item.created for item in queryset]
+        else:
+            previous = None
 
-        request = Request(factory.get(previous_url))
-        queryset = self.paginate_queryset(request)
-        assert [item.created for item in queryset] == [1, 2, 3, 4, 5]
+        return (previous, current, next, previous_url, next_url)
 
-        previous_url = self.pagination.get_previous_link()
-        assert previous_url is None
-
-
-class TestCrazyCursorPagination:
-    """
-    Unit tests for `pagination.CursorPagination`.
-    """
-
-    def setup(self):
-        class MockObject(object):
-            def __init__(self, idx):
-                self.created = idx
-
-        class MockQuerySet(object):
-            def __init__(self, items):
-                self.items = items
+    def test_invalid_cursor(self):
+        request = Request(factory.get('/', {'cursor': '123'}))
+        with pytest.raises(exceptions.NotFound):
+            self.pagination.paginate_queryset(self.queryset, request)
 
-            def filter(self, created__gt=None, created__lt=None):
-                if created__gt is not None:
-                    return MockQuerySet([
-                        item for item in self.items
-                        if item.created > int(created__gt)
-                    ])
+    def test_cursor_pagination(self):
+        (previous, current, next, previous_url, next_url) = self.get_pages('/')
 
-                assert created__lt is not None
-                return MockQuerySet([
-                    item for item in self.items
-                    if item.created < int(created__lt)
-                ])
+        assert previous is None
+        assert current == [1, 1, 1, 1, 1]
+        assert next == [1, 2, 3, 4, 4]
 
-            def order_by(self, ordering):
-                if ordering.startswith('-'):
-                    return MockQuerySet(reversed(self.items))
-                return self
+        (previous, current, next, previous_url, next_url) = self.get_pages(next_url)
 
-            def __getitem__(self, sliced):
-                return self.items[sliced]
+        assert previous == [1, 1, 1, 1, 1]
+        assert current == [1, 2, 3, 4, 4]
+        assert next == [4, 4, 5, 6, 7]
 
-        self.pagination = pagination.CursorPagination()
-        self.queryset = MockQuerySet([
-            MockObject(idx) for idx in [
-                1, 1, 1, 1, 1,
-                1, 1, 1, 1, 1,
-                1, 1, 2, 3, 4,
-                5, 6, 7, 8, 9
-            ]
-        ])
+        (previous, current, next, previous_url, next_url) = self.get_pages(next_url)
 
-    def paginate_queryset(self, request):
-        return list(self.pagination.paginate_queryset(self.queryset, request))
+        assert previous == [1, 2, 3, 4, 4]
+        assert current == [4, 4, 5, 6, 7]
+        assert next == [7, 7, 7, 7, 7]
 
-    def test_following_cursor_identical_items(self):
-        request = Request(factory.get('/'))
-        queryset = self.paginate_queryset(request)
-        assert [item.created for item in queryset] == [1, 1, 1, 1, 1]
+        (previous, current, next, previous_url, next_url) = self.get_pages(next_url)
 
-        next_url = self.pagination.get_next_link()
-        assert next_url
+        assert previous == [4, 4, 4, 5, 6]  # Paging artifact
+        assert current == [7, 7, 7, 7, 7]
+        assert next == [7, 7, 7, 8, 9]
 
-        request = Request(factory.get(next_url))
-        queryset = self.paginate_queryset(request)
-        assert [item.created for item in queryset] == [1, 1, 1, 1, 1]
+        (previous, current, next, previous_url, next_url) = self.get_pages(next_url)
 
-        next_url = self.pagination.get_next_link()
-        assert next_url
+        assert previous == [7, 7, 7, 7, 7]
+        assert current == [7, 7, 7, 8, 9]
+        assert next == [9, 9, 9, 9, 9]
 
-        request = Request(factory.get(next_url))
-        queryset = self.paginate_queryset(request)
-        assert [item.created for item in queryset] == [1, 1, 2, 3, 4]
+        (previous, current, next, previous_url, next_url) = self.get_pages(next_url)
 
-        next_url = self.pagination.get_next_link()
-        assert next_url
+        assert previous == [7, 7, 7, 8, 9]
+        assert current == [9, 9, 9, 9, 9]
+        assert next is None
 
-        request = Request(factory.get(next_url))
-        queryset = self.paginate_queryset(request)
-        assert [item.created for item in queryset] == [5, 6, 7, 8, 9]
+        (previous, current, next, previous_url, next_url) = self.get_pages(previous_url)
 
-        next_url = self.pagination.get_next_link()
-        assert next_url is None
+        assert previous == [7, 7, 7, 7, 7]
+        assert current == [7, 7, 7, 8, 9]
+        assert next == [9, 9, 9, 9, 9]
 
-        # Now page back again
+        (previous, current, next, previous_url, next_url) = self.get_pages(previous_url)
 
-        previous_url = self.pagination.get_previous_link()
-        assert previous_url
+        assert previous == [4, 4, 5, 6, 7]
+        assert current == [7, 7, 7, 7, 7]
+        assert next == [8, 9, 9, 9, 9]  # Paging artifact
 
-        request = Request(factory.get(previous_url))
-        queryset = self.paginate_queryset(request)
-        assert [item.created for item in queryset] == [1, 1, 2, 3, 4]
+        (previous, current, next, previous_url, next_url) = self.get_pages(previous_url)
 
-        previous_url = self.pagination.get_previous_link()
-        assert previous_url
+        assert previous == [1, 2, 3, 4, 4]
+        assert current == [4, 4, 5, 6, 7]
+        assert next == [7, 7, 7, 7, 7]
 
-        request = Request(factory.get(previous_url))
-        queryset = self.paginate_queryset(request)
-        assert [item.created for item in queryset] == [1, 1, 1, 1, 1]
+        (previous, current, next, previous_url, next_url) = self.get_pages(previous_url)
 
-        previous_url = self.pagination.get_previous_link()
-        assert previous_url
+        assert previous == [1, 1, 1, 1, 1]
+        assert current == [1, 2, 3, 4, 4]
+        assert next == [4, 4, 5, 6, 7]
 
-        request = Request(factory.get(previous_url))
-        queryset = self.paginate_queryset(request)
-        assert [item.created for item in queryset] == [1, 1, 1, 1, 1]
+        (previous, current, next, previous_url, next_url) = self.get_pages(previous_url)
 
-        previous_url = self.pagination.get_previous_link()
-        assert previous_url is None
+        assert previous is None
+        assert current == [1, 1, 1, 1, 1]
+        assert next == [1, 2, 3, 4, 4]
 
 
 def test_get_displayed_page_numbers():
-- 
cgit v1.2.3
From 83a82b44a56a303d43a16dd675fae116e51b9d85 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 22 Jan 2015 15:07:01 +0000
Subject: Support for tuple ordering in cursor pagination
---
 tests/test_pagination.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'tests')
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
index e32dd028..fffdcbe9 100644
--- a/tests/test_pagination.py
+++ b/tests/test_pagination.py
@@ -450,7 +450,7 @@ class TestCursorPagination:
                 ])
 
             def order_by(self, ordering):
-                if ordering.startswith('-'):
+                if ordering[0].startswith('-'):
                     return MockQuerySet(list(reversed(self.items)))
                 return self
 
-- 
cgit v1.2.3
From 408261ee02b176732b7f840f7042e7c24f3ecd27 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 22 Jan 2015 15:15:52 +0000
Subject: Support ordering attribute either on view or on pagination class for
 CursorPagination
---
 tests/test_pagination.py | 1 +
 1 file changed, 1 insertion(+)
(limited to 'tests')
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
index fffdcbe9..c05b4aba 100644
--- a/tests/test_pagination.py
+++ b/tests/test_pagination.py
@@ -459,6 +459,7 @@ class TestCursorPagination:
 
         class ExamplePagination(pagination.CursorPagination):
             page_size = 5
+            ordering = 'created'
 
         self.pagination = ExamplePagination()
         self.queryset = MockQuerySet([
-- 
cgit v1.2.3
From 0822c9e55820f8e4737329e38abc2e21718af9e5 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 22 Jan 2015 16:12:05 +0000
Subject: Cursor pagination now works with OrderingFilter
---
 tests/test_pagination.py | 40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)
(limited to 'tests')
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
index c05b4aba..338be610 100644
--- a/tests/test_pagination.py
+++ b/tests/test_pagination.py
@@ -77,6 +77,20 @@ class TestPaginationIntegration:
             'count': 50
         }
 
+    def test_setting_page_size_to_zero(self):
+        """
+        When page_size parameter is invalid it should return to the default.
+        """
+        request = factory.get('/', {'page_size': 0})
+        response = self.view(request)
+        assert response.status_code == status.HTTP_200_OK
+        assert response.data == {
+            'results': [2, 4, 6, 8, 10],
+            'previous': None,
+            'next': 'http://testserver/?page=2&page_size=0',
+            'count': 50
+        }
+
     def test_additional_query_params_are_preserved(self):
         request = factory.get('/', {'page': 2, 'filter': 'even'})
         response = self.view(request)
@@ -88,6 +102,14 @@ class TestPaginationIntegration:
             'count': 50
         }
 
+    def test_404_not_found_for_zero_page(self):
+        request = factory.get('/', {'page': '0'})
+        response = self.view(request)
+        assert response.status_code == status.HTTP_404_NOT_FOUND
+        assert response.data == {
+            'detail': 'Invalid page "0": That page number is less than 1.'
+        }
+
     def test_404_not_found_for_invalid_page(self):
         request = factory.get('/', {'page': 'invalid'})
         response = self.view(request)
@@ -507,6 +529,24 @@ class TestCursorPagination:
         with pytest.raises(exceptions.NotFound):
             self.pagination.paginate_queryset(self.queryset, request)
 
+    def test_use_with_ordering_filter(self):
+        class MockView:
+            filter_backends = (filters.OrderingFilter,)
+            ordering_fields = ['username', 'created']
+            ordering = 'created'
+
+        request = Request(factory.get('/', {'ordering': 'username'}))
+        ordering = self.pagination.get_ordering(request, [], MockView())
+        assert ordering == ('username',)
+
+        request = Request(factory.get('/', {'ordering': '-username'}))
+        ordering = self.pagination.get_ordering(request, [], MockView())
+        assert ordering == ('-username',)
+
+        request = Request(factory.get('/', {'ordering': 'invalid'}))
+        ordering = self.pagination.get_ordering(request, [], MockView())
+        assert ordering == ('created',)
+
     def test_cursor_pagination(self):
         (previous, current, next, previous_url, next_url) = self.get_pages('/')
 
-- 
cgit v1.2.3
From 43d983fae82ab23ca94f52deb29e938eb2a40e88 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 22 Jan 2015 17:25:12 +0000
Subject: Add paging controls
---
 tests/test_pagination.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)
(limited to 'tests')
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
index 338be610..13bfb627 100644
--- a/tests/test_pagination.py
+++ b/tests/test_pagination.py
@@ -1,3 +1,4 @@
+# coding: utf-8
 from __future__ import unicode_literals
 from rest_framework import exceptions, generics, pagination, serializers, status, filters
 from rest_framework.request import Request
@@ -471,7 +472,7 @@ class TestCursorPagination:
                     if item.created < int(created__lt)
                 ])
 
-            def order_by(self, ordering):
+            def order_by(self, *ordering):
                 if ordering[0].startswith('-'):
                     return MockQuerySet(list(reversed(self.items)))
                 return self
@@ -614,6 +615,8 @@ class TestCursorPagination:
         assert current == [1, 1, 1, 1, 1]
         assert next == [1, 2, 3, 4, 4]
 
+        assert isinstance(self.pagination.to_html(), type(''))
+
 
 def test_get_displayed_page_numbers():
     """
-- 
cgit v1.2.3
From b09ef28959fe63351f0dd24564b7d2d344b44fa3 Mon Sep 17 00:00:00 2001
From: Brandon Cazander
Date: Sat, 24 Jan 2015 01:37:23 -0800
Subject: Add failing test for request.version AttributeError in BrowsableAPI.
---
 tests/browsable_api/auth_urls.py          |  9 ++++++++-
 tests/browsable_api/test_browsable_api.py | 10 ++++++++++
 tests/browsable_api/views.py              | 27 +++++++++++++++++++++++++++
 3 files changed, 45 insertions(+), 1 deletion(-)
(limited to 'tests')
diff --git a/tests/browsable_api/auth_urls.py b/tests/browsable_api/auth_urls.py
index bce7dcf9..098a99ac 100644
--- a/tests/browsable_api/auth_urls.py
+++ b/tests/browsable_api/auth_urls.py
@@ -1,10 +1,17 @@
 from __future__ import unicode_literals
 from django.conf.urls import patterns, url, include
+from rest_framework import routers
 
-from .views import MockView
+from .views import MockView, FooViewSet, BarViewSet
+
+router = routers.SimpleRouter()
+router.register(r'foo', FooViewSet)
+router.register(r'bar', BarViewSet)
 
 urlpatterns = patterns(
     '',
     (r'^$', MockView.as_view()),
+    url(r'^', include(router.urls)),
+    url(r'^bar/(?P\d+)/$', BarViewSet, name='bar-list'),
     url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')),
 )
diff --git a/tests/browsable_api/test_browsable_api.py b/tests/browsable_api/test_browsable_api.py
index 5f264783..31907f84 100644
--- a/tests/browsable_api/test_browsable_api.py
+++ b/tests/browsable_api/test_browsable_api.py
@@ -3,6 +3,7 @@ from django.contrib.auth.models import User
 from django.test import TestCase
 
 from rest_framework.test import APIClient
+from .models import Foo, Bar
 
 
 class DropdownWithAuthTests(TestCase):
@@ -16,6 +17,8 @@ class DropdownWithAuthTests(TestCase):
         self.email = 'lennon@thebeatles.com'
         self.password = 'password'
         self.user = User.objects.create_user(self.username, self.email, self.password)
+        foo = Foo.objects.create(name='Foo')
+        Bar.objects.create(foo=foo)
 
     def tearDown(self):
         self.client.logout()
@@ -25,6 +28,13 @@ class DropdownWithAuthTests(TestCase):
         response = self.client.get('/')
         self.assertContains(response, 'john')
 
+    def test_bug_2455_clone_request(self):
+        self.client.login(username=self.username, password=self.password)
+        json_response = self.client.get('/foo/1/?format=json')
+        self.assertEqual(json_response.status_code, 200)
+        browsable_api_response = self.client.get('/foo/1/')
+        self.assertEqual(browsable_api_response.status_code, 200)
+
     def test_logout_shown_when_logged_in(self):
         self.client.login(username=self.username, password=self.password)
         response = self.client.get('/')
diff --git a/tests/browsable_api/views.py b/tests/browsable_api/views.py
index 000f4e80..f06f7c40 100644
--- a/tests/browsable_api/views.py
+++ b/tests/browsable_api/views.py
@@ -1,9 +1,14 @@
 from __future__ import unicode_literals
 
 from rest_framework.views import APIView
+from rest_framework.viewsets import ModelViewSet
 from rest_framework import authentication
 from rest_framework import renderers
 from rest_framework.response import Response
+from rest_framework.renderers import BrowsableAPIRenderer, JSONRenderer
+from rest_framework.versioning import NamespaceVersioning
+from .models import Foo, Bar
+from .serializers import FooSerializer, BarSerializer
 
 
 class MockView(APIView):
@@ -13,3 +18,25 @@ class MockView(APIView):
 
     def get(self, request):
         return Response({'a': 1, 'b': 2, 'c': 3})
+
+
+class SerializerClassMixin(object):
+    def get_serializer_class(self):
+        # Get base name of serializer
+        self.request.version
+        return self.serializer_class
+
+
+class FooViewSet(SerializerClassMixin, ModelViewSet):
+    versioning_class = NamespaceVersioning
+    model = Foo
+    queryset = Foo.objects.all()
+    serializer_class = FooSerializer
+    renderer_classes = (BrowsableAPIRenderer, JSONRenderer)
+
+
+class BarViewSet(SerializerClassMixin, ModelViewSet):
+    model = Bar
+    queryset = Bar.objects.all()
+    serializer_class = BarSerializer
+    renderer_classes = (BrowsableAPIRenderer, )
-- 
cgit v1.2.3
From 0ee2edc0a14c4d14b8aa6e4b63ccbd0c2cc78024 Mon Sep 17 00:00:00 2001
From: Brandon Cazander
Date: Sat, 24 Jan 2015 01:44:09 -0800
Subject: Add missed files for test.
---
 tests/browsable_api/models.py      |  9 +++++++++
 tests/browsable_api/serializers.py | 14 ++++++++++++++
 2 files changed, 23 insertions(+)
 create mode 100644 tests/browsable_api/models.py
 create mode 100644 tests/browsable_api/serializers.py
(limited to 'tests')
diff --git a/tests/browsable_api/models.py b/tests/browsable_api/models.py
new file mode 100644
index 00000000..05c6c23b
--- /dev/null
+++ b/tests/browsable_api/models.py
@@ -0,0 +1,9 @@
+from django.db import models
+
+
+class Foo(models.Model):
+    name = models.CharField(max_length=30)
+
+
+class Bar(models.Model):
+    foo = models.ForeignKey("Foo", editable=False)
diff --git a/tests/browsable_api/serializers.py b/tests/browsable_api/serializers.py
new file mode 100644
index 00000000..e8364540
--- /dev/null
+++ b/tests/browsable_api/serializers.py
@@ -0,0 +1,14 @@
+from .models import Foo, Bar
+from rest_framework.serializers import HyperlinkedModelSerializer, HyperlinkedIdentityField
+
+
+class FooSerializer(HyperlinkedModelSerializer):
+    bar = HyperlinkedIdentityField(view_name='bar-list')
+
+    class Meta:
+        model = Foo
+
+
+class BarSerializer(HyperlinkedModelSerializer):
+    class Meta:
+        model = Bar
-- 
cgit v1.2.3
From 6c083b12a1162bf8e0f51e6c52ff13a1bd621cf2 Mon Sep 17 00:00:00 2001
From: Brandon Cazander
Date: Sat, 24 Jan 2015 11:00:36 -0800
Subject: Streamline test for #2455
---
 tests/browsable_api/auth_urls.py          |  8 +-------
 tests/browsable_api/models.py             |  9 ---------
 tests/browsable_api/serializers.py        | 14 --------------
 tests/browsable_api/test_browsable_api.py | 10 ----------
 tests/browsable_api/views.py              | 27 ---------------------------
 tests/test_metadata.py                    | 15 +++++++++++++++
 6 files changed, 16 insertions(+), 67 deletions(-)
 delete mode 100644 tests/browsable_api/models.py
 delete mode 100644 tests/browsable_api/serializers.py
(limited to 'tests')
diff --git a/tests/browsable_api/auth_urls.py b/tests/browsable_api/auth_urls.py
index 098a99ac..97bc1036 100644
--- a/tests/browsable_api/auth_urls.py
+++ b/tests/browsable_api/auth_urls.py
@@ -1,17 +1,11 @@
 from __future__ import unicode_literals
 from django.conf.urls import patterns, url, include
-from rest_framework import routers
 
-from .views import MockView, FooViewSet, BarViewSet
+from .views import MockView
 
-router = routers.SimpleRouter()
-router.register(r'foo', FooViewSet)
-router.register(r'bar', BarViewSet)
 
 urlpatterns = patterns(
     '',
     (r'^$', MockView.as_view()),
-    url(r'^', include(router.urls)),
-    url(r'^bar/(?P\d+)/$', BarViewSet, name='bar-list'),
     url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')),
 )
diff --git a/tests/browsable_api/models.py b/tests/browsable_api/models.py
deleted file mode 100644
index 05c6c23b..00000000
--- a/tests/browsable_api/models.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from django.db import models
-
-
-class Foo(models.Model):
-    name = models.CharField(max_length=30)
-
-
-class Bar(models.Model):
-    foo = models.ForeignKey("Foo", editable=False)
diff --git a/tests/browsable_api/serializers.py b/tests/browsable_api/serializers.py
deleted file mode 100644
index e8364540..00000000
--- a/tests/browsable_api/serializers.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from .models import Foo, Bar
-from rest_framework.serializers import HyperlinkedModelSerializer, HyperlinkedIdentityField
-
-
-class FooSerializer(HyperlinkedModelSerializer):
-    bar = HyperlinkedIdentityField(view_name='bar-list')
-
-    class Meta:
-        model = Foo
-
-
-class BarSerializer(HyperlinkedModelSerializer):
-    class Meta:
-        model = Bar
diff --git a/tests/browsable_api/test_browsable_api.py b/tests/browsable_api/test_browsable_api.py
index 31907f84..5f264783 100644
--- a/tests/browsable_api/test_browsable_api.py
+++ b/tests/browsable_api/test_browsable_api.py
@@ -3,7 +3,6 @@ from django.contrib.auth.models import User
 from django.test import TestCase
 
 from rest_framework.test import APIClient
-from .models import Foo, Bar
 
 
 class DropdownWithAuthTests(TestCase):
@@ -17,8 +16,6 @@ class DropdownWithAuthTests(TestCase):
         self.email = 'lennon@thebeatles.com'
         self.password = 'password'
         self.user = User.objects.create_user(self.username, self.email, self.password)
-        foo = Foo.objects.create(name='Foo')
-        Bar.objects.create(foo=foo)
 
     def tearDown(self):
         self.client.logout()
@@ -28,13 +25,6 @@ class DropdownWithAuthTests(TestCase):
         response = self.client.get('/')
         self.assertContains(response, 'john')
 
-    def test_bug_2455_clone_request(self):
-        self.client.login(username=self.username, password=self.password)
-        json_response = self.client.get('/foo/1/?format=json')
-        self.assertEqual(json_response.status_code, 200)
-        browsable_api_response = self.client.get('/foo/1/')
-        self.assertEqual(browsable_api_response.status_code, 200)
-
     def test_logout_shown_when_logged_in(self):
         self.client.login(username=self.username, password=self.password)
         response = self.client.get('/')
diff --git a/tests/browsable_api/views.py b/tests/browsable_api/views.py
index f06f7c40..000f4e80 100644
--- a/tests/browsable_api/views.py
+++ b/tests/browsable_api/views.py
@@ -1,14 +1,9 @@
 from __future__ import unicode_literals
 
 from rest_framework.views import APIView
-from rest_framework.viewsets import ModelViewSet
 from rest_framework import authentication
 from rest_framework import renderers
 from rest_framework.response import Response
-from rest_framework.renderers import BrowsableAPIRenderer, JSONRenderer
-from rest_framework.versioning import NamespaceVersioning
-from .models import Foo, Bar
-from .serializers import FooSerializer, BarSerializer
 
 
 class MockView(APIView):
@@ -18,25 +13,3 @@ class MockView(APIView):
 
     def get(self, request):
         return Response({'a': 1, 'b': 2, 'c': 3})
-
-
-class SerializerClassMixin(object):
-    def get_serializer_class(self):
-        # Get base name of serializer
-        self.request.version
-        return self.serializer_class
-
-
-class FooViewSet(SerializerClassMixin, ModelViewSet):
-    versioning_class = NamespaceVersioning
-    model = Foo
-    queryset = Foo.objects.all()
-    serializer_class = FooSerializer
-    renderer_classes = (BrowsableAPIRenderer, JSONRenderer)
-
-
-class BarViewSet(SerializerClassMixin, ModelViewSet):
-    model = Bar
-    queryset = Bar.objects.all()
-    serializer_class = BarSerializer
-    renderer_classes = (BrowsableAPIRenderer, )
diff --git a/tests/test_metadata.py b/tests/test_metadata.py
index 972a896a..bdc84edf 100644
--- a/tests/test_metadata.py
+++ b/tests/test_metadata.py
@@ -1,6 +1,7 @@
 from __future__ import unicode_literals
 from rest_framework import exceptions, serializers, status, views
 from rest_framework.request import Request
+from rest_framework.renderers import BrowsableAPIRenderer
 from rest_framework.test import APIRequestFactory
 
 request = Request(APIRequestFactory().options('/'))
@@ -168,3 +169,17 @@ class TestMetadata:
         response = view(request=request)
         assert response.status_code == status.HTTP_200_OK
         assert list(response.data['actions'].keys()) == ['POST']
+
+    def test_bug_2455_clone_request(self):
+        class ExampleView(views.APIView):
+            renderer_classes = (BrowsableAPIRenderer,)
+
+            def post(self, request):
+                pass
+
+            def get_serializer(self):
+                assert hasattr(self.request, 'version')
+                return serializers.Serializer()
+
+        view = ExampleView.as_view()
+        view(request=request)
-- 
cgit v1.2.3
From ac87490b91e3405d497da360afed10842a73dfd0 Mon Sep 17 00:00:00 2001
From: Brandon Cazander
Date: Tue, 27 Jan 2015 17:10:17 -0800
Subject: Clone the versioning_scheme when necessary. Fixes #2477
---
 tests/test_metadata.py | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)
(limited to 'tests')
diff --git a/tests/test_metadata.py b/tests/test_metadata.py
index bdc84edf..5031c0f3 100644
--- a/tests/test_metadata.py
+++ b/tests/test_metadata.py
@@ -1,5 +1,5 @@
 from __future__ import unicode_literals
-from rest_framework import exceptions, serializers, status, views
+from rest_framework import exceptions, serializers, status, views, versioning
 from rest_framework.request import Request
 from rest_framework.renderers import BrowsableAPIRenderer
 from rest_framework.test import APIRequestFactory
@@ -183,3 +183,18 @@ class TestMetadata:
 
         view = ExampleView.as_view()
         view(request=request)
+
+    def test_bug_2477_clone_request(self):
+        class ExampleView(views.APIView):
+            renderer_classes = (BrowsableAPIRenderer,)
+
+            def post(self, request):
+                pass
+
+            def get_serializer(self):
+                assert hasattr(self.request, 'versioning_scheme')
+                return serializers.Serializer()
+
+        scheme = versioning.QueryParameterVersioning
+        view = ExampleView.as_view(versioning_class=scheme)
+        view(request=request)
-- 
cgit v1.2.3
From 2cc4cb24652366c6622af08370a0c04b429aa4b8 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Sat, 31 Jan 2015 08:53:40 +0000
Subject: Fix error text in test.
---
 tests/test_generics.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'tests')
diff --git a/tests/test_generics.py b/tests/test_generics.py
index fba8718f..88e792ce 100644
--- a/tests/test_generics.py
+++ b/tests/test_generics.py
@@ -483,7 +483,7 @@ class TestFilterBackendAppliedToViews(TestCase):
         request = factory.get('/1')
         response = instance_view(request, pk=1).render()
         self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-        self.assertEqual(response.data, {'detail': 'Not found'})
+        self.assertEqual(response.data, {'detail': 'Not found.'})
 
     def test_get_instance_view_will_return_single_object_when_filter_does_not_exclude_it(self):
         """
-- 
cgit v1.2.3
From 77d061d234e03004f34058028707ecddfc730fae Mon Sep 17 00:00:00 2001
From: Brandon Cazander
Date: Wed, 28 Jan 2015 17:08:34 -0800
Subject: Provide rest_framework.resolve. Fixes #2489
---
 tests/test_relations.py | 50 +++++++++++++++++++++++++++++++++++++++++++++++--
 tests/urls.py           |  4 ++--
 2 files changed, 50 insertions(+), 4 deletions(-)
(limited to 'tests')
diff --git a/tests/test_relations.py b/tests/test_relations.py
index fbe176e2..b82a1f2a 100644
--- a/tests/test_relations.py
+++ b/tests/test_relations.py
@@ -1,11 +1,28 @@
 from .utils import mock_reverse, fail_reverse, BadType, MockObject, MockQueryset
-from django.core.exceptions import ImproperlyConfigured
+from django.conf.urls import patterns, url, include
+from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
 from django.utils.datastructures import MultiValueDict
 from rest_framework import serializers
 from rest_framework.fields import empty
-from rest_framework.test import APISimpleTestCase
+from rest_framework.test import APISimpleTestCase, APIRequestFactory
+from rest_framework.versioning import NamespaceVersioning
 import pytest
 
+factory = APIRequestFactory()
+request = factory.get('/')  # Just to ensure we have a request in the serializer context
+
+dummy_view = lambda request, pk: None
+
+included_patterns = [
+    url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
+]
+
+urlpatterns = patterns(
+    '',
+    url(r'^v1/', include(included_patterns, namespace='v1')),
+    url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
+)
+
 
 class TestStringRelatedField(APISimpleTestCase):
     def setUp(self):
@@ -48,6 +65,35 @@ class TestPrimaryKeyRelatedField(APISimpleTestCase):
         assert representation == self.instance.pk
 
 
+class TestHyperlinkedRelatedField(APISimpleTestCase):
+    urls = 'tests.test_relations'
+
+    def setUp(self):
+        class HyperlinkedMockQueryset(MockQueryset):
+            def get(self, **lookup):
+                for item in self.items:
+                    if item.pk == int(lookup.get('pk', -1)):
+                        return item
+                raise ObjectDoesNotExist()
+
+        self.queryset = HyperlinkedMockQueryset([
+            MockObject(pk=1, name='foo'),
+            MockObject(pk=2, name='bar'),
+            MockObject(pk=3, name='baz')
+        ])
+        self.field = serializers.HyperlinkedRelatedField(
+            view_name='example-detail',
+            queryset=self.queryset
+        )
+        request = factory.post('/')
+        request.versioning_scheme = NamespaceVersioning()
+        self.field._context = {'request': request}
+
+    def test_bug_2489(self):
+        self.field.to_internal_value('/example/3/')
+        self.field.to_internal_value('/v1/example/3/')
+
+
 class TestHyperlinkedIdentityField(APISimpleTestCase):
     def setUp(self):
         self.instance = MockObject(pk=1, name='foo')
diff --git a/tests/urls.py b/tests/urls.py
index 41f527df..742e361d 100644
--- a/tests/urls.py
+++ b/tests/urls.py
@@ -1,6 +1,6 @@
 """
 Blank URLConf just to keep the test suite happy
 """
-from django.conf.urls import patterns
+from tests import test_relations
 
-urlpatterns = patterns('')
+urlpatterns = test_relations.urlpatterns
-- 
cgit v1.2.3
From f3067a7fabdd0edb5bc5f48cfdadd2850866c189 Mon Sep 17 00:00:00 2001
From: Brandon Cazander
Date: Mon, 2 Feb 2015 20:41:06 -0800
Subject: Remove unnecessary APIRequestFactory get from tests.
---
 tests/test_relations.py | 1 -
 1 file changed, 1 deletion(-)
(limited to 'tests')
diff --git a/tests/test_relations.py b/tests/test_relations.py
index b82a1f2a..ff377d38 100644
--- a/tests/test_relations.py
+++ b/tests/test_relations.py
@@ -9,7 +9,6 @@ from rest_framework.versioning import NamespaceVersioning
 import pytest
 
 factory = APIRequestFactory()
-request = factory.get('/')  # Just to ensure we have a request in the serializer context
 
 dummy_view = lambda request, pk: None
 
-- 
cgit v1.2.3
From 030f01afdbcd4018a288250ef1f4c12de28e63bb Mon Sep 17 00:00:00 2001
From: Brandon Cazander
Date: Tue, 3 Feb 2015 02:14:38 -0800
Subject: Reorganize tests.
---
 tests/test_relations.py  | 49 ++----------------------------------------------
 tests/test_versioning.py | 41 ++++++++++++++++++++++++++++++++++++++--
 tests/urls.py            |  4 ++--
 3 files changed, 43 insertions(+), 51 deletions(-)
(limited to 'tests')
diff --git a/tests/test_relations.py b/tests/test_relations.py
index ff377d38..fbe176e2 100644
--- a/tests/test_relations.py
+++ b/tests/test_relations.py
@@ -1,27 +1,11 @@
 from .utils import mock_reverse, fail_reverse, BadType, MockObject, MockQueryset
-from django.conf.urls import patterns, url, include
-from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
+from django.core.exceptions import ImproperlyConfigured
 from django.utils.datastructures import MultiValueDict
 from rest_framework import serializers
 from rest_framework.fields import empty
-from rest_framework.test import APISimpleTestCase, APIRequestFactory
-from rest_framework.versioning import NamespaceVersioning
+from rest_framework.test import APISimpleTestCase
 import pytest
 
-factory = APIRequestFactory()
-
-dummy_view = lambda request, pk: None
-
-included_patterns = [
-    url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
-]
-
-urlpatterns = patterns(
-    '',
-    url(r'^v1/', include(included_patterns, namespace='v1')),
-    url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
-)
-
 
 class TestStringRelatedField(APISimpleTestCase):
     def setUp(self):
@@ -64,35 +48,6 @@ class TestPrimaryKeyRelatedField(APISimpleTestCase):
         assert representation == self.instance.pk
 
 
-class TestHyperlinkedRelatedField(APISimpleTestCase):
-    urls = 'tests.test_relations'
-
-    def setUp(self):
-        class HyperlinkedMockQueryset(MockQueryset):
-            def get(self, **lookup):
-                for item in self.items:
-                    if item.pk == int(lookup.get('pk', -1)):
-                        return item
-                raise ObjectDoesNotExist()
-
-        self.queryset = HyperlinkedMockQueryset([
-            MockObject(pk=1, name='foo'),
-            MockObject(pk=2, name='bar'),
-            MockObject(pk=3, name='baz')
-        ])
-        self.field = serializers.HyperlinkedRelatedField(
-            view_name='example-detail',
-            queryset=self.queryset
-        )
-        request = factory.post('/')
-        request.versioning_scheme = NamespaceVersioning()
-        self.field._context = {'request': request}
-
-    def test_bug_2489(self):
-        self.field.to_internal_value('/example/3/')
-        self.field.to_internal_value('/v1/example/3/')
-
-
 class TestHyperlinkedIdentityField(APISimpleTestCase):
     def setUp(self):
         self.instance = MockObject(pk=1, name='foo')
diff --git a/tests/test_versioning.py b/tests/test_versioning.py
index c44f727d..e7c8485e 100644
--- a/tests/test_versioning.py
+++ b/tests/test_versioning.py
@@ -1,9 +1,13 @@
+from .utils import MockObject, MockQueryset
 from django.conf.urls import include, url
+from django.core.exceptions import ObjectDoesNotExist
+from rest_framework import serializers
 from rest_framework import status, versioning
 from rest_framework.decorators import APIView
 from rest_framework.response import Response
 from rest_framework.reverse import reverse
-from rest_framework.test import APIRequestFactory, APITestCase
+from rest_framework.test import APIRequestFactory, APITestCase, APISimpleTestCase
+from rest_framework.versioning import NamespaceVersioning
 
 
 class RequestVersionView(APIView):
@@ -29,15 +33,18 @@ class RequestInvalidVersionView(APIView):
 factory = APIRequestFactory()
 
 mock_view = lambda request: None
+dummy_view = lambda request, pk: None
 
 included_patterns = [
     url(r'^namespaced/$', mock_view, name='another'),
+    url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
 ]
 
 urlpatterns = [
     url(r'^v1/', include(included_patterns, namespace='v1')),
     url(r'^another/$', mock_view, name='another'),
-    url(r'^(?P[^/]+)/another/$', mock_view, name='another')
+    url(r'^(?P[^/]+)/another/$', mock_view, name='another'),
+    url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
 ]
 
 
@@ -221,3 +228,33 @@ class TestInvalidVersion:
         request.resolver_match = FakeResolverMatch
         response = view(request, version='v3')
         assert response.status_code == status.HTTP_404_NOT_FOUND
+
+
+class TestHyperlinkedRelatedField(APISimpleTestCase):
+    urls = 'tests.test_versioning'
+
+    def setUp(self):
+
+        class HyperlinkedMockQueryset(MockQueryset):
+            def get(self, **lookup):
+                for item in self.items:
+                    if item.pk == int(lookup.get('pk', -1)):
+                        return item
+                raise ObjectDoesNotExist()
+
+        self.queryset = HyperlinkedMockQueryset([
+            MockObject(pk=1, name='foo'),
+            MockObject(pk=2, name='bar'),
+            MockObject(pk=3, name='baz')
+        ])
+        self.field = serializers.HyperlinkedRelatedField(
+            view_name='example-detail',
+            queryset=self.queryset
+        )
+        request = factory.post('/', urlconf='tests.test_versioning')
+        request.versioning_scheme = NamespaceVersioning()
+        self.field._context = {'request': request}
+
+    def test_bug_2489(self):
+        self.field.to_internal_value('/example/3/')
+        self.field.to_internal_value('/v1/example/3/')
diff --git a/tests/urls.py b/tests/urls.py
index 742e361d..41f527df 100644
--- a/tests/urls.py
+++ b/tests/urls.py
@@ -1,6 +1,6 @@
 """
 Blank URLConf just to keep the test suite happy
 """
-from tests import test_relations
+from django.conf.urls import patterns
 
-urlpatterns = test_relations.urlpatterns
+urlpatterns = patterns('')
-- 
cgit v1.2.3
From e1c45133126e0c47b8470b4cf7a43c6a7f4fca43 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 5 Feb 2015 00:58:09 +0000
Subject: Fix NamespaceVersioning with hyperlinked serializer fields
---
 tests/test_relations_hyperlink.py |  7 +++---
 tests/test_versioning.py          | 50 +++++++++++++++++++++++----------------
 tests/utils.py                    | 24 +++++++++++++++++++
 3 files changed, 56 insertions(+), 25 deletions(-)
(limited to 'tests')
diff --git a/tests/test_relations_hyperlink.py b/tests/test_relations_hyperlink.py
index f1b882ed..aede61d2 100644
--- a/tests/test_relations_hyperlink.py
+++ b/tests/test_relations_hyperlink.py
@@ -1,5 +1,5 @@
 from __future__ import unicode_literals
-from django.conf.urls import patterns, url
+from django.conf.urls import url
 from django.test import TestCase
 from rest_framework import serializers
 from rest_framework.test import APIRequestFactory
@@ -14,8 +14,7 @@ request = factory.get('/')  # Just to ensure we have a request in the serializer
 
 dummy_view = lambda request, pk: None
 
-urlpatterns = patterns(
-    '',
+urlpatterns = [
     url(r'^dummyurl/(?P[0-9]+)/$', dummy_view, name='dummy-url'),
     url(r'^manytomanysource/(?P[0-9]+)/$', dummy_view, name='manytomanysource-detail'),
     url(r'^manytomanytarget/(?P[0-9]+)/$', dummy_view, name='manytomanytarget-detail'),
@@ -24,7 +23,7 @@ urlpatterns = patterns(
     url(r'^nullableforeignkeysource/(?P[0-9]+)/$', dummy_view, name='nullableforeignkeysource-detail'),
     url(r'^onetoonetarget/(?P[0-9]+)/$', dummy_view, name='onetoonetarget-detail'),
     url(r'^nullableonetoonesource/(?P[0-9]+)/$', dummy_view, name='nullableonetoonesource-detail'),
-)
+]
 
 
 # ManyToMany
diff --git a/tests/test_versioning.py b/tests/test_versioning.py
index e7c8485e..cdd10065 100644
--- a/tests/test_versioning.py
+++ b/tests/test_versioning.py
@@ -1,4 +1,4 @@
-from .utils import MockObject, MockQueryset
+from .utils import MockObject, MockQueryset, UsingURLPatterns
 from django.conf.urls import include, url
 from django.core.exceptions import ObjectDoesNotExist
 from rest_framework import serializers
@@ -6,8 +6,9 @@ from rest_framework import status, versioning
 from rest_framework.decorators import APIView
 from rest_framework.response import Response
 from rest_framework.reverse import reverse
-from rest_framework.test import APIRequestFactory, APITestCase, APISimpleTestCase
+from rest_framework.test import APIRequestFactory, APITestCase
 from rest_framework.versioning import NamespaceVersioning
+import pytest
 
 
 class RequestVersionView(APIView):
@@ -35,18 +36,6 @@ factory = APIRequestFactory()
 mock_view = lambda request: None
 dummy_view = lambda request, pk: None
 
-included_patterns = [
-    url(r'^namespaced/$', mock_view, name='another'),
-    url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
-]
-
-urlpatterns = [
-    url(r'^v1/', include(included_patterns, namespace='v1')),
-    url(r'^another/$', mock_view, name='another'),
-    url(r'^(?P[^/]+)/another/$', mock_view, name='another'),
-    url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
-]
-
 
 class TestRequestVersion:
     def test_unversioned(self):
@@ -121,8 +110,17 @@ class TestRequestVersion:
         assert response.data == {'version': None}
 
 
-class TestURLReversing(APITestCase):
-    urls = 'tests.test_versioning'
+class TestURLReversing(UsingURLPatterns, APITestCase):
+    included = [
+        url(r'^namespaced/$', mock_view, name='another'),
+        url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
+    ]
+
+    urlpatterns = [
+        url(r'^v1/', include(included, namespace='v1')),
+        url(r'^another/$', mock_view, name='another'),
+        url(r'^(?P[^/]+)/another/$', mock_view, name='another'),
+    ]
 
     def test_reverse_unversioned(self):
         view = ReverseView.as_view()
@@ -230,10 +228,18 @@ class TestInvalidVersion:
         assert response.status_code == status.HTTP_404_NOT_FOUND
 
 
-class TestHyperlinkedRelatedField(APISimpleTestCase):
-    urls = 'tests.test_versioning'
+class TestHyperlinkedRelatedField(UsingURLPatterns, APITestCase):
+    included = [
+        url(r'^namespaced/(?P\d+)/$', mock_view, name='namespaced'),
+    ]
+
+    urlpatterns = [
+        url(r'^v1/', include(included, namespace='v1')),
+        url(r'^v2/', include(included, namespace='v2'))
+    ]
 
     def setUp(self):
+        super(TestHyperlinkedRelatedField, self).setUp()
 
         class HyperlinkedMockQueryset(MockQueryset):
             def get(self, **lookup):
@@ -248,13 +254,15 @@ class TestHyperlinkedRelatedField(APISimpleTestCase):
             MockObject(pk=3, name='baz')
         ])
         self.field = serializers.HyperlinkedRelatedField(
-            view_name='example-detail',
+            view_name='namespaced',
             queryset=self.queryset
         )
         request = factory.post('/', urlconf='tests.test_versioning')
         request.versioning_scheme = NamespaceVersioning()
+        request.version = 'v1'
         self.field._context = {'request': request}
 
     def test_bug_2489(self):
-        self.field.to_internal_value('/example/3/')
-        self.field.to_internal_value('/v1/example/3/')
+        self.field.to_internal_value('/v1/namespaced/3/')
+        with pytest.raises(serializers.ValidationError):
+            self.field.to_internal_value('/v2/namespaced/3/')
diff --git a/tests/utils.py b/tests/utils.py
index 5b2d7586..b9034996 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -2,6 +2,30 @@ from django.core.exceptions import ObjectDoesNotExist
 from django.core.urlresolvers import NoReverseMatch
 
 
+class UsingURLPatterns(object):
+    """
+    Isolates URL patterns used during testing on the test class itself.
+    For example:
+
+    class MyTestCase(UsingURLPatterns, TestCase):
+        urlpatterns = [
+            ...
+        ]
+
+        def test_something(self):
+            ...
+    """
+    urls = __name__
+
+    def setUp(self):
+        global urlpatterns
+        urlpatterns = self.urlpatterns
+
+    def tearDown(self):
+        global urlpatterns
+        urlpatterns = []
+
+
 class MockObject(object):
     def __init__(self, **kwargs):
         self._kwargs = kwargs
-- 
cgit v1.2.3
From f98f842827c6e79bbaa196482e3c3c549e8999c8 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 5 Feb 2015 01:24:55 +0000
Subject: Minor bits of test cleanup
---
 tests/test_versioning.py | 39 +++++++++++++++------------------------
 1 file changed, 15 insertions(+), 24 deletions(-)
(limited to 'tests')
diff --git a/tests/test_versioning.py b/tests/test_versioning.py
index cdd10065..553463d1 100644
--- a/tests/test_versioning.py
+++ b/tests/test_versioning.py
@@ -1,6 +1,5 @@
-from .utils import MockObject, MockQueryset, UsingURLPatterns
+from .utils import UsingURLPatterns
 from django.conf.urls import include, url
-from django.core.exceptions import ObjectDoesNotExist
 from rest_framework import serializers
 from rest_framework import status, versioning
 from rest_framework.decorators import APIView
@@ -33,8 +32,8 @@ class RequestInvalidVersionView(APIView):
 
 factory = APIRequestFactory()
 
-mock_view = lambda request: None
-dummy_view = lambda request, pk: None
+dummy_view = lambda request: None
+dummy_pk_view = lambda request, pk: None
 
 
 class TestRequestVersion:
@@ -112,14 +111,14 @@ class TestRequestVersion:
 
 class TestURLReversing(UsingURLPatterns, APITestCase):
     included = [
-        url(r'^namespaced/$', mock_view, name='another'),
-        url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
+        url(r'^namespaced/$', dummy_view, name='another'),
+        url(r'^example/(?P\d+)/$', dummy_pk_view, name='example-detail')
     ]
 
     urlpatterns = [
         url(r'^v1/', include(included, namespace='v1')),
-        url(r'^another/$', mock_view, name='another'),
-        url(r'^(?P[^/]+)/another/$', mock_view, name='another'),
+        url(r'^another/$', dummy_view, name='another'),
+        url(r'^(?P[^/]+)/another/$', dummy_view, name='another'),
     ]
 
     def test_reverse_unversioned(self):
@@ -230,7 +229,7 @@ class TestInvalidVersion:
 
 class TestHyperlinkedRelatedField(UsingURLPatterns, APITestCase):
     included = [
-        url(r'^namespaced/(?P\d+)/$', mock_view, name='namespaced'),
+        url(r'^namespaced/(?P\d+)/$', dummy_view, name='namespaced'),
     ]
 
     urlpatterns = [
@@ -241,28 +240,20 @@ class TestHyperlinkedRelatedField(UsingURLPatterns, APITestCase):
     def setUp(self):
         super(TestHyperlinkedRelatedField, self).setUp()
 
-        class HyperlinkedMockQueryset(MockQueryset):
-            def get(self, **lookup):
-                for item in self.items:
-                    if item.pk == int(lookup.get('pk', -1)):
-                        return item
-                raise ObjectDoesNotExist()
-
-        self.queryset = HyperlinkedMockQueryset([
-            MockObject(pk=1, name='foo'),
-            MockObject(pk=2, name='bar'),
-            MockObject(pk=3, name='baz')
-        ])
+        class MockQueryset(object):
+            def get(self, pk):
+                return 'object %s' % pk
+
         self.field = serializers.HyperlinkedRelatedField(
             view_name='namespaced',
-            queryset=self.queryset
+            queryset=MockQueryset()
         )
-        request = factory.post('/', urlconf='tests.test_versioning')
+        request = factory.get('/')
         request.versioning_scheme = NamespaceVersioning()
         request.version = 'v1'
         self.field._context = {'request': request}
 
     def test_bug_2489(self):
-        self.field.to_internal_value('/v1/namespaced/3/')
+        assert self.field.to_internal_value('/v1/namespaced/3/') == 'object 3'
         with pytest.raises(serializers.ValidationError):
             self.field.to_internal_value('/v2/namespaced/3/')
-- 
cgit v1.2.3
From 7f801b9a01fa7df3b081ddec803bd0d34cc3b35b Mon Sep 17 00:00:00 2001
From: José Padilla
Date: Fri, 6 Feb 2015 01:09:19 -0400
Subject: Add trim_whitespace to CharField #2517
If set to `True` then leading and trailing
whitespace is trimmed. Defaults to `True`.---
 tests/test_fields.py | 8 ++++++++
 1 file changed, 8 insertions(+)
(limited to 'tests')
diff --git a/tests/test_fields.py b/tests/test_fields.py
index 48ada780..5a5418e6 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -410,6 +410,14 @@ class TestCharField(FieldValues):
     }
     field = serializers.CharField()
 
+    def test_trim_whitespace_default(self):
+        field = serializers.CharField()
+        assert field.to_representation(' abc ') == 'abc'
+
+    def test_trim_whitespace_disabled(self):
+        field = serializers.CharField(trim_whitespace=False)
+        assert field.to_representation(' abc ') == ' abc '
+
 
 class TestEmailField(FieldValues):
     """
-- 
cgit v1.2.3
From dec3493d7c3ea630a4d51b21bb7e793f0e97ae99 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 6 Feb 2015 14:43:43 +0000
Subject: Minor cleanup
---
 tests/test_fields.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
(limited to 'tests')
diff --git a/tests/test_fields.py b/tests/test_fields.py
index 5a5418e6..ab3418bd 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -412,11 +412,11 @@ class TestCharField(FieldValues):
 
     def test_trim_whitespace_default(self):
         field = serializers.CharField()
-        assert field.to_representation(' abc ') == 'abc'
+        assert field.to_internal_value(' abc ') == 'abc'
 
     def test_trim_whitespace_disabled(self):
         field = serializers.CharField(trim_whitespace=False)
-        assert field.to_representation(' abc ') == ' abc '
+        assert field.to_internal_value(' abc ') == ' abc '
 
 
 class TestEmailField(FieldValues):
-- 
cgit v1.2.3