mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-22 09:36:49 +03:00
378b04eeaa
When only `ALLOWED_VERSIONS` but no `DEFAULT_VERSION` is specified, a version should be enforced.
411 lines
15 KiB
Python
411 lines
15 KiB
Python
import pytest
|
|
from django.conf.urls import include, url
|
|
from django.test import override_settings
|
|
|
|
from rest_framework import serializers, status, versioning
|
|
from rest_framework.decorators import APIView
|
|
from rest_framework.relations import PKOnlyObject
|
|
from rest_framework.response import Response
|
|
from rest_framework.reverse import reverse
|
|
from rest_framework.test import APIRequestFactory, APITestCase
|
|
from rest_framework.versioning import NamespaceVersioning
|
|
|
|
|
|
@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 = []
|
|
|
|
|
|
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)})
|
|
|
|
|
|
class AllowedVersionsView(RequestVersionView):
|
|
def determine_version(self, request, *args, **kwargs):
|
|
scheme = self.versioning_class()
|
|
scheme.allowed_versions = ('v1', 'v2')
|
|
return (scheme.determine_version(request, *args, **kwargs), scheme)
|
|
|
|
|
|
class AllowedAndDefaultVersionsView(RequestVersionView):
|
|
def determine_version(self, request, *args, **kwargs):
|
|
scheme = self.versioning_class()
|
|
scheme.allowed_versions = ('v1', 'v2')
|
|
scheme.default_version = 'v2'
|
|
return (scheme.determine_version(request, *args, **kwargs), scheme)
|
|
|
|
|
|
class AllowedWithNoneVersionsView(RequestVersionView):
|
|
def determine_version(self, request, *args, **kwargs):
|
|
scheme = self.versioning_class()
|
|
scheme.allowed_versions = ('v1', 'v2', None)
|
|
return (scheme.determine_version(request, *args, **kwargs), scheme)
|
|
|
|
|
|
class AllowedWithNoneAndDefaultVersionsView(RequestVersionView):
|
|
def determine_version(self, request, *args, **kwargs):
|
|
scheme = self.versioning_class()
|
|
scheme.allowed_versions = ('v1', 'v2', None)
|
|
scheme.default_version = 'v2'
|
|
return (scheme.determine_version(request, *args, **kwargs), scheme)
|
|
|
|
|
|
factory = APIRequestFactory()
|
|
|
|
|
|
def dummy_view(request):
|
|
pass
|
|
|
|
|
|
def dummy_pk_view(request, pk):
|
|
pass
|
|
|
|
|
|
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}
|
|
|
|
@override_settings(ALLOWED_HOSTS=['*'])
|
|
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='*/*; 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}
|
|
|
|
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(URLPatternsTestCase):
|
|
included = [
|
|
url(r'^namespaced/$', dummy_view, name='another'),
|
|
url(r'^example/(?P<pk>\d+)/$', dummy_pk_view, name='example-detail')
|
|
]
|
|
|
|
urlpatterns = [
|
|
url(r'^v1/', include(included, namespace='v1')),
|
|
url(r'^another/$', dummy_view, name='another'),
|
|
url(r'^(?P<version>[v1|v2]+)/another/$', dummy_view, name='another'),
|
|
]
|
|
|
|
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_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/'}
|
|
|
|
@override_settings(ALLOWED_HOSTS=['*'])
|
|
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/'}
|
|
|
|
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_reverse_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/'}
|
|
|
|
|
|
class TestInvalidVersion:
|
|
def test_invalid_query_param_versioning(self):
|
|
scheme = versioning.QueryParameterVersioning
|
|
view = AllowedVersionsView.as_view(versioning_class=scheme)
|
|
|
|
request = factory.get('/endpoint/?version=v3')
|
|
response = view(request)
|
|
assert response.status_code == status.HTTP_404_NOT_FOUND
|
|
|
|
@override_settings(ALLOWED_HOSTS=['*'])
|
|
def test_invalid_host_name_versioning(self):
|
|
scheme = versioning.HostNameVersioning
|
|
view = AllowedVersionsView.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 = AllowedVersionsView.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 = AllowedVersionsView.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 = AllowedVersionsView.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
|
|
|
|
|
|
class TestAllowedAndDefaultVersion:
|
|
def test_missing_without_default(self):
|
|
scheme = versioning.AcceptHeaderVersioning
|
|
view = AllowedVersionsView.as_view(versioning_class=scheme)
|
|
|
|
request = factory.get('/endpoint/', HTTP_ACCEPT='application/json')
|
|
response = view(request)
|
|
assert response.status_code == status.HTTP_406_NOT_ACCEPTABLE
|
|
|
|
def test_missing_with_default(self):
|
|
scheme = versioning.AcceptHeaderVersioning
|
|
view = AllowedAndDefaultVersionsView.as_view(versioning_class=scheme)
|
|
|
|
request = factory.get('/endpoint/', HTTP_ACCEPT='application/json')
|
|
response = view(request)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
assert response.data == {'version': 'v2'}
|
|
|
|
def test_with_default(self):
|
|
scheme = versioning.AcceptHeaderVersioning
|
|
view = AllowedAndDefaultVersionsView.as_view(versioning_class=scheme)
|
|
|
|
request = factory.get('/endpoint/',
|
|
HTTP_ACCEPT='application/json; version=v2')
|
|
response = view(request)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
def test_missing_without_default_but_none_allowed(self):
|
|
scheme = versioning.AcceptHeaderVersioning
|
|
view = AllowedWithNoneVersionsView.as_view(versioning_class=scheme)
|
|
|
|
request = factory.get('/endpoint/', HTTP_ACCEPT='application/json')
|
|
response = view(request)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
assert response.data == {'version': None}
|
|
|
|
def test_missing_with_default_and_none_allowed(self):
|
|
scheme = versioning.AcceptHeaderVersioning
|
|
view = AllowedWithNoneAndDefaultVersionsView.as_view(versioning_class=scheme)
|
|
|
|
request = factory.get('/endpoint/', HTTP_ACCEPT='application/json')
|
|
response = view(request)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
assert response.data == {'version': 'v2'}
|
|
|
|
|
|
class TestHyperlinkedRelatedField(URLPatternsTestCase):
|
|
included = [
|
|
url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_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 MockQueryset(object):
|
|
def get(self, pk):
|
|
return 'object %s' % pk
|
|
|
|
self.field = serializers.HyperlinkedRelatedField(
|
|
view_name='namespaced',
|
|
queryset=MockQueryset()
|
|
)
|
|
request = factory.get('/')
|
|
request.versioning_scheme = NamespaceVersioning()
|
|
request.version = 'v1'
|
|
self.field._context = {'request': request}
|
|
|
|
def test_bug_2489(self):
|
|
assert self.field.to_internal_value('/v1/namespaced/3/') == 'object 3'
|
|
with pytest.raises(serializers.ValidationError):
|
|
self.field.to_internal_value('/v2/namespaced/3/')
|
|
|
|
|
|
class TestNamespaceVersioningHyperlinkedRelatedFieldScheme(URLPatternsTestCase):
|
|
nested = [
|
|
url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_view, name='nested'),
|
|
]
|
|
included = [
|
|
url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_view, name='namespaced'),
|
|
url(r'^nested/', include(nested, namespace='nested-namespace'))
|
|
]
|
|
|
|
urlpatterns = [
|
|
url(r'^v1/', include(included, namespace='v1', app_name='restframeworkv1')),
|
|
url(r'^v2/', include(included, namespace='v2', app_name='restframeworkv2')),
|
|
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/'
|
|
|
|
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/'
|
|
|
|
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/'
|