2015-06-25 23:55:51 +03:00
|
|
|
import pytest
|
2014-12-16 19:14:08 +03:00
|
|
|
from django.conf.urls import include, url
|
2016-06-01 17:31:00 +03:00
|
|
|
from django.test import override_settings
|
2015-06-25 23:55:51 +03:00
|
|
|
|
|
|
|
from rest_framework import serializers, status, versioning
|
2014-12-16 18:34:19 +03:00
|
|
|
from rest_framework.decorators import APIView
|
2015-06-25 23:55:51 +03:00
|
|
|
from rest_framework.relations import PKOnlyObject
|
2014-12-16 18:34:19 +03:00
|
|
|
from rest_framework.response import Response
|
|
|
|
from rest_framework.reverse import reverse
|
2015-02-05 03:58:09 +03:00
|
|
|
from rest_framework.test import APIRequestFactory, APITestCase
|
2015-02-03 13:14:38 +03:00
|
|
|
from rest_framework.versioning import NamespaceVersioning
|
2015-06-25 23:55:51 +03:00
|
|
|
|
2016-06-01 17:31:00 +03:00
|
|
|
|
|
|
|
@override_settings(ROOT_URLCONF='tests.test_versioning')
|
|
|
|
class URLPatternsTestCase(APITestCase):
|
|
|
|
"""
|
|
|
|
Isolates URL patterns used during testing on the test class itself.
|
|
|
|
For example:
|
|
|
|
|
|
|
|
class MyTestCase(URLPatternsTestCase):
|
|
|
|
urlpatterns = [
|
|
|
|
...
|
|
|
|
]
|
|
|
|
|
|
|
|
def test_something(self):
|
|
|
|
...
|
|
|
|
"""
|
|
|
|
def setUp(self):
|
|
|
|
global urlpatterns
|
|
|
|
urlpatterns = self.urlpatterns
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
global urlpatterns
|
|
|
|
urlpatterns = []
|
2014-12-16 18:34:19 +03:00
|
|
|
|
|
|
|
|
|
|
|
class RequestVersionView(APIView):
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
return Response({'version': request.version})
|
|
|
|
|
2014-12-16 19:14:08 +03:00
|
|
|
|
2014-12-16 18:34:19 +03:00
|
|
|
class ReverseView(APIView):
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
return Response({'url': reverse('another', request=request)})
|
|
|
|
|
|
|
|
|
2014-12-17 15:41:46 +03:00
|
|
|
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})
|
|
|
|
|
|
|
|
|
2014-12-16 18:34:19 +03:00
|
|
|
factory = APIRequestFactory()
|
|
|
|
|
2015-02-09 23:43:50 +03:00
|
|
|
|
|
|
|
def dummy_view(request):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def dummy_pk_view(request, pk):
|
|
|
|
pass
|
2014-12-16 18:34:19 +03:00
|
|
|
|
|
|
|
|
|
|
|
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}
|
|
|
|
|
2016-06-23 17:41:25 +03:00
|
|
|
@override_settings(ALLOWED_HOSTS=['*'])
|
2014-12-16 18:34:19 +03:00
|
|
|
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'}
|
|
|
|
|
2015-04-23 13:38:15 +03:00
|
|
|
request = factory.get('/endpoint/', HTTP_ACCEPT='*/*; version=1.2.3')
|
|
|
|
response = view(request)
|
|
|
|
assert response.data == {'version': '1.2.3'}
|
|
|
|
|
2014-12-16 18:34:19 +03:00
|
|
|
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}
|
|
|
|
|
2014-12-16 19:14:08 +03:00
|
|
|
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}
|
|
|
|
|
2014-12-16 18:34:19 +03:00
|
|
|
|
2016-06-01 17:31:00 +03:00
|
|
|
class TestURLReversing(URLPatternsTestCase):
|
2015-02-05 03:58:09 +03:00
|
|
|
included = [
|
2015-02-05 04:24:55 +03:00
|
|
|
url(r'^namespaced/$', dummy_view, name='another'),
|
|
|
|
url(r'^example/(?P<pk>\d+)/$', dummy_pk_view, name='example-detail')
|
2015-02-05 03:58:09 +03:00
|
|
|
]
|
|
|
|
|
|
|
|
urlpatterns = [
|
|
|
|
url(r'^v1/', include(included, namespace='v1')),
|
2015-02-05 04:24:55 +03:00
|
|
|
url(r'^another/$', dummy_view, name='another'),
|
2015-06-15 23:51:37 +03:00
|
|
|
url(r'^(?P<version>[v1|v2]+)/another/$', dummy_view, name='another'),
|
2015-02-05 03:58:09 +03:00
|
|
|
]
|
2014-12-16 18:34:19 +03:00
|
|
|
|
|
|
|
def test_reverse_unversioned(self):
|
|
|
|
view = ReverseView.as_view()
|
|
|
|
|
|
|
|
request = factory.get('/endpoint/')
|
|
|
|
response = view(request)
|
|
|
|
assert response.data == {'url': 'http://testserver/another/'}
|
|
|
|
|
2014-12-16 19:14:08 +03:00
|
|
|
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/'}
|
|
|
|
|
2016-06-23 17:41:25 +03:00
|
|
|
@override_settings(ALLOWED_HOSTS=['*'])
|
2014-12-16 18:34:19 +03:00
|
|
|
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/'}
|
2014-12-16 19:14:08 +03:00
|
|
|
|
|
|
|
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/'}
|
|
|
|
|
2014-12-17 15:41:46 +03:00
|
|
|
def test_reverse_namespace_versioning(self):
|
2014-12-16 19:14:08 +03:00
|
|
|
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/'}
|
2014-12-17 15:41:46 +03:00
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2016-06-23 17:41:25 +03:00
|
|
|
@override_settings(ALLOWED_HOSTS=['*'])
|
2014-12-17 15:41:46 +03:00
|
|
|
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
|
2015-02-03 13:14:38 +03:00
|
|
|
|
|
|
|
|
2016-06-01 17:31:00 +03:00
|
|
|
class TestHyperlinkedRelatedField(URLPatternsTestCase):
|
2015-02-05 03:58:09 +03:00
|
|
|
included = [
|
2015-03-20 00:14:48 +03:00
|
|
|
url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_view, name='namespaced'),
|
2015-02-05 03:58:09 +03:00
|
|
|
]
|
|
|
|
|
|
|
|
urlpatterns = [
|
|
|
|
url(r'^v1/', include(included, namespace='v1')),
|
|
|
|
url(r'^v2/', include(included, namespace='v2'))
|
|
|
|
]
|
2015-02-03 13:14:38 +03:00
|
|
|
|
|
|
|
def setUp(self):
|
2015-02-05 03:58:09 +03:00
|
|
|
super(TestHyperlinkedRelatedField, self).setUp()
|
2015-02-03 13:14:38 +03:00
|
|
|
|
2015-02-05 04:24:55 +03:00
|
|
|
class MockQueryset(object):
|
|
|
|
def get(self, pk):
|
|
|
|
return 'object %s' % pk
|
|
|
|
|
2015-02-03 13:14:38 +03:00
|
|
|
self.field = serializers.HyperlinkedRelatedField(
|
2015-02-05 03:58:09 +03:00
|
|
|
view_name='namespaced',
|
2015-02-05 04:24:55 +03:00
|
|
|
queryset=MockQueryset()
|
2015-02-03 13:14:38 +03:00
|
|
|
)
|
2015-02-05 04:24:55 +03:00
|
|
|
request = factory.get('/')
|
2015-02-03 13:14:38 +03:00
|
|
|
request.versioning_scheme = NamespaceVersioning()
|
2015-02-05 03:58:09 +03:00
|
|
|
request.version = 'v1'
|
2015-02-03 13:14:38 +03:00
|
|
|
self.field._context = {'request': request}
|
|
|
|
|
|
|
|
def test_bug_2489(self):
|
2015-02-05 04:24:55 +03:00
|
|
|
assert self.field.to_internal_value('/v1/namespaced/3/') == 'object 3'
|
2015-02-05 03:58:09 +03:00
|
|
|
with pytest.raises(serializers.ValidationError):
|
|
|
|
self.field.to_internal_value('/v2/namespaced/3/')
|
2015-03-20 00:14:48 +03:00
|
|
|
|
|
|
|
|
2016-06-01 17:31:00 +03:00
|
|
|
class TestNamespaceVersioningHyperlinkedRelatedFieldScheme(URLPatternsTestCase):
|
2016-06-23 18:00:11 +03:00
|
|
|
nested = [
|
|
|
|
url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_view, name='nested'),
|
|
|
|
]
|
2015-03-20 00:14:48 +03:00
|
|
|
included = [
|
|
|
|
url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_view, name='namespaced'),
|
2016-06-23 18:00:11 +03:00
|
|
|
url(r'^nested/', include(nested, namespace='nested-namespace'))
|
2015-03-20 00:14:48 +03:00
|
|
|
]
|
|
|
|
|
|
|
|
urlpatterns = [
|
2016-07-13 16:40:42 +03:00
|
|
|
url(r'^v1/', include(included, namespace='v1', app_name='restframeworkv1')),
|
|
|
|
url(r'^v2/', include(included, namespace='v2', app_name='restframeworkv2')),
|
2015-03-20 00:14:48 +03:00
|
|
|
url(r'^non-api/(?P<pk>\d+)/$', dummy_pk_view, name='non-api-view')
|
|
|
|
]
|
|
|
|
|
|
|
|
def _create_field(self, view_name, version):
|
|
|
|
request = factory.get("/")
|
|
|
|
request.versioning_scheme = NamespaceVersioning()
|
|
|
|
request.version = version
|
|
|
|
|
|
|
|
field = serializers.HyperlinkedRelatedField(
|
|
|
|
view_name=view_name,
|
|
|
|
read_only=True)
|
|
|
|
field._context = {'request': request}
|
|
|
|
return field
|
|
|
|
|
|
|
|
def test_api_url_is_properly_reversed_with_v1(self):
|
|
|
|
field = self._create_field('namespaced', 'v1')
|
|
|
|
assert field.to_representation(PKOnlyObject(3)) == 'http://testserver/v1/namespaced/3/'
|
|
|
|
|
|
|
|
def test_api_url_is_properly_reversed_with_v2(self):
|
|
|
|
field = self._create_field('namespaced', 'v2')
|
|
|
|
assert field.to_representation(PKOnlyObject(5)) == 'http://testserver/v2/namespaced/5/'
|
|
|
|
|
2016-06-23 18:00:11 +03:00
|
|
|
def test_api_url_is_properly_reversed_with_nested(self):
|
|
|
|
field = self._create_field('nested', 'v1:nested-namespace')
|
|
|
|
assert field.to_representation(PKOnlyObject(3)) == 'http://testserver/v1/nested/namespaced/3/'
|
|
|
|
|
2015-03-20 00:14:48 +03:00
|
|
|
def test_non_api_url_is_properly_reversed_regardless_of_the_version(self):
|
|
|
|
"""
|
|
|
|
Regression test for #2711
|
|
|
|
"""
|
|
|
|
field = self._create_field('non-api-view', 'v1')
|
|
|
|
assert field.to_representation(PKOnlyObject(10)) == 'http://testserver/non-api/10/'
|
|
|
|
|
|
|
|
field = self._create_field('non-api-view', 'v2')
|
|
|
|
assert field.to_representation(PKOnlyObject(10)) == 'http://testserver/non-api/10/'
|