validators tests -> resources tests

This commit is contained in:
Tom Christie 2011-05-16 16:52:39 +01:00
parent 3470134373
commit 3039f6f6c2
2 changed files with 81 additions and 68 deletions

View File

@ -188,9 +188,13 @@ class FormResource(Resource):
def _validate(self, data, files, allowed_extra_fields=(), fake_data=None): def _validate(self, data, files, allowed_extra_fields=(), fake_data=None):
""" """
Wrapped by validate to hide the extra_fields option that the ModelValidatorMixin uses. Wrapped by validate to hide the extra flags that are used in the implementation.
extra_fields is a list of fields which are not defined by the form, but which we still
allowed_extra_fields is a list of fields which are not defined by the form, but which we still
expect to see on the input. expect to see on the input.
fake_data is a string that should be used as an extra key, as a kludge to force .errors
to be populated when an empty dict is supplied in `data`
""" """
# We'd like nice error messages even if no content is supplied. # We'd like nice error messages even if no content is supplied.
@ -369,7 +373,7 @@ class ModelResource(FormResource):
if self.form: if self.form:
# Use explict Form # Use explict Form
return super(ModelFormValidator, self).get_bound_form(data, files) return super(ModelResource, self).get_bound_form(data, files)
elif self.model: elif self.model:
# Fall back to ModelForm which we create on the fly # Fall back to ModelForm which we create on the fly

View File

