diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/browsable_api/__init__.py | 0 | ||||
| -rw-r--r-- | tests/browsable_api/auth_urls.py | 10 | ||||
| -rw-r--r-- | tests/browsable_api/no_auth_urls.py | 9 | ||||
| -rw-r--r-- | tests/browsable_api/test_browsable_api.py | 65 | ||||
| -rw-r--r-- | tests/browsable_api/views.py | 15 | ||||
| -rw-r--r-- | tests/conftest.py | 1 | ||||
| -rw-r--r-- | tests/test_authentication.py | 12 | ||||
| -rw-r--r-- | tests/test_description.py | 24 | ||||
| -rw-r--r-- | tests/test_fields.py | 12 | ||||
| -rw-r--r-- | tests/test_filters.py | 69 | ||||
| -rw-r--r-- | tests/test_generics.py | 39 | ||||
| -rw-r--r-- | tests/test_pagination.py | 19 | ||||
| -rw-r--r-- | tests/test_parsers.py | 24 | ||||
| -rw-r--r-- | tests/test_routers.py | 18 | ||||
| -rw-r--r-- | tests/test_validation.py | 40 |
15 files changed, 343 insertions, 14 deletions
diff --git a/tests/browsable_api/__init__.py b/tests/browsable_api/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/browsable_api/__init__.py diff --git a/tests/browsable_api/auth_urls.py b/tests/browsable_api/auth_urls.py new file mode 100644 index 00000000..bce7dcf9 --- /dev/null +++ b/tests/browsable_api/auth_urls.py @@ -0,0 +1,10 @@ +from __future__ import unicode_literals +from django.conf.urls import patterns, url, include + +from .views import MockView + +urlpatterns = patterns( + '', + (r'^$', MockView.as_view()), + url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')), +) diff --git a/tests/browsable_api/no_auth_urls.py b/tests/browsable_api/no_auth_urls.py new file mode 100644 index 00000000..5e3604a6 --- /dev/null +++ b/tests/browsable_api/no_auth_urls.py @@ -0,0 +1,9 @@ +from __future__ import unicode_literals +from django.conf.urls import patterns + +from .views import MockView + +urlpatterns = patterns( + '', + (r'^$', MockView.as_view()), +) diff --git a/tests/browsable_api/test_browsable_api.py b/tests/browsable_api/test_browsable_api.py new file mode 100644 index 00000000..5f264783 --- /dev/null +++ b/tests/browsable_api/test_browsable_api.py @@ -0,0 +1,65 @@ +from __future__ import unicode_literals +from django.contrib.auth.models import User +from django.test import TestCase + +from rest_framework.test import APIClient + + +class DropdownWithAuthTests(TestCase): + """Tests correct dropdown behaviour with Auth views enabled.""" + + urls = 'tests.browsable_api.auth_urls' + + def setUp(self): + self.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) + + def tearDown(self): + self.client.logout() + + def test_name_shown_when_logged_in(self): + self.client.login(username=self.username, password=self.password) + response = self.client.get('/') + self.assertContains(response, 'john') + + def test_logout_shown_when_logged_in(self): + self.client.login(username=self.username, password=self.password) + response = self.client.get('/') + self.assertContains(response, '>Log out<') + + def test_login_shown_when_logged_out(self): + response = self.client.get('/') + self.assertContains(response, '>Log in<') + + +class NoDropdownWithoutAuthTests(TestCase): + """Tests correct dropdown behaviour with Auth views NOT enabled.""" + + urls = 'tests.browsable_api.no_auth_urls' + + def setUp(self): + self.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) + + def tearDown(self): + self.client.logout() + + def test_name_shown_when_logged_in(self): + self.client.login(username=self.username, password=self.password) + response = self.client.get('/') + self.assertContains(response, 'john') + + def test_dropdown_not_shown_when_logged_in(self): + self.client.login(username=self.username, password=self.password) + response = self.client.get('/') + self.assertNotContains(response, '<li class="dropdown">') + + def test_dropdown_not_shown_when_logged_out(self): + response = self.client.get('/') + self.assertNotContains(response, '<li class="dropdown">') diff --git a/tests/browsable_api/views.py b/tests/browsable_api/views.py new file mode 100644 index 00000000..000f4e80 --- /dev/null +++ b/tests/browsable_api/views.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals + +from rest_framework.views import APIView +from rest_framework import authentication +from rest_framework import renderers +from rest_framework.response import Response + + +class MockView(APIView): + + authentication_classes = (authentication.SessionAuthentication,) + renderer_classes = (renderers.BrowsableAPIRenderer,) + + def get(self, request): + return Response({'a': 1, 'b': 2, 'c': 3}) diff --git a/tests/conftest.py b/tests/conftest.py index f3723aea..4b33e19c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,7 @@ def pytest_configure(): DEBUG_PROPAGATE_EXCEPTIONS=True, DATABASES={'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}}, + SITE_ID=1, SECRET_KEY='not very secret in tests', USE_I18N=True, USE_L10N=True, diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 2b9d73e4..32041f9c 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -57,7 +57,8 @@ urlpatterns = patterns( authentication_classes=[OAuthAuthentication], permission_classes=[permissions.TokenHasReadWriteScope] ) - ) + ), + url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')) ) @@ -134,6 +135,15 @@ class SessionAuthTests(TestCase): def tearDown(self): self.csrf_client.logout() + def test_login_view_renders_on_get(self): + """ + Ensure the login template renders for a basic GET. + + cf. [#1810](https://github.com/tomchristie/django-rest-framework/pull/1810) + """ + response = self.csrf_client.get('/auth/login/') + self.assertContains(response, '<Label class="span4">Username:</label>') + def test_post_form_session_auth_failing_csrf(self): """ Ensure POSTing form over session authentication without CSRF token fails. diff --git a/tests/test_description.py b/tests/test_description.py index 1e481f06..0675d209 100644 --- a/tests/test_description.py +++ b/tests/test_description.py @@ -98,6 +98,30 @@ class TestViewNamesAndDescriptions(TestCase): pass self.assertEqual(MockView().get_view_description(), '') + def test_view_description_can_be_promise(self): + """ + Ensure a view may have a docstring that is actually a lazily evaluated + class that can be converted to a string. + + See: https://github.com/tomchristie/django-rest-framework/issues/1708 + """ + # use a mock object instead of gettext_lazy to ensure that we can't end + # up with a test case string in our l10n catalog + class MockLazyStr(object): + def __init__(self, string): + self.s = string + + def __str__(self): + return self.s + + def __unicode__(self): + return self.s + + class MockView(APIView): + __doc__ = MockLazyStr("a gettext string") + + self.assertEqual(MockView().get_view_description(), 'a gettext string') + def test_markdown(self): """ Ensure markdown to HTML works as expected. diff --git a/tests/test_fields.py b/tests/test_fields.py index 094ac1eb..0ddbe48b 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1004,6 +1004,18 @@ class BooleanField(TestCase): self.assertFalse(BooleanRequiredSerializer(data={}).is_valid()) +class ModelCharField(TestCase): + """ + Tests for CharField + """ + def test_none_serializing(self): + class CharFieldSerializer(serializers.Serializer): + char = serializers.CharField(allow_none=True, required=False) + serializer = CharFieldSerializer(data={'char': None}) + self.assertTrue(serializer.is_valid()) + self.assertIsNone(serializer.object['char']) + + class SerializerMethodFieldTest(TestCase): """ Tests for the SerializerMethodField field_to_native() behavior diff --git a/tests/test_filters.py b/tests/test_filters.py index 47bffd43..5722fd7c 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -408,16 +408,61 @@ class SearchFilterTests(TestCase): ) -class OrdringFilterModel(models.Model): +class OrderingFilterModel(models.Model): title = models.CharField(max_length=20) text = models.CharField(max_length=100) class OrderingFilterRelatedModel(models.Model): - related_object = models.ForeignKey(OrdringFilterModel, + related_object = models.ForeignKey(OrderingFilterModel, related_name="relateds") +class DjangoFilterOrderingModel(models.Model): + date = models.DateField() + text = models.CharField(max_length=10) + + class Meta: + ordering = ['-date'] + + +class DjangoFilterOrderingTests(TestCase): + def setUp(self): + data = [{ + 'date': datetime.date(2012, 10, 8), + 'text': 'abc' + }, { + 'date': datetime.date(2013, 10, 8), + 'text': 'bcd' + }, { + 'date': datetime.date(2014, 10, 8), + 'text': 'cde' + }] + + for d in data: + DjangoFilterOrderingModel.objects.create(**d) + + def test_default_ordering(self): + class DjangoFilterOrderingView(generics.ListAPIView): + model = DjangoFilterOrderingModel + filter_backends = (filters.DjangoFilterBackend,) + filter_fields = ['text'] + ordering = ('-date',) + + view = DjangoFilterOrderingView.as_view() + request = factory.get('/') + response = view(request) + + self.assertEqual( + response.data, + [ + {'id': 3, 'date': datetime.date(2014, 10, 8), 'text': 'cde'}, + {'id': 2, 'date': datetime.date(2013, 10, 8), 'text': 'bcd'}, + {'id': 1, 'date': datetime.date(2012, 10, 8), 'text': 'abc'} + ] + ) + + class OrderingFilterTests(TestCase): def setUp(self): # Sequence of title/text is: @@ -436,11 +481,11 @@ class OrderingFilterTests(TestCase): chr(idx + ord('b')) + chr(idx + ord('c')) ) - OrdringFilterModel(title=title, text=text).save() + OrderingFilterModel(title=title, text=text).save() def test_ordering(self): class OrderingListView(generics.ListAPIView): - model = OrdringFilterModel + model = OrderingFilterModel filter_backends = (filters.OrderingFilter,) ordering = ('title',) ordering_fields = ('text',) @@ -459,7 +504,7 @@ class OrderingFilterTests(TestCase): def test_reverse_ordering(self): class OrderingListView(generics.ListAPIView): - model = OrdringFilterModel + model = OrderingFilterModel filter_backends = (filters.OrderingFilter,) ordering = ('title',) ordering_fields = ('text',) @@ -478,7 +523,7 @@ class OrderingFilterTests(TestCase): def test_incorrectfield_ordering(self): class OrderingListView(generics.ListAPIView): - model = OrdringFilterModel + model = OrderingFilterModel filter_backends = (filters.OrderingFilter,) ordering = ('title',) ordering_fields = ('text',) @@ -497,7 +542,7 @@ class OrderingFilterTests(TestCase): def test_default_ordering(self): class OrderingListView(generics.ListAPIView): - model = OrdringFilterModel + model = OrderingFilterModel filter_backends = (filters.OrderingFilter,) ordering = ('title',) oredering_fields = ('text',) @@ -516,7 +561,7 @@ class OrderingFilterTests(TestCase): def test_default_ordering_using_string(self): class OrderingListView(generics.ListAPIView): - model = OrdringFilterModel + model = OrderingFilterModel filter_backends = (filters.OrderingFilter,) ordering = 'title' ordering_fields = ('text',) @@ -536,7 +581,7 @@ class OrderingFilterTests(TestCase): def test_ordering_by_aggregate_field(self): # create some related models to aggregate order by num_objs = [2, 5, 3] - for obj, num_relateds in zip(OrdringFilterModel.objects.all(), + for obj, num_relateds in zip(OrderingFilterModel.objects.all(), num_objs): for _ in range(num_relateds): new_related = OrderingFilterRelatedModel( @@ -545,11 +590,11 @@ class OrderingFilterTests(TestCase): new_related.save() class OrderingListView(generics.ListAPIView): - model = OrdringFilterModel + model = OrderingFilterModel filter_backends = (filters.OrderingFilter,) ordering = 'title' ordering_fields = '__all__' - queryset = OrdringFilterModel.objects.all().annotate( + queryset = OrderingFilterModel.objects.all().annotate( models.Count("relateds")) view = OrderingListView.as_view() @@ -567,7 +612,7 @@ class OrderingFilterTests(TestCase): def test_ordering_with_nonstandard_ordering_param(self): with temporary_setting('ORDERING_PARAM', 'order', filters): class OrderingListView(generics.ListAPIView): - model = OrdringFilterModel + model = OrderingFilterModel filter_backends = (filters.OrderingFilter,) ordering = ('title',) ordering_fields = ('text',) diff --git a/tests/test_generics.py b/tests/test_generics.py index e9f5bebd..97116349 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -681,3 +681,42 @@ class TestFilterBackendAppliedToViews(TestCase): response = view(request).render() self.assertContains(response, 'field_b') self.assertNotContains(response, 'field_a') + + def test_options_with_dynamic_serializer(self): + """ + Ensure that OPTIONS returns correct POST json schema: + DynamicSerializer with single field 'field_b' + """ + request = factory.options('/') + view = DynamicSerializerView.as_view() + + with self.assertNumQueries(0): + response = view(request).render() + + expected = { + 'name': 'Dynamic Serializer', + 'description': '', + 'renders': [ + 'text/html', + 'application/json' + ], + 'parses': [ + 'application/json', + 'application/x-www-form-urlencoded', + 'multipart/form-data' + ], + 'actions': { + 'POST': { + 'field_b': { + 'type': 'string', + 'required': True, + 'read_only': False, + 'label': 'field b', + 'max_length': 100 + } + } + } + } + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data, expected) diff --git a/tests/test_pagination.py b/tests/test_pagination.py index 80c33e2e..e1c2528b 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -412,6 +412,15 @@ class CustomPaginationSerializer(pagination.BasePaginationSerializer): 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'] @@ -434,6 +443,16 @@ class TestCustomPaginationSerializer(TestCase): } 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): diff --git a/tests/test_parsers.py b/tests/test_parsers.py index 8af90677..3f2672df 100644 --- a/tests/test_parsers.py +++ b/tests/test_parsers.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + from __future__ import unicode_literals from rest_framework.compat import StringIO from django import forms @@ -113,3 +115,25 @@ class TestFileUploadParser(TestCase): parser = FileUploadParser() filename = parser.get_filename(self.stream, None, self.parser_context) self.assertEqual(filename, 'file.txt') + + def test_get_encoded_filename(self): + parser = FileUploadParser() + + self.__replace_content_disposition('inline; filename*=utf-8\'\'ÀĥƦ.txt') + filename = parser.get_filename(self.stream, None, self.parser_context) + self.assertEqual(filename, 'ÀĥƦ.txt') + + self.__replace_content_disposition('inline; filename=fallback.txt; filename*=utf-8\'\'ÀĥƦ.txt') + filename = parser.get_filename(self.stream, None, self.parser_context) + self.assertEqual(filename, 'ÀĥƦ.txt') + + self.__replace_content_disposition('inline; filename=fallback.txt; filename*=utf-8\'en-us\'ÀĥƦ.txt') + filename = parser.get_filename(self.stream, None, self.parser_context) + self.assertEqual(filename, 'ÀĥƦ.txt') + + self.__replace_content_disposition('inline; filename=fallback.txt; filename*=utf-8--ÀĥƦ.txt') + filename = parser.get_filename(self.stream, None, self.parser_context) + self.assertEqual(filename, 'fallback.txt') + + def __replace_content_disposition(self, disposition): + self.parser_context['request'].META['HTTP_CONTENT_DISPOSITION'] = disposition diff --git a/tests/test_routers.py b/tests/test_routers.py index b076f134..f6f5a977 100644 --- a/tests/test_routers.py +++ b/tests/test_routers.py @@ -3,7 +3,7 @@ from django.conf.urls import patterns, url, include from django.db import models from django.test import TestCase from django.core.exceptions import ImproperlyConfigured -from rest_framework import serializers, viewsets, permissions +from rest_framework import serializers, viewsets, mixins, permissions from rest_framework.decorators import detail_route, list_route from rest_framework.response import Response from rest_framework.routers import SimpleRouter, DefaultRouter @@ -284,3 +284,19 @@ class TestDynamicListAndDetailRouter(TestCase): else: method_map = 'get' self.assertEqual(route.mapping[method_map], endpoint) + + +class TestRootWithAListlessViewset(TestCase): + def setUp(self): + class NoteViewSet(mixins.RetrieveModelMixin, + viewsets.GenericViewSet): + model = RouterTestModel + + self.router = DefaultRouter() + self.router.register(r'notes', NoteViewSet) + self.view = self.router.urls[0].callback + + def test_api_root(self): + request = factory.get('/') + response = self.view(request) + self.assertEqual(response.data, {}) diff --git a/tests/test_validation.py b/tests/test_validation.py index e13e4078..a46e38ac 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals from django.core.validators import MaxValueValidator +from django.core.exceptions import ValidationError from django.db import models from django.test import TestCase from rest_framework import generics, serializers, status @@ -146,3 +147,42 @@ class TestMaxValueValidatorValidation(TestCase): response = view(request, pk=obj.pk).render() self.assertEqual(response.content, b'{"number_value": ["Ensure this value is less than or equal to 100."]}') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + +class TestChoiceFieldChoicesValidate(TestCase): + CHOICES = [ + (0, 'Small'), + (1, 'Medium'), + (2, 'Large'), + ] + + CHOICES_NESTED = [ + ('Category', ( + (1, 'First'), + (2, 'Second'), + (3, 'Third'), + )), + (4, 'Fourth'), + ] + + def test_choices(self): + """ + Make sure a value for choices works as expected. + """ + f = serializers.ChoiceField(choices=self.CHOICES) + value = self.CHOICES[0][0] + try: + f.validate(value) + except ValidationError: + self.fail("Value %s does not validate" % str(value)) + + def test_nested_choices(self): + """ + Make sure a nested value for choices works as expected. + """ + f = serializers.ChoiceField(choices=self.CHOICES_NESTED) + value = self.CHOICES_NESTED[0][1][0][0] + try: + f.validate(value) + except ValidationError: + self.fail("Value %s does not validate" % str(value)) |
