Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

catch validation errors during patch serialization #682

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions flask_restless/views/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,8 @@ def patch(self, resource_id):
result = self.serializer.serialize(instance, only=only)
except SerializationException as exception:
return errors_from_serialization_exceptions([exception])
except self.validation_exceptions as exception:
return self._handle_validation_exception(exception)
status = 200
else:
result = dict()
Expand Down
1 change: 1 addition & 0 deletions requirements/test.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
-r install.txt
unittest2
marshmallow
24 changes: 24 additions & 0 deletions tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from sqlalchemy.orm.session import Session as SessionBase
from sqlalchemy.types import CHAR
from sqlalchemy.types import TypeDecorator
from marshmallow import ValidationError

from flask_restless import APIManager
from flask_restless import collection_name
Expand Down Expand Up @@ -119,6 +120,29 @@ def deserialize_many(self, *args, **kw):
raise DeserializationException


class raise_s_validation_error(DefaultSerializer):
"""A serializer that unconditionally raises a validation error when
either :meth:`.serialize` or :meth:`.serialize_many` is called.

This class is useful for tests of validation errors.

"""

def serialize(self, instance, *args, **kw):
"""Immediately raises a :exc:`ValidationError` with
access to the provided `instance` of a SQLAlchemy model.

"""
raise ValidationError(instance)

def serialize_many(self, instances, *args, **kw):
"""Immediately raises a :exc:`ValidationError`.

This function requires `instances` to be non-empty.

"""
raise ValidationError(instances[0])

def isclass(obj):
"""Returns ``True`` if and only if the specified object is a type (or a
class).
Expand Down
30 changes: 30 additions & 0 deletions tests/test_updating.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import backref
from sqlalchemy.orm import relationship
from marshmallow import ValidationError

from flask_restless import APIManager
from flask_restless import JSONAPI_MIMETYPE
Expand All @@ -55,6 +56,7 @@
from .helpers import MSIE9_UA
from .helpers import ManagerTestBase
from .helpers import raise_s_exception as raise_exception
from .helpers import raise_s_validation_error as raise_validation_error


class TestUpdating(ManagerTestBase):
Expand Down Expand Up @@ -893,6 +895,34 @@ def test_serialization_exception(self):
check_sole_error(response, 500, ['Failed to serialize', 'type', 'tag',
'ID', '1'])

def test_serialization_validation_error(self):
"""Tests that serialization validation errors are caught when
responding with content.

A representation of the modified resource is returned to the
client when an update causes additional changes in the resource
in ways other than those specified by the client.

"""
tag = self.Tag(id=1)
self.session.add(tag)
self.session.commit()
self.manager.create_api(self.Tag, methods=['PATCH'],
validation_exceptions=[ValidationError],
serializer_class=raise_validation_error)
data = {
'data': {
'type': 'tag',
'id': '1',
'attributes': {
'name': u'foo'
}
}
}
response = self.app.patch('/api/tag/1', data=dumps(data))
assert response.status_code == 400
assert response.status == '400 BAD REQUEST'

def test_dont_assign_to_method(self):
"""Tests that if a certain method is to be included in a
resource, that method is not assigned to when updating the
Expand Down