@ -2,62 +2,62 @@ from django import forms
from django.db import models from django.db import models
from django.test import TestCase from django.test import TestCase
from djangorestframework.compat import RequestFactory from djangorestframework.compat import RequestFactory
from djangorestframework.validators import BaseValidator, FormValidator, ModelFormValidator from djangorestframework.resources import Resource, FormResource, ModelResource
from djangorestframework.response import ErrorResponse from djangorestframework.response import ErrorResponse
from djangorestframework.views import BaseView from djangorestframework.views import BaseView
from djangorestframework.resources import Resource from djangorestframework.resources import Resource
class TestValidatorMixinInterfaces(TestCase):
"""Basic tests to ensure that the ValidatorMixin classes expose the expected interfaces"""
def test_validator_mixin_interface(self):
"""Ensure the ValidatorMixin base class interface is as expected."""
self.assertRaises(NotImplementedError, BaseValidator(None).validate, None)
class TestDisabledValidations(TestCase): class TestDisabledValidations(TestCase):
"""Tests on FormValidator with validation disabled by setting form to None""" """Tests on FormValidator with validation disabled by setting form to None"""
def test_disabled_form_validator_returns_content_unchanged(self): def test_disabled_form_validator_returns_content_unchanged(self):
"""If the view's form attribute is None then FormValidator(view).validate(content) """If the view's form attribute is None then FormValidator(view).validate_request(content, None)
should just return the content unmodified.""" should just return the content unmodified."""
class DisabledFormView(BaseView): class DisabledFormResource(FormResource):
form = None form = None
view = DisabledFormView() class MockView(BaseView):
resource = DisabledFormResource
view = MockView()
content = {'qwerty':'uiop'} content = {'qwerty':'uiop'}
self.assertEqual(FormValidator(view).validate(content), content) self.assertEqual(FormResource(view).validate_request(content, None), content)
def test_disabled_form_validator_get_bound_form_returns_none(self): def test_disabled_form_validator_get_bound_form_returns_none(self):
"""If the view's form attribute is None on then """If the view's form attribute is None on then
FormValidator(view).get_bound_form(content) should just return None.""" FormValidator(view).get_bound_form(content) should just return None."""
class DisabledFormView(BaseView): class DisabledFormResource(FormResource):
form = None form = None
view = DisabledFormView() class MockView(BaseView):
resource = DisabledFormResource
view = MockView()
content = {'qwerty':'uiop'} content = {'qwerty':'uiop'}
self.assertEqual(FormValidator(view).get_bound_form(content), None) self.assertEqual(FormResource(view).get_bound_form(content), None)
def test_disabled_model_form_validator_returns_content_unchanged(self): def test_disabled_model_form_validator_returns_content_unchanged(self):
"""If the view's form is None and does not have a Resource with a model set then """If the view's form is None and does not have a Resource with a model set then
ModelFormValidator(view).validate(content) should just return the content unmodified.""" ModelFormValidator(view).validate_request(content, None) should just return the content unmodified."""
class DisabledModelFormView(BaseView): class DisabledModelFormView(BaseView):
form = None resource = ModelResource
view = DisabledModelFormView() view = DisabledModelFormView()
content = {'qwerty':'uiop'} content = {'qwerty':'uiop'}
self.assertEqual(ModelFormValidator(view).get_bound_form(content), None)# self.assertEqual(ModelResource(view).get_bound_form(content), None)#
def test_disabled_model_form_validator_get_bound_form_returns_none(self): def test_disabled_model_form_validator_get_bound_form_returns_none(self):
"""If the form attribute is None on FormValidatorMixin then get_bound_form(content) should just return None.""" """If the form attribute is None on FormValidatorMixin then get_bound_form(content) should just return None."""
class DisabledModelFormView(BaseView): class DisabledModelFormView(BaseView):
model = None resource = ModelResource
view = DisabledModelFormView() view = DisabledModelFormView()
content = {'qwerty':'uiop'} content = {'qwerty':'uiop'}
self.assertEqual(ModelFormValidator(view).get_bound_form(content), None) self.assertEqual(ModelResource(view).get_bound_form(content), None)
class TestNonFieldErrors(TestCase): class TestNonFieldErrors(TestCase):
"""Tests against form validation errors caused by non-field errors. (eg as might be caused by some custom form validation)""" """Tests against form validation errors caused by non-field errors. (eg as might be caused by some custom form validation)"""
@ -74,13 +74,16 @@ class TestNonFieldErrors(TestCase):
raise forms.ValidationError(self.ERROR_TEXT) raise forms.ValidationError(self.ERROR_TEXT)
return self.cleaned_data #pragma: no cover return self.cleaned_data #pragma: no cover
class MockView(object): class MockResource(FormResource):
form = MockForm form = MockForm
class MockView(BaseView):
pass
view = MockView() view = MockView()
content = {'field1': 'example1', 'field2': 'example2'} content = {'field1': 'example1', 'field2': 'example2'}
try: try:
FormValidator(view).validate(content) MockResource(view).validate_request(content, None)
except ErrorResponse, exc: except ErrorResponse, exc:
self.assertEqual(exc.response.raw_content, {'errors': [MockForm.ERROR_TEXT]}) self.assertEqual(exc.response.raw_content, {'errors': [MockForm.ERROR_TEXT]})
else: else:
@ -95,14 +98,20 @@ class TestFormValidation(TestCase):
class MockForm(forms.Form): class MockForm(forms.Form):
qwerty = forms.CharField(required=True) qwerty = forms.CharField(required=True)
class MockFormView(BaseView): class MockFormResource(FormResource):
form = MockForm form = MockForm
validators = (FormValidator,)
class MockModelResource(ModelResource):
form = MockForm
class MockFormView(BaseView):
resource = MockFormResource
class MockModelFormView(BaseView): class MockModelFormView(BaseView):
form = MockForm resource = MockModelResource
validators = (ModelFormValidator,)
self.MockFormResource = MockFormResource
self.MockModelResource = MockModelResource
self.MockFormView = MockFormView self.MockFormView = MockFormView
self.MockModelFormView = MockModelFormView self.MockModelFormView = MockModelFormView
@ -110,35 +119,35 @@ class TestFormValidation(TestCase):
def validation_returns_content_unchanged_if_already_valid_and_clean(self, validator): def validation_returns_content_unchanged_if_already_valid_and_clean(self, validator):
"""If the content is already valid and clean then validate(content) should just return the content unmodified.""" """If the content is already valid and clean then validate(content) should just return the content unmodified."""
content = {'qwerty':'uiop'} content = {'qwerty':'uiop'}
self.assertEqual(validator.validate(content), content) self.assertEqual(validator.validate_request(content, None), content)
def validation_failure_raises_response_exception(self, validator): def validation_failure_raises_response_exception(self, validator):
"""If form validation fails a ResourceException 400 (Bad Request) should be raised.""" """If form validation fails a ResourceException 400 (Bad Request) should be raised."""
content = {} content = {}
self.assertRaises(ErrorResponse, validator.validate, content) self.assertRaises(ErrorResponse, validator.validate_request, content, None)
def validation_does_not_allow_extra_fields_by_default(self, validator): def validation_does_not_allow_extra_fields_by_default(self, validator):
"""If some (otherwise valid) content includes fields that are not in the form then validation should fail. """If some (otherwise valid) content includes fields that are not in the form then validation should fail.
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
broken clients more easily (eg submitting content with a misnamed field)""" broken clients more easily (eg submitting content with a misnamed field)"""
content = {'qwerty': 'uiop', 'extra': 'extra'} content = {'qwerty': 'uiop', 'extra': 'extra'}
self.assertRaises(ErrorResponse, validator.validate, content) self.assertRaises(ErrorResponse, validator.validate_request, content, None)
def validation_allows_extra_fields_if_explicitly_set(self, validator): def validation_allows_extra_fields_if_explicitly_set(self, validator):
"""If we include an allowed_extra_fields paramater on _validate, then allow fields with those names.""" """If we include an allowed_extra_fields paramater on _validate, then allow fields with those names."""
content = {'qwerty': 'uiop', 'extra': 'extra'} content = {'qwerty': 'uiop', 'extra': 'extra'}
validator._validate(content, allowed_extra_fields=('extra',)) validator._validate(content, None, allowed_extra_fields=('extra',))
def validation_does_not_require_extra_fields_if_explicitly_set(self, validator): def validation_does_not_require_extra_fields_if_explicitly_set(self, validator):
"""If we include an allowed_extra_fields paramater on _validate, then do not fail if we do not have fields with those names.""" """If we include an allowed_extra_fields paramater on _validate, then do not fail if we do not have fields with those names."""
content = {'qwerty': 'uiop'} content = {'qwerty': 'uiop'}
self.assertEqual(validator._validate(content, allowed_extra_fields=('extra',)), content) self.assertEqual(validator._validate(content, None, allowed_extra_fields=('extra',)), content)
def validation_failed_due_to_no_content_returns_appropriate_message(self, validator): def validation_failed_due_to_no_content_returns_appropriate_message(self, validator):
"""If validation fails due to no content, ensure the response contains a single non-field error""" """If validation fails due to no content, ensure the response contains a single non-field error"""
content = {} content = {}
try: try:
validator.validate(content) validator.validate_request(content, None)
except ErrorResponse, exc: except ErrorResponse, exc:
self.assertEqual(exc.response.raw_content, {'field-errors': {'qwerty': ['This field is required.']}}) self.assertEqual(exc.response.raw_content, {'field-errors': {'qwerty': ['This field is required.']}})
else: else:
@ -148,7 +157,7 @@ class TestFormValidation(TestCase):
"""If validation fails due to a field error, ensure the response contains a single field error""" """If validation fails due to a field error, ensure the response contains a single field error"""
content = {'qwerty': ''} content = {'qwerty': ''}
try: try:
validator.validate(content) validator.validate_request(content, None)
except ErrorResponse, exc: except ErrorResponse, exc:
self.assertEqual(exc.response.raw_content, {'field-errors': {'qwerty': ['This field is required.']}}) self.assertEqual(exc.response.raw_content, {'field-errors': {'qwerty': ['This field is required.']}})
else: else:
@ -158,7 +167,7 @@ class TestFormValidation(TestCase):
"""If validation fails due to an invalid field, ensure the response contains a single field error""" """If validation fails due to an invalid field, ensure the response contains a single field error"""
content = {'qwerty': 'uiop', 'extra': 'extra'} content = {'qwerty': 'uiop', 'extra': 'extra'}
try: try:
validator.validate(content) validator.validate_request(content, None)
except ErrorResponse, exc: except ErrorResponse, exc:
self.assertEqual(exc.response.raw_content, {'field-errors': {'extra': ['This field does not exist.']}}) self.assertEqual(exc.response.raw_content, {'field-errors': {'extra': ['This field does not exist.']}})
else: else:
@ -168,87 +177,87 @@ class TestFormValidation(TestCase):
"""If validation for multiple reasons, ensure the response contains each error""" """If validation for multiple reasons, ensure the response contains each error"""
content = {'qwerty': '', 'extra': 'extra'} content = {'qwerty': '', 'extra': 'extra'}
try: try:
validator.validate(content) validator.validate_request(content, None)
except ErrorResponse, exc: except ErrorResponse, exc:
self.assertEqual(exc.response.raw_content, {'field-errors': {'qwerty': ['This field is required.'], self.assertEqual(exc.response.raw_content, {'field-errors': {'qwerty': ['This field is required.'],
'extra': ['This field does not exist.']}}) 'extra': ['This field does not exist.']}})
else: else:
self.fail('ResourceException was not raised') #pragma: no cover self.fail('ResourceException was not raised') #pragma: no cover
# Tests on FormValidtionMixin # Tests on FormResource
def test_form_validation_returns_content_unchanged_if_already_valid_and_clean(self): def test_form_validation_returns_content_unchanged_if_already_valid_and_clean(self):
validator = FormValidator(self.MockFormView()) validator = self.MockFormResource(self.MockFormView())
self.validation_returns_content_unchanged_if_already_valid_and_clean(validator) self.validation_returns_content_unchanged_if_already_valid_and_clean(validator)
def test_form_validation_failure_raises_response_exception(self): def test_form_validation_failure_raises_response_exception(self):
validator = FormValidator(self.MockFormView()) validator = self.MockFormResource(self.MockFormView())
self.validation_failure_raises_response_exception(validator) self.validation_failure_raises_response_exception(validator)
def test_validation_does_not_allow_extra_fields_by_default(self): def test_validation_does_not_allow_extra_fields_by_default(self):
validator = FormValidator(self.MockFormView()) validator = self.MockFormResource(self.MockFormView())
self.validation_does_not_allow_extra_fields_by_default(validator) self.validation_does_not_allow_extra_fields_by_default(validator)
def test_validation_allows_extra_fields_if_explicitly_set(self): def test_validation_allows_extra_fields_if_explicitly_set(self):
validator = FormValidator(self.MockFormView()) validator = self.MockFormResource(self.MockFormView())
self.validation_allows_extra_fields_if_explicitly_set(validator) self.validation_allows_extra_fields_if_explicitly_set(validator)
def test_validation_does_not_require_extra_fields_if_explicitly_set(self): def test_validation_does_not_require_extra_fields_if_explicitly_set(self):
validator = FormValidator(self.MockFormView()) validator = self.MockFormResource(self.MockFormView())
self.validation_does_not_require_extra_fields_if_explicitly_set(validator) self.validation_does_not_require_extra_fields_if_explicitly_set(validator)
def test_validation_failed_due_to_no_content_returns_appropriate_message(self): def test_validation_failed_due_to_no_content_returns_appropriate_message(self):
validator = FormValidator(self.MockFormView()) validator = self.MockFormResource(self.MockFormView())
self.validation_failed_due_to_no_content_returns_appropriate_message(validator) self.validation_failed_due_to_no_content_returns_appropriate_message(validator)
def test_validation_failed_due_to_field_error_returns_appropriate_message(self): def test_validation_failed_due_to_field_error_returns_appropriate_message(self):
validator = FormValidator(self.MockFormView()) validator = self.MockFormResource(self.MockFormView())
self.validation_failed_due_to_field_error_returns_appropriate_message(validator) self.validation_failed_due_to_field_error_returns_appropriate_message(validator)
def test_validation_failed_due_to_invalid_field_returns_appropriate_message(self): def test_validation_failed_due_to_invalid_field_returns_appropriate_message(self):
validator = FormValidator(self.MockFormView()) validator = self.MockFormResource(self.MockFormView())
self.validation_failed_due_to_invalid_field_returns_appropriate_message(validator) self.validation_failed_due_to_invalid_field_returns_appropriate_message(validator)
def test_validation_failed_due_to_multiple_errors_returns_appropriate_message(self): def test_validation_failed_due_to_multiple_errors_returns_appropriate_message(self):
validator = FormValidator(self.MockFormView()) validator = self.MockFormResource(self.MockFormView())
self.validation_failed_due_to_multiple_errors_returns_appropriate_message(validator) self.validation_failed_due_to_multiple_errors_returns_appropriate_message(validator)
# Same tests on ModelFormValidtionMixin # Same tests on ModelResource
def test_modelform_validation_returns_content_unchanged_if_already_valid_and_clean(self): def test_modelform_validation_returns_content_unchanged_if_already_valid_and_clean(self):
validator = ModelFormValidator(self.MockModelFormView()) validator = self.MockModelResource(self.MockModelFormView())
self.validation_returns_content_unchanged_if_already_valid_and_clean(validator) self.validation_returns_content_unchanged_if_already_valid_and_clean(validator)
def test_modelform_validation_failure_raises_response_exception(self): def test_modelform_validation_failure_raises_response_exception(self):
validator = ModelFormValidator(self.MockModelFormView()) validator = self.MockModelResource(self.MockModelFormView())
self.validation_failure_raises_response_exception(validator) self.validation_failure_raises_response_exception(validator)
def test_modelform_validation_does_not_allow_extra_fields_by_default(self): def test_modelform_validation_does_not_allow_extra_fields_by_default(self):
validator = ModelFormValidator(self.MockModelFormView()) validator = self.MockModelResource(self.MockModelFormView())
self.validation_does_not_allow_extra_fields_by_default(validator) self.validation_does_not_allow_extra_fields_by_default(validator)
def test_modelform_validation_allows_extra_fields_if_explicitly_set(self): def test_modelform_validation_allows_extra_fields_if_explicitly_set(self):
validator = ModelFormValidator(self.MockModelFormView()) validator = self.MockModelResource(self.MockModelFormView())
self.validation_allows_extra_fields_if_explicitly_set(validator) self.validation_allows_extra_fields_if_explicitly_set(validator)
def test_modelform_validation_does_not_require_extra_fields_if_explicitly_set(self): def test_modelform_validation_does_not_require_extra_fields_if_explicitly_set(self):
validator = ModelFormValidator(self.MockModelFormView()) validator = self.MockModelResource(self.MockModelFormView())
self.validation_does_not_require_extra_fields_if_explicitly_set(validator) self.validation_does_not_require_extra_fields_if_explicitly_set(validator)
def test_modelform_validation_failed_due_to_no_content_returns_appropriate_message(self): def test_modelform_validation_failed_due_to_no_content_returns_appropriate_message(self):
validator = ModelFormValidator(self.MockModelFormView()) validator = self.MockModelResource(self.MockModelFormView())
self.validation_failed_due_to_no_content_returns_appropriate_message(validator) self.validation_failed_due_to_no_content_returns_appropriate_message(validator)
def test_modelform_validation_failed_due_to_field_error_returns_appropriate_message(self): def test_modelform_validation_failed_due_to_field_error_returns_appropriate_message(self):
validator = ModelFormValidator(self.MockModelFormView()) validator = self.MockModelResource(self.MockModelFormView())
self.validation_failed_due_to_field_error_returns_appropriate_message(validator) self.validation_failed_due_to_field_error_returns_appropriate_message(validator)
def test_modelform_validation_failed_due_to_invalid_field_returns_appropriate_message(self): def test_modelform_validation_failed_due_to_invalid_field_returns_appropriate_message(self):
validator = ModelFormValidator(self.MockModelFormView()) validator = self.MockModelResource(self.MockModelFormView())
self.validation_failed_due_to_invalid_field_returns_appropriate_message(validator) self.validation_failed_due_to_invalid_field_returns_appropriate_message(validator)
def test_modelform_validation_failed_due_to_multiple_errors_returns_appropriate_message(self): def test_modelform_validation_failed_due_to_multiple_errors_returns_appropriate_message(self):
validator = ModelFormValidator(self.MockModelFormView()) validator = self.MockModelResource(self.MockModelFormView())
self.validation_failed_due_to_multiple_errors_returns_appropriate_message(validator) self.validation_failed_due_to_multiple_errors_returns_appropriate_message(validator)
@ -265,43 +274,43 @@ class TestModelFormValidator(TestCase):
def readonly(self): def readonly(self):
return 'read only' return 'read only'
class MockResource(Resource): class MockResource(ModelResource):
model = MockModel model = MockModel
class MockView(BaseView): class MockView(BaseView):
resource = MockResource resource = MockResource
self.validator = ModelFormValidator(MockView) self.validator = MockResource(MockView)
def test_property_fields_are_allowed_on_model_forms(self): def test_property_fields_are_allowed_on_model_forms(self):
"""Validation on ModelForms may include property fields that exist on the Model to be included in the input.""" """Validation on ModelForms may include property fields that exist on the Model to be included in the input."""
content = {'qwerty':'example', 'uiop': 'example', 'readonly': 'read only'} content = {'qwerty':'example', 'uiop': 'example', 'readonly': 'read only'}
self.assertEqual(self.validator.validate(content), content) self.assertEqual(self.validator.validate_request(content, None), content)
def test_property_fields_are_not_required_on_model_forms(self): def test_property_fields_are_not_required_on_model_forms(self):
"""Validation on ModelForms does not require property fields that exist on the Model to be included in the input.""" """Validation on ModelForms does not require property fields that exist on the Model to be included in the input."""
content = {'qwerty':'example', 'uiop': 'example'} content = {'qwerty':'example', 'uiop': 'example'}
self.assertEqual(self.validator.validate(content), content) self.assertEqual(self.validator.validate_request(content, None), content)
def test_extra_fields_not_allowed_on_model_forms(self): def test_extra_fields_not_allowed_on_model_forms(self):
"""If some (otherwise valid) content includes fields that are not in the form then validation should fail. """If some (otherwise valid) content includes fields that are not in the form then validation should fail.
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
broken clients more easily (eg submitting content with a misnamed field)""" broken clients more easily (eg submitting content with a misnamed field)"""
content = {'qwerty': 'example', 'uiop':'example', 'readonly': 'read only', 'extra': 'extra'} content = {'qwerty': 'example', 'uiop':'example', 'readonly': 'read only', 'extra': 'extra'}
self.assertRaises(ErrorResponse, self.validator.validate, content) self.assertRaises(ErrorResponse, self.validator.validate_request, content, None)
def test_validate_requires_fields_on_model_forms(self): def test_validate_requires_fields_on_model_forms(self):
"""If some (otherwise valid) content includes fields that are not in the form then validation should fail. """If some (otherwise valid) content includes fields that are not in the form then validation should fail.
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
broken clients more easily (eg submitting content with a misnamed field)""" broken clients more easily (eg submitting content with a misnamed field)"""
content = {'readonly': 'read only'} content = {'readonly': 'read only'}
self.assertRaises(ErrorResponse, self.validator.validate, content) self.assertRaises(ErrorResponse, self.validator.validate_request, content, None)
def test_validate_does_not_require_blankable_fields_on_model_forms(self): def test_validate_does_not_require_blankable_fields_on_model_forms(self):
"""Test standard ModelForm validation behaviour - fields with blank=True are not required.""" """Test standard ModelForm validation behaviour - fields with blank=True are not required."""
content = {'qwerty':'example', 'readonly': 'read only'} content = {'qwerty':'example', 'readonly': 'read only'}
self.validator.validate(content) self.validator.validate_request(content, None)
def test_model_form_validator_uses_model_forms(self): def test_model_form_validator_uses_model_forms(self):
self.assertTrue(isinstance(self.validator.get_bound_form(), forms.ModelForm)) self.assertTrue(isinstance(self.validator.get_bound_form(), forms.ModelForm))