diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_relations.py | 139 | ||||
| -rw-r--r-- | tests/utils.py | 53 |
2 files changed, 192 insertions, 0 deletions
diff --git a/tests/test_relations.py b/tests/test_relations.py index b1bc66b6..a3672117 100644 --- a/tests/test_relations.py +++ b/tests/test_relations.py @@ -1,3 +1,142 @@ +from .utils import mock_reverse, fail_reverse, BadType, MockObject, MockQueryset +from django.core.exceptions import ImproperlyConfigured, ValidationError +from rest_framework import serializers +from rest_framework.test import APISimpleTestCase +import pytest + + +class TestStringRelatedField(APISimpleTestCase): + def setUp(self): + self.instance = MockObject(pk=1, name='foo') + self.field = serializers.StringRelatedField() + + def test_string_related_representation(self): + representation = self.field.to_representation(self.instance) + assert representation == '<MockObject name=foo, pk=1>' + + +class TestPrimaryKeyRelatedField(APISimpleTestCase): + def setUp(self): + self.queryset = MockQueryset([ + MockObject(pk=1, name='foo'), + MockObject(pk=2, name='bar'), + MockObject(pk=3, name='baz') + ]) + self.instance = self.queryset.items[2] + self.field = serializers.PrimaryKeyRelatedField(queryset=self.queryset) + + def test_pk_related_lookup_exists(self): + instance = self.field.to_internal_value(self.instance.pk) + assert instance is self.instance + + def test_pk_related_lookup_does_not_exist(self): + with pytest.raises(ValidationError) as excinfo: + self.field.to_internal_value(4) + msg = excinfo.value.message + assert msg == "Invalid pk '4' - object does not exist." + + def test_pk_related_lookup_invalid_type(self): + with pytest.raises(ValidationError) as excinfo: + self.field.to_internal_value(BadType()) + msg = excinfo.value.message + assert msg == 'Incorrect type. Expected pk value, received BadType.' + + def test_pk_representation(self): + representation = self.field.to_representation(self.instance) + assert representation == self.instance.pk + + +class TestHyperlinkedIdentityField(APISimpleTestCase): + def setUp(self): + self.instance = MockObject(pk=1, name='foo') + self.field = serializers.HyperlinkedIdentityField(view_name='example') + self.field.reverse = mock_reverse + self.field.context = {'request': True} + + def test_representation(self): + representation = self.field.to_representation(self.instance) + assert representation == 'http://example.org/example/1/' + + def test_representation_unsaved_object(self): + representation = self.field.to_representation(MockObject(pk=None)) + assert representation is None + + def test_representation_with_format(self): + self.field.context['format'] = 'xml' + representation = self.field.to_representation(self.instance) + assert representation == 'http://example.org/example/1.xml/' + + def test_improperly_configured(self): + """ + If a matching view cannot be reversed with the given instance, + the the user has misconfigured something, as the URL conf and the + hyperlinked field do not match. + """ + self.field.reverse = fail_reverse + with pytest.raises(ImproperlyConfigured): + self.field.to_representation(self.instance) + + +class TestHyperlinkedIdentityFieldWithFormat(APISimpleTestCase): + """ + Tests for a hyperlinked identity field that has a `format` set, + which enforces that alternate formats are never linked too. + + Eg. If your API includes some endpoints that accept both `.xml` and `.json`, + but other endpoints that only accept `.json`, we allow for hyperlinked + relationships that enforce only a single suffix type. + """ + + def setUp(self): + self.instance = MockObject(pk=1, name='foo') + self.field = serializers.HyperlinkedIdentityField(view_name='example', format='json') + self.field.reverse = mock_reverse + self.field.context = {'request': True} + + def test_representation(self): + representation = self.field.to_representation(self.instance) + assert representation == 'http://example.org/example/1/' + + def test_representation_with_format(self): + self.field.context['format'] = 'xml' + representation = self.field.to_representation(self.instance) + assert representation == 'http://example.org/example/1.json/' + + +class TestSlugRelatedField(APISimpleTestCase): + def setUp(self): + self.queryset = MockQueryset([ + MockObject(pk=1, name='foo'), + MockObject(pk=2, name='bar'), + MockObject(pk=3, name='baz') + ]) + self.instance = self.queryset.items[2] + self.field = serializers.SlugRelatedField( + slug_field='name', queryset=self.queryset + ) + + def test_slug_related_lookup_exists(self): + instance = self.field.to_internal_value(self.instance.name) + assert instance is self.instance + + def test_slug_related_lookup_does_not_exist(self): + with pytest.raises(ValidationError) as excinfo: + self.field.to_internal_value('doesnotexist') + msg = excinfo.value.message + assert msg == 'Object with name=doesnotexist does not exist.' + + def test_slug_related_lookup_invalid_type(self): + with pytest.raises(ValidationError) as excinfo: + self.field.to_internal_value(BadType()) + msg = excinfo.value.message + assert msg == 'Invalid value.' + + def test_representation(self): + representation = self.field.to_representation(self.instance) + assert representation == self.instance.name + +# Older tests, for review... + # """ # General tests for relational fields. # """ diff --git a/tests/utils.py b/tests/utils.py index 28be81bd..5e902ba9 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,4 +1,6 @@ from contextlib import contextmanager +from django.core.exceptions import ObjectDoesNotExist +from django.core.urlresolvers import NoReverseMatch from django.utils import six from rest_framework.settings import api_settings @@ -23,3 +25,54 @@ def temporary_setting(setting, value, module=None): if module is not None: six.moves.reload_module(module) + + +class MockObject(object): + def __init__(self, **kwargs): + self._kwargs = kwargs + for key, val in kwargs.items(): + setattr(self, key, val) + + def __str__(self): + kwargs_str = ', '.join([ + '%s=%s' % (key, value) + for key, value in sorted(self._kwargs.items()) + ]) + return '<MockObject %s>' % kwargs_str + + +class MockQueryset(object): + def __init__(self, iterable): + self.items = iterable + + def get(self, **lookup): + for item in self.items: + if all([ + getattr(item, key, None) == value + for key, value in lookup.items() + ]): + return item + raise ObjectDoesNotExist() + + +class BadType(object): + """ + When used as a lookup with a `MockQueryset`, these objects + will raise a `TypeError`, as occurs in Django when making + queryset lookups with an incorrect type for the lookup value. + """ + def __eq__(self): + raise TypeError() + + +def mock_reverse(view_name, args=None, kwargs=None, request=None, format=None): + args = args or [] + kwargs = kwargs or {} + value = (args + list(kwargs.values()) + ['-'])[0] + prefix = 'http://example.org' if request else '' + suffix = ('.' + format) if (format is not None) else '' + return '%s/%s/%s%s/' % (prefix, view_name, value, suffix) + + +def fail_reverse(view_name, args=None, kwargs=None, request=None, format=None): + raise NoReverseMatch() |
