"""Tests for declarative JSONAPISerializer serialize method.""" import unittest import datetime from sqlalchemy import ( create_engine, Column, String, Integer, ForeignKey, Boolean, DateTime) from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import backref, relationship, sessionmaker from sqlalchemy_jsonapi.declarative import serializer class SerializeResourcesWithoutRelatedModels(unittest.TestCase): """Tests for serializing a resource that has no related models.""" def setUp(self): """Configure sqlalchemy and session.""" self.engine = create_engine('sqlite://') Session = sessionmaker(bind=self.engine) self.session = Session() self.Base = declarative_base() class User(self.Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) first_name = Column(String(50), nullable=False) age = Column(Integer, nullable=False) username = Column(String(50), unique=True, nullable=False) is_admin = Column(Boolean, default=False) date_joined = Column(DateTime) self.User = User self.Base.metadata.create_all(self.engine) def tearDown(self): """Reset the sqlalchemy engine.""" self.Base.metadata.drop_all(self.engine) def test_serialize_single_resource_with_only_id_field(self): """Serialize a resource with only an 'id' field. If attributes, other than 'id', are not specified in fields, then the attributes remain an empty object. """ class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" fields = ['id'] model = self.User dasherize = True user = self.User( first_name='Sally', age=27, is_admin=True, username='SallySmith1', date_joined=datetime.date(2017, 12, 5)) self.session.add(user) self.session.commit() user = self.session.query(self.User).get(user.id) user_serializer = UserSerializer() serialized_data = user_serializer.serialize(user) expected_data = { 'data': { 'id': str(user.id), 'type': user.__tablename__, 'attributes': {}, 'relationships': {} }, 'meta': { 'sqlalchemy_jsonapi_version': '4.0.9' }, 'jsonapi': { 'version': '1.0' } } self.assertEqual(expected_data, serialized_data) def test_serialize_single_resource_with_dasherize_true(self): """Serialize a resource where attributes are dasherized. Attribute keys contain dashes instead of underscores. """ class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" fields = [ 'id', 'first_name', 'username', 'age', 'date_joined', 'is_admin'] model = self.User dasherize = True user = self.User( first_name='Sally', age=27, is_admin=True, username='SallySmith1', date_joined=datetime.date(2017, 12, 5)) self.session.add(user) self.session.commit() user = self.session.query(self.User).get(user.id) user_serializer = UserSerializer() serialized_data = user_serializer.serialize(user) expected_data = { 'data': { 'id': str(user.id), 'type': u'{}'.format(user.__tablename__), 'attributes': { 'date-joined': user.date_joined.isoformat(), 'username': u'{}'.format(user.username), 'age': user.age, 'first-name': u'{}'.format(user.first_name), 'is-admin': user.is_admin }, 'relationships': {} }, 'meta': { 'sqlalchemy_jsonapi_version': '4.0.9' }, 'jsonapi': { 'version': '1.0' } } self.assertEqual(expected_data, serialized_data) def test_serialize_single_resource_with_dasherize_false(self): """Serialize a resource where attributes are not dasherized. Attribute keys are underscored like in serializer model. """ class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" fields = [ 'id', 'first_name', 'username', 'age', 'date_joined', 'is_admin'] model = self.User dasherize = False user = self.User( first_name='Sally', age=27, is_admin=True, username='SallySmith1', date_joined=datetime.date(2017, 12, 5)) self.session.add(user) self.session.commit() user = self.session.query(self.User).get(user.id) user_serializer = UserSerializer() serialized_data = user_serializer.serialize(user) expected_data = { 'data': { 'id': str(user.id), 'type': u'{}'.format(user.__tablename__), 'attributes': { 'date_joined': user.date_joined.isoformat(), 'username': u'{}'.format(user.username), 'age': user.age, 'first_name': u'{}'.format(user.first_name), 'is_admin': user.is_admin }, 'relationships': {} }, 'meta': { 'sqlalchemy_jsonapi_version': '4.0.9' }, 'jsonapi': { 'version': '1.0' } } self.assertEqual(expected_data, serialized_data) def test_serialize_collection_of_resources(self): """Serialize a collection of resources returns a list of objects.""" class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" fields = ['id'] model = self.User dasherize = True user = self.User( first_name='Sally', age=27, is_admin=True, username='SallySmith1', date_joined=datetime.date(2017, 12, 5)) self.session.add(user) self.session.commit() users = self.session.query(self.User) user_serializer = UserSerializer() serialized_data = user_serializer.serialize(users) expected_data = { 'data': [{ 'id': str(user.id), 'type': 'users', 'attributes': {}, 'relationships': {} }], 'meta': { 'sqlalchemy_jsonapi_version': '4.0.9' }, 'jsonapi': { 'version': '1.0' } } self.assertEquals(expected_data, serialized_data) def test_serialize_empty_collection(self): """Serialize a collection that is empty returns an empty list.""" class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" fields = ['id'] model = self.User dasherize = True users = self.session.query(self.User) user_serializer = UserSerializer() serialized_data = user_serializer.serialize(users) expected_data = { 'data': [], 'meta': { 'sqlalchemy_jsonapi_version': '4.0.9' }, 'jsonapi': { 'version': '1.0' } } self.assertEquals(expected_data, serialized_data) def test_serialize_resource_not_found(self): """Serialize a resource that does not exist returns None.""" class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" fields = ['id'] model = self.User dasherize = True # Nonexistant user user = self.session.query(self.User).get(99999999) user_serializer = UserSerializer() serialized_data = user_serializer.serialize(user) expected_data = { 'data': None, 'meta': { 'sqlalchemy_jsonapi_version': '4.0.9' }, 'jsonapi': { 'version': '1.0' } } self.assertEqual(expected_data, serialized_data) class SerializeResourceWithRelatedModels(unittest.TestCase): """Tests for serializing a resource that has related models.""" def setUp(self): """Configure sqlalchemy and session.""" self.engine = create_engine('sqlite://') Session = sessionmaker(bind=self.engine) self.session = Session() self.Base = declarative_base() class User(self.Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) first_name = Column(String(50), nullable=False) class Post(self.Base): __tablename__ = 'posts' id = Column(Integer, primary_key=True) title = Column(String(100), nullable=False) author_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE')) blog_author = relationship('User', lazy='joined', backref=backref('posts', lazy='dynamic', cascade='all,delete')) self.User = User self.Post = Post self.Base.metadata.create_all(self.engine) def tearDown(self): """Reset the sqlalchemy engine.""" self.Base.metadata.drop_all(self.engine) def test_serialize_resource_with_to_many_relationship_success(self): """Serailize a resource with a to-many relationship.""" class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" fields = ['id', 'first_name'] model = self.User user = self.User(first_name='Sally') self.session.add(user) self.session.commit() user = self.session.query(self.User).get(user.id) user_serializer = UserSerializer() serialized_data = user_serializer.serialize(user) expected_data = { 'data': { 'id': str(user.id), 'type': user.__tablename__, 'attributes': { 'first-name': u'{}'.format(user.first_name) }, 'relationships': { 'posts': { 'links': { 'self': '/users/1/relationships/posts', 'related': '/users/1/posts' } } } }, 'meta': { 'sqlalchemy_jsonapi_version': '4.0.9' }, 'jsonapi': { 'version': '1.0' } } self.assertEqual(expected_data, serialized_data) def test_serialize_resource_with_to_one_relationship_success(self): """Serialize a resource with a to-one relationship.""" class PostSerializer(serializer.JSONAPISerializer): """Declarative serializer for Post.""" fields = ['id', 'title'] model = self.Post blog_post = self.Post(title='Foo') self.session.add(blog_post) self.session.commit() post = self.session.query(self.Post).get(blog_post.id) blog_post_serializer = PostSerializer() serialized_data = blog_post_serializer.serialize(post) expected_data = { 'data': { 'id': str(blog_post.id), 'type': blog_post.__tablename__, 'attributes': { 'title': u'{}'.format(blog_post.title) }, 'relationships': { 'blog-author': { 'links': { 'self': '/posts/1/relationships/blog-author', 'related': '/posts/1/blog-author' } } } }, 'meta': { 'sqlalchemy_jsonapi_version': '4.0.9' }, 'jsonapi': { 'version': '1.0' } } self.assertEqual(expected_data, serialized_data) def test_serialize_resource_with_relationship_given_dasherize_false(self): """Serialize a resource with to-one relationship given dasherize false. Relationship keys are underscored like in model. """ class PostSerializer(serializer.JSONAPISerializer): """Declarative serializer for Post.""" fields = ['id', 'title'] model = self.Post dasherize = False blog_post = self.Post(title='Foo') self.session.add(blog_post) self.session.commit() post = self.session.query(self.Post).get(blog_post.id) blog_post_serializer = PostSerializer() serialized_data = blog_post_serializer.serialize(post) expected_data = { 'data': { 'id': str(blog_post.id), 'type': blog_post.__tablename__, 'attributes': { 'title': u'{}'.format(blog_post.title) }, 'relationships': { 'blog_author': { 'links': { 'self': '/posts/1/relationships/blog_author', 'related': '/posts/1/blog_author' } } } }, 'meta': { 'sqlalchemy_jsonapi_version': '4.0.9' }, 'jsonapi': { 'version': '1.0' } } self.assertEqual(expected_data, serialized_data) class TestSerializeErrors(unittest.TestCase): """Tests for errors raised in serialize method.""" def setUp(self): """Configure sqlalchemy and session.""" self.engine = create_engine('sqlite://') Session = sessionmaker(bind=self.engine) self.session = Session() self.Base = declarative_base() class User(self.Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) first_name = Column(String(50), nullable=False) class Post(self.Base): __tablename__ = 'posts' id = Column(Integer, primary_key=True) title = Column(String(100), nullable=False) author_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE')) blog_author = relationship('User', lazy='joined', backref=backref('posts', lazy='dynamic', cascade='all,delete')) self.User = User self.Post = Post self.Base.metadata.create_all(self.engine) def tearDown(self): """Reset the sqlalchemy engine.""" self.Base.metadata.drop_all(self.engine) def test_serialize_resource_with_mismatched_model(self): """A serializers model type much match the resource it serializes.""" class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" fields = ['id'] model = self.Post user = self.User(first_name='Sally') self.session.add(user) self.session.commit() user = self.session.query(self.User).get(user.id) user_serializer = UserSerializer() with self.assertRaises(TypeError): user_serializer.serialize(user) def test_serialize_resource_with_unknown_attribute_in_fields(self): """Cannot serialize attributes that are unknown to resource.""" class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" fields = ['id', 'firsts_names_unknown'] model = self.User user = self.User(first_name='Sally') self.session.add(user) self.session.commit() user = self.session.query(self.User).get(user.id) user_serializer = UserSerializer() with self.assertRaises(AttributeError): user_serializer.serialize(user) def test_serialize_resource_with_related_model_in_fields(self): """Model serializer fields cannot contain related models. It is against json-api spec to serialize related models as attributes. """ class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" fields = ['id', 'posts'] model = self.User user = self.User(first_name='Sally') self.session.add(user) self.session.commit() user = self.session.query(self.User).get(user.id) user_serializer = UserSerializer() with self.assertRaises(AttributeError): user_serializer.serialize(user) def test_serialize_resource_with_foreign_key_in_fields(self): """Model serializer fields cannot contain foreign keys. It is against json-api spec to serialize foreign keys as attributes. """ class PostSerializer(serializer.JSONAPISerializer): """Declarative serializer for Post.""" fields = ['id', 'author_id'] model = self.Post blog_post = self.Post(title='Foo') self.session.add(blog_post) self.session.commit() post = self.session.query(self.Post).get(blog_post.id) blog_post_serializer = PostSerializer() with self.assertRaises(AttributeError): blog_post_serializer.serialize(post) def test_serialize_resource_with_invalid_primary_key(self): """Resource cannot have unknown primary key. The primary key must be an attribute on the resource. """ class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for Post.""" fields = ['unknown_primary_key', 'first_name'] primary_key = 'unknown_primary_key' model = self.User user = self.User(first_name='Sally') self.session.add(user) self.session.commit() user = self.session.query(self.User).get(user.id) user_serializer = UserSerializer() with self.assertRaises(AttributeError): user_serializer.serialize(user) class TestSerializerInstantiationErrors(unittest.TestCase): """Test exceptions raised in instantiation of serializer.""" def setUp(self): """Configure sqlalchemy and session.""" self.engine = create_engine('sqlite://') Session = sessionmaker(bind=self.engine) self.session = Session() self.Base = declarative_base() class User(self.Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) first_name = Column(String(50), nullable=False) self.User = User self.Base.metadata.create_all(self.engine) def tearDown(self): """Reset the sqlalchemy engine.""" self.Base.metadata.drop_all(self.engine) def test_serializer_with_no_defined_model(self): """Serializer requires model member.""" class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" fields = ['id'] with self.assertRaises(TypeError): UserSerializer() def test_serializer_with_no_defined_fields(self): """At minimum fields must exist.""" class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" model = self.User with self.assertRaises(ValueError): UserSerializer() def test_serializer_with_missing_id_field(self): """An 'id' is required in serializer fields.""" class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" fields = ['first_name'] model = self.User with self.assertRaises(ValueError): UserSerializer()