spec/serializer_spec.rb in jsonapi-serializers-1.0.1 vs spec/serializer_spec.rb in jsonapi-serializers-2.0.0.pre.beta.1

- old
+ new

@@ -1,8 +1,9 @@ require 'active_model/errors' require 'active_model/naming' require 'active_model/translation' +require 'pry' describe JSONAPI::Serializer do def serialize_primary(object, options = {}) # Note: intentional high-coupling to protected method for tests. JSONAPI::Serializer.send(:serialize_primary, object, options) @@ -14,10 +15,11 @@ # - a single resource object or null, for requests that target single resources # http://jsonapi.org/format/#document-structure-top-level primary_data = serialize_primary(nil, {serializer: MyApp::PostSerializer}) expect(primary_data).to be_nil end + it 'can serialize primary data for a simple object' do post = create(:post) primary_data = serialize_primary(post, {serializer: MyApp::SimplestPostSerializer}) expect(primary_data).to eq({ 'id' => '1', @@ -29,10 +31,11 @@ 'links' => { 'self' => '/posts/1', }, }) end + it 'can serialize primary data for a simple object with a long name' do long_comment = create(:long_comment, post: create(:post)) primary_data = serialize_primary(long_comment, {serializer: MyApp::LongCommentSerializer}) expect(primary_data).to eq({ 'id' => '1', @@ -57,10 +60,11 @@ }, }, }, }) end + it 'can serialize primary data for a simple object with resource-level metadata' do post = create(:post) primary_data = serialize_primary(post, {serializer: MyApp::PostSerializerWithMetadata}) expect(primary_data).to eq({ 'id' => '1', @@ -78,10 +82,11 @@ 'Aliens', ], }, }) end + context 'without any linkage includes (default)' do it 'can serialize primary data for an object with to-one and to-many relationships' do post = create(:post) primary_data = serialize_primary(post, {serializer: MyApp::PostSerializer}) expect(primary_data).to eq({ @@ -109,10 +114,11 @@ }, }, }, }) end + it 'does not include relationship links if relationship_{self_link,_related_link} are nil' do post = create(:post) primary_data = serialize_primary(post, {serializer: MyApp::PostSerializerWithoutLinks}) expect(primary_data).to eq({ 'id' => '1', @@ -127,10 +133,11 @@ 'author' => {}, 'long-comments' => {}, }, }) end + it 'does not include id when it is nil' do post = create(:post) post.id = nil primary_data = serialize_primary(post, {serializer: MyApp::PostSerializerWithoutLinks}) expect(primary_data).to eq({ @@ -143,10 +150,11 @@ 'author' => {}, 'long-comments' => {}, }, }) end + it 'serializes object when multiple attributes are declared once' do post = create(:post) primary_data = serialize_primary(post, {serializer: MyApp::MultipleAttributesSerializer}) expect(primary_data).to eq({ 'id' => '1', @@ -159,10 +167,11 @@ 'self' => '/posts/1', } }) end end + context 'with linkage includes' do it 'can serialize primary data for a null to-one relationship' do post = create(:post, author: nil) options = { serializer: MyApp::PostSerializer, @@ -198,10 +207,11 @@ 'data' => [], }, }, }) end + it 'can serialize primary data for a simple to-one relationship' do post = create(:post, :with_author) options = { serializer: MyApp::PostSerializer, include_linkages: ['author', 'long-comments'], @@ -239,10 +249,11 @@ 'data' => [], }, }, }) end + it 'can serialize primary data for an empty to-many relationship' do post = create(:post, long_comments: []) options = { serializer: MyApp::PostSerializer, include_linkages: ['author', 'long-comments'], @@ -277,10 +288,11 @@ 'data' => [], }, }, }) end + it 'can serialize primary data for a simple to-many relationship' do long_comments = create_list(:long_comment, 2) post = create(:post, long_comments: long_comments) options = { serializer: MyApp::PostSerializer, @@ -326,10 +338,11 @@ }, }, }) end end + it 'can serialize primary data for an empty serializer with no attributes' do post = create(:post) primary_data = serialize_primary(post, {serializer: MyApp::EmptySerializer}) expect(primary_data).to eq({ 'id' => '1', @@ -337,10 +350,11 @@ 'links' => { 'self' => '/posts/1', }, }) end + it 'can find the correct serializer by object class name' do post = create(:post) primary_data = serialize_primary(post) expect(primary_data).to eq({ 'id' => '1', @@ -500,50 +514,57 @@ # primary data is not explicitly tested here. If things are broken, look above here first. it 'can serialize a nil object' do expect(JSONAPI::Serializer.serialize(nil)).to eq({'data' => nil}) end + it 'can serialize a nil object with includes' do # Also, the include argument is not validated in this case because we don't know the type. data = JSONAPI::Serializer.serialize(nil, include: ['fake']) expect(data).to eq({'data' => nil, 'included' => []}) end + it 'can serialize an empty array' do # Also, the include argument is not validated in this case because we don't know the type. data = JSONAPI::Serializer.serialize([], is_collection: true, include: ['fake']) expect(data).to eq({'data' => [], 'included' => []}) end + it 'can serialize a simple object' do post = create(:post) expect(JSONAPI::Serializer.serialize(post)).to eq({ 'data' => serialize_primary(post, {serializer: MyApp::PostSerializer}), }) end + it 'can include a top level jsonapi node' do post = create(:post) jsonapi_version = {'version' => '1.0'} expect(JSONAPI::Serializer.serialize(post, jsonapi: jsonapi_version)).to eq({ 'jsonapi' => {'version' => '1.0'}, 'data' => serialize_primary(post, {serializer: MyApp::PostSerializer}), }) end + it 'can include a top level meta node' do post = create(:post) meta = {authors: ['Yehuda Katz', 'Steve Klabnik'], copyright: 'Copyright 2015 Example Corp.'} expect(JSONAPI::Serializer.serialize(post, meta: meta)).to eq({ 'meta' => meta, 'data' => serialize_primary(post, {serializer: MyApp::PostSerializer}), }) end + it 'can include a top level links node' do post = create(:post) links = {self: 'http://example.com/posts'} expect(JSONAPI::Serializer.serialize(post, links: links)).to eq({ 'links' => links, 'data' => serialize_primary(post, {serializer: MyApp::PostSerializer}), }) end + # TODO: remove this code on next major release it 'can include a top level errors node - deprecated' do post = create(:post) errors = [ { @@ -560,28 +581,32 @@ expect(JSONAPI::Serializer.serialize(post, errors: errors)).to eq({ 'errors' => errors, 'data' => serialize_primary(post, {serializer: MyApp::PostSerializer}), }) end + it 'can serialize a single object with an `each` method by passing skip_collection_check: true' do post = create(:post) post.define_singleton_method(:each) do "defining this just to defeat the duck-type check" end + expect(JSONAPI::Serializer.serialize(post, skip_collection_check: true)).to eq({ 'data' => serialize_primary(post, {serializer: MyApp::PostSerializer}), }) end + it 'can serialize a collection' do posts = create_list(:post, 2) expect(JSONAPI::Serializer.serialize(posts, is_collection: true)).to eq({ 'data' => [ serialize_primary(posts.first, {serializer: MyApp::PostSerializer}), serialize_primary(posts.last, {serializer: MyApp::PostSerializer}), ], }) end + it 'raises AmbiguousCollectionError if is_collection is not passed' do posts = create_list(:post, 2) error = JSONAPI::Serializer::AmbiguousCollectionError expect { JSONAPI::Serializer.serialize(posts) }.to raise_error(error) end @@ -594,21 +619,24 @@ it 'can serialize a nil object when given serializer' do options = {serializer: MyApp::PostSerializer} expect(JSONAPI::Serializer.serialize(nil, options)).to eq({'data' => nil}) end + it 'can serialize an empty array when given serializer' do options = {is_collection: true, serializer: MyApp::PostSerializer} expect(JSONAPI::Serializer.serialize([], options)).to eq({'data' => []}) end + it 'can serialize a simple object when given serializer' do post = create(:post) options = {serializer: MyApp::SimplestPostSerializer} expect(JSONAPI::Serializer.serialize(post, options)).to eq({ 'data' => serialize_primary(post, {serializer: MyApp::SimplestPostSerializer}), }) end + it 'handles include of nil to-one relationship with compound document' do post = create(:post) expected_primary_data = serialize_primary(post, { serializer: MyApp::PostSerializer, @@ -617,10 +645,11 @@ expect(JSONAPI::Serializer.serialize(post, include: ['author'])).to eq({ 'data' => expected_primary_data, 'included' => [], }) end + it 'handles include of simple to-one relationship with compound document' do post = create(:post, :with_author) expected_primary_data = serialize_primary(post, { serializer: MyApp::PostSerializer, @@ -631,10 +660,11 @@ 'included' => [ serialize_primary(post.author, {serializer: MyAppOtherNamespace::UserSerializer}), ], }) end + it 'handles include of empty to-many relationships with compound document' do post = create(:post, :with_author, long_comments: []) expected_primary_data = serialize_primary(post, { serializer: MyApp::PostSerializer, @@ -643,10 +673,11 @@ expect(JSONAPI::Serializer.serialize(post, include: ['long-comments'])).to eq({ 'data' => expected_primary_data, 'included' => [], }) end + it 'handles include of to-many relationships with compound document' do long_comments = create_list(:long_comment, 2) post = create(:post, :with_author, long_comments: long_comments) expected_primary_data = serialize_primary(post, { @@ -659,10 +690,11 @@ serialize_primary(long_comments.first, {serializer: MyApp::LongCommentSerializer}), serialize_primary(long_comments.last, {serializer: MyApp::LongCommentSerializer}), ], }) end + it 'only includes one copy of each referenced relationship' do long_comment = create(:long_comment) long_comments = [long_comment, long_comment] post = create(:post, :with_author, long_comments: long_comments) @@ -675,10 +707,11 @@ 'included' => [ serialize_primary(long_comment, {serializer: MyApp::LongCommentSerializer}), ], }) end + it 'handles circular-referencing relationships with compound document' do long_comments = create_list(:long_comment, 2) post = create(:post, :with_author, long_comments: long_comments) # Make sure each long-comment has a circular reference back to the post. @@ -694,14 +727,16 @@ serialize_primary(post.long_comments.first, {serializer: MyApp::LongCommentSerializer}), serialize_primary(post.long_comments.last, {serializer: MyApp::LongCommentSerializer}), ], }) end + it 'errors if include is not a defined attribute' do user = create(:user) expect { JSONAPI::Serializer.serialize(user, include: ['fake-attr']) }.to raise_error end + it 'handles recursive loading of relationships' do user = create(:user) long_comments = create_list(:long_comment, 2, user: user) post = create(:post, :with_author, long_comments: long_comments) # Make sure each long-comment has a circular reference back to the post. @@ -735,10 +770,11 @@ # Multiple expectations for better diff output for debugging. expect(actual_data['data']).to eq(expected_data['data']) expect(actual_data['included']).to eq(expected_data['included']) expect(actual_data).to eq(expected_data) end + it 'handles recursive loading of multiple to-one relationships on children' do first_user = create(:user) second_user = create(:user) first_comment = create(:long_comment, user: first_user) second_comment = create(:long_comment, user: second_user) @@ -772,10 +808,11 @@ # Multiple expectations for better diff output for debugging. expect(actual_data['data']).to eq(expected_data['data']) expect(actual_data['included']).to eq(expected_data['included']) expect(actual_data).to eq(expected_data) end + it 'includes linkage in compounded resources only if the immediate parent was also included' do comment_user = create(:user) long_comments = [create(:long_comment, user: comment_user)] post = create(:post, :with_author, long_comments: long_comments) @@ -800,10 +837,11 @@ # Multiple expectations for better diff output for debugging. expect(actual_data['data']).to eq(expected_data['data']) expect(actual_data['included']).to eq(expected_data['included']) expect(actual_data).to eq(expected_data) end + it 'handles recursive loading of to-many relationships with overlapping include paths' do user = create(:user) long_comments = create_list(:long_comment, 2, user: user) post = create(:post, :with_author, long_comments: long_comments) # Make sure each long-comment has a circular reference back to the post. @@ -882,10 +920,11 @@ 'self' => '/posts/1' } } }) end + it 'allows to limit fields(relationships) for serialized resource' do first_user = create(:user) second_user = create(:user) first_comment = create(:long_comment, user: first_user) second_comment = create(:long_comment, user: second_user) @@ -907,10 +946,11 @@ 'related' => '/posts/1/long-comments' } } }) end + it "allows also to pass specific fields as array instead of comma-separates values" do first_user = create(:user) second_user = create(:user) first_comment = create(:long_comment, user: first_user) second_comment = create(:long_comment, user: second_user) @@ -928,10 +968,11 @@ 'related' => '/posts/1/author' } } }) end + it 'allows to limit fields(attributes and relationships) for included resources' do first_user = create(:user) second_user = create(:user) first_comment = create(:long_comment, user: first_user) second_comment = create(:long_comment, user: second_user) @@ -981,52 +1022,59 @@ describe 'internal-only parse_relationship_paths' do it 'correctly handles empty arrays' do result = JSONAPI::Serializer.send(:parse_relationship_paths, []) expect(result).to eq({}) end + it 'correctly handles single-level relationship paths' do result = JSONAPI::Serializer.send(:parse_relationship_paths, ['foo']) expect(result).to eq({ 'foo' => {_include: true} }) end + it 'correctly handles multi-level relationship paths' do result = JSONAPI::Serializer.send(:parse_relationship_paths, ['foo.bar']) expect(result).to eq({ 'foo' => {_include: true, 'bar' => {_include: true}} }) end + it 'correctly handles multi-level relationship paths with same parent' do paths = ['foo', 'foo.bar'] result = JSONAPI::Serializer.send(:parse_relationship_paths, paths) expect(result).to eq({ 'foo' => {_include: true, 'bar' => {_include: true}} }) end + it 'correctly handles multi-level relationship paths with different parent' do paths = ['foo', 'bar', 'bar.baz'] result = JSONAPI::Serializer.send(:parse_relationship_paths, paths) expect(result).to eq({ 'foo' => {_include: true}, 'bar' => {_include: true, 'baz' => {_include: true}}, }) end + it 'correctly handles three-leveled path' do paths = ['foo', 'foo.bar', 'foo.bar.baz'] result = JSONAPI::Serializer.send(:parse_relationship_paths, paths) expect(result).to eq({ 'foo' => {_include: true, 'bar' => {_include: true, 'baz' => {_include: true}}} }) end + it 'correctly handles three-leveled path with skipped middle' do paths = ['foo', 'foo.bar.baz'] result = JSONAPI::Serializer.send(:parse_relationship_paths, paths) expect(result).to eq({ 'foo' => {_include: true, 'bar' => {_include: true, 'baz' => {_include: true}}} }) end end + describe 'if/unless handling with contexts' do it 'can be used to show/hide attributes' do post = create(:post) options = {serializer: MyApp::PostSerializerWithContext} @@ -1066,10 +1114,11 @@ options[:context] = {show_body: true, hide_body: true} data = JSONAPI::Serializer.serialize(post, options) expect(data['data']['attributes']).to_not have_key('body') end end + describe 'context' do it 'is passed through all relationship serializers' do # Force long_comments to be serialized by the context-sensitive serializer. expect_any_instance_of(MyApp::LongComment).to receive(:jsonapi_serializer_class_name) .at_least(:once) @@ -1111,20 +1160,22 @@ expect(data['data']['relationships']['author']['links']).to eq({ 'self' => '/posts/1/relationships/author', 'related' => '/posts/1/author' }) end + it 'adds base_url to links if passed' do long_comments = create_list(:long_comment, 1) post = create(:post, long_comments: long_comments) data = JSONAPI::Serializer.serialize(post, base_url: 'http://example.com') expect(data['data']['links']['self']).to eq('http://example.com/posts/1') expect(data['data']['relationships']['author']['links']).to eq({ 'self' => 'http://example.com/posts/1/relationships/author', 'related' => 'http://example.com/posts/1/author' }) end + it 'uses overriden base_url method if it exists' do long_comments = create_list(:long_comment, 1) post = create(:post, long_comments: long_comments) data = JSONAPI::Serializer.serialize(post, serializer: MyApp::PostSerializerWithBaseUrl) expect(data['data']['links']['self']).to eq('http://example.com/posts/1') @@ -1229,8 +1280,58 @@ actual_data = JSONAPI::Serializer.serialize(post, include: includes, namespace: Api::V1) # Multiple expectations for better diff output for debugging. expect(actual_data['data']).to eq(expected_data['data']) expect(actual_data['included']).to eq(expected_data['included']) expect(actual_data).to eq(expected_data) + end + end + + describe 'instrumentation' do + let(:post) { create(:post, :with_author) } + let(:events) { [] } + + before do + ActiveSupport::Notifications.subscribe(notification_name) do |*args| + events << ActiveSupport::Notifications::Event.new(*args) + end + end + + describe 'serialize_primary' do + let(:notification_name) { 'render.jsonapi_serializers.serialize_primary' } + + it 'sends an event for a single serialize call' do + JSONAPI::Serializer.serialize(post) + + expect(events.length).to eq(1) + expect(events[0].name).to eq(notification_name) + expect(events[0].payload).to eq({class_name: "MyApp::Post"}) + end + + it 'sends events for includes' do + JSONAPI::Serializer.serialize(post, include: ['author']) + + expect(events.length).to eq(2) + expect(events[0].payload).to eq({class_name: "MyApp::Post"}) + expect(events[1].payload).to eq({class_name: "MyApp::User"}) + end + end + + describe 'find_recursive_relationships' do + let(:notification_name) { 'render.jsonapi_serializers.find_recursive_relationships' } + + it 'does not send event when there are no includes' do + JSONAPI::Serializer.serialize(post) + expect(events.length).to eq(0) + end + + it 'sends events for includes' do + JSONAPI::Serializer.serialize(post, include: ['author']) + + expect(events.length).to eq(2) + expect(events[0].name).to eq(notification_name) + expect(events[0].payload).to eq({class_name: "MyApp::User"}) + expect(events[1].name).to eq(notification_name) + expect(events[1].payload).to eq({class_name: "MyApp::Post"}) + end end end end