require File.expand_path('../../../test_helper', __FILE__)
require 'jsonapi-resources'
require 'json'

class SerializerTest < ActionDispatch::IntegrationTest
  def setup
    @post = Post.find(1)
    @fred = Person.find_by(name: 'Fred Reader')

    @expense_entry = ExpenseEntry.find(1)

    JSONAPI.configuration.json_key_format = :camelized_key
    JSONAPI.configuration.route_format = :camelized_route
    JSONAPI.configuration.always_include_to_one_linkage_data = false
  end

  def after_teardown
    JSONAPI.configuration.always_include_to_one_linkage_data = false
    JSONAPI.configuration.json_key_format = :underscored_key
  end

  def test_serializer

    serialized = JSONAPI::ResourceSerializer.new(
      PostResource,
      base_url: 'http://example.com').serialize_to_hash(PostResource.new(@post, nil)
    )

    assert_hash_equals(
      {
        data: {
          type: 'posts',
          id: '1',
          links: {
            self: 'http://example.com/posts/1',
          },
          attributes: {
            title: 'New post',
            body: 'A body!!!',
            subject: 'New post'
          },
          relationships: {
            section: {
              links: {
                self: 'http://example.com/posts/1/relationships/section',
                related: 'http://example.com/posts/1/section'
              }
            },
            author: {
              links: {
                self: 'http://example.com/posts/1/relationships/author',
                related: 'http://example.com/posts/1/author'
              }
            },
            tags: {
              links: {
                self: 'http://example.com/posts/1/relationships/tags',
                related: 'http://example.com/posts/1/tags'
              }
            },
            comments: {
              links: {
                self: 'http://example.com/posts/1/relationships/comments',
                related: 'http://example.com/posts/1/comments'
              }
            }
          }
        }
      },
      serialized
    )
  end

  def test_serializer_nil_handling
    assert_hash_equals(
      {
        data: nil
      },
      JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(nil)
    )
  end

  def test_serializer_namespaced_resource
    assert_hash_equals(
      {
        data: {
          type: 'posts',
          id: '1',
          links: {
            self: 'http://example.com/api/v1/posts/1'
          },
          attributes: {
            title: 'New post',
            body: 'A body!!!',
            subject: 'New post'
          },
          relationships: {
            section: {
              links:{
                self: 'http://example.com/api/v1/posts/1/relationships/section',
                related: 'http://example.com/api/v1/posts/1/section'
              }
            },
            writer: {
              links:{
                self: 'http://example.com/api/v1/posts/1/relationships/writer',
                related: 'http://example.com/api/v1/posts/1/writer'
              }
            },
            comments: {
              links:{
                self: 'http://example.com/api/v1/posts/1/relationships/comments',
                related: 'http://example.com/api/v1/posts/1/comments'
              }
            }
          }
        }
      },
      JSONAPI::ResourceSerializer.new(Api::V1::PostResource,
                                      base_url: 'http://example.com').serialize_to_hash(
        Api::V1::PostResource.new(@post, nil))
    )
  end

  def test_serializer_limited_fieldset

    assert_hash_equals(
      {
        data: {
          type: 'posts',
          id: '1',
          links: {
            self: '/posts/1'
          },
          attributes: {
            title: 'New post'
          },
          relationships: {
            author: {
              links: {
                self: '/posts/1/relationships/author',
                related: '/posts/1/author'
              }
            }
          }
        }
      },
      JSONAPI::ResourceSerializer.new(PostResource,
                                      fields: {posts: [:id, :title, :author]}).serialize_to_hash(PostResource.new(@post, nil))
    )
  end

  def test_serializer_include
    serialized = JSONAPI::ResourceSerializer.new(
      PostResource,
      include: ['author']
    ).serialize_to_hash(PostResource.new(@post, nil))

    assert_hash_equals(
      {
        data: {
          type: 'posts',
          id: '1',
          links: {
            self: '/posts/1'
          },
          attributes: {
            title: 'New post',
            body: 'A body!!!',
            subject: 'New post'
          },
          relationships: {
            section: {
              links: {
                self: '/posts/1/relationships/section',
                related: '/posts/1/section'
              }
            },
            author: {
              links: {
                self: '/posts/1/relationships/author',
                related: '/posts/1/author'
              },
              data: {
                type: 'people',
                id: '1'
              }
            },
            tags: {
              links: {
                self: '/posts/1/relationships/tags',
                related: '/posts/1/tags'
              }
            },
            comments: {
              links: {
                self: '/posts/1/relationships/comments',
                related: '/posts/1/comments'
              }
            }
          }
        },
        included: [
          {
            type: 'people',
            id: '1',
            attributes: {
              name: 'Joe Author',
              email: 'joe@xyz.fake',
              dateJoined: '2013-08-07 16:25:00 -0400'
            },
            links: {
              self: '/people/1'
            },
            relationships: {
             comments: {
               links: {
                 self: '/people/1/relationships/comments',
                 related: '/people/1/comments'
               }
             },
             posts: {
               links: {
                 self: '/people/1/relationships/posts',
                 related: '/people/1/posts'
               }
             },
             preferences: {
               links: {
                 self: '/people/1/relationships/preferences',
                 related: '/people/1/preferences'
               }
             },
             hairCut: {
               links: {
                 self: "/people/1/relationships/hairCut",
                 related: "/people/1/hairCut"
               }
             },
             vehicles: {
               links: {
                 self: "/people/1/relationships/vehicles",
                 related: "/people/1/vehicles"
               }
             }
            }
          }
        ]
      },
      serialized
    )
  end

  def test_serializer_key_format
    serialized = JSONAPI::ResourceSerializer.new(
      PostResource,
      include: ['author'],
      key_formatter: UnderscoredKeyFormatter
    ).serialize_to_hash(PostResource.new(@post, nil))

    assert_hash_equals(
      {
        data: {
          type: 'posts',
          id: '1',
          attributes: {
            title: 'New post',
            body: 'A body!!!',
            subject: 'New post'
          },
          links: {
            self: '/posts/1'
          },
          relationships: {
            section: {
              links: {
                self: '/posts/1/relationships/section',
                related: '/posts/1/section'
              }
            },
            author: {
              links: {
                self: '/posts/1/relationships/author',
                related: '/posts/1/author'
              },
              data: {
                type: 'people',
                id: '1'
              }
            },
            tags: {
              links: {
                self: '/posts/1/relationships/tags',
                related: '/posts/1/tags'
              }
            },
            comments: {
              links: {
                self: '/posts/1/relationships/comments',
                related: '/posts/1/comments'
              }
            }
          }
        },
        included: [
          {
            type: 'people',
            id: '1',
            attributes: {
              name: 'Joe Author',
              email: 'joe@xyz.fake',
              date_joined: '2013-08-07 16:25:00 -0400'
            },
            links: {
              self: '/people/1'
            },
            relationships: {
              comments: {
                links: {
                  self: '/people/1/relationships/comments',
                  related: '/people/1/comments'
                }
              },
              posts: {
                links: {
                  self: '/people/1/relationships/posts',
                  related: '/people/1/posts'
                }
              },
              preferences: {
                links: {
                  self: '/people/1/relationships/preferences',
                  related: '/people/1/preferences'
                }
              },
              hair_cut: {
                links: {
                  self: '/people/1/relationships/hairCut',
                  related: '/people/1/hairCut'
                }
              },
              vehicles: {
                links: {
                  self: "/people/1/relationships/vehicles",
                  related: "/people/1/vehicles"
                }
              }
            }
          }
        ]
      },
      serialized
    )
  end

  def test_serializer_include_sub_objects

    assert_hash_equals(
      {
        data: {
          type: 'posts',
          id: '1',
          attributes: {
            title: 'New post',
            body: 'A body!!!',
            subject: 'New post'
          },
          links: {
            self: '/posts/1'
          },
          relationships: {
            section: {
              links: {
                self: '/posts/1/relationships/section',
                related: '/posts/1/section'
              }
            },
            author: {
              links: {
                self: '/posts/1/relationships/author',
              related: '/posts/1/author'
              }
            },
            tags: {
              links: {
                self: '/posts/1/relationships/tags',
                related: '/posts/1/tags'
              }
            },
            comments: {
              links: {
                self: '/posts/1/relationships/comments',
                related: '/posts/1/comments'
              },
              data: [
                {type: 'comments', id: '1'},
                {type: 'comments', id: '2'}
              ]
            }
          }
        },
        included: [
            {
              type: 'tags',
              id: '1',
              attributes: {
                name: 'short'
              },
              links: {
                self: '/tags/1'
              },
              relationships: {
                posts: {
                  links: {
                    self: '/tags/1/relationships/posts',
                    related: '/tags/1/posts'
                  }
                }
              }
            },
            {
              type: 'tags',
              id: '2',
              attributes: {
                name: 'whiny'
              },
              links: {
                self: '/tags/2'
              },
              relationships: {
                posts: {
                  links: {
                    self: '/tags/2/relationships/posts',
                    related: '/tags/2/posts'
                  }
                }
              }
            },
            {
              type: 'tags',
              id: '4',
              attributes: {
                name: 'happy'
              },
              links: {
                self: '/tags/4'
              },
              relationships: {
                posts: {
                  links: {
                    self: '/tags/4/relationships/posts',
                    related: '/tags/4/posts'
                  },
                }
              }
            },
            {
              type: 'comments',
              id: '1',
              attributes: {
                body: 'what a dumb post'
              },
              links: {
                self: '/comments/1'
              },
              relationships: {
                author: {
                  links: {
                    self: '/comments/1/relationships/author',
                    related: '/comments/1/author'
                  }
                },
                post: {
                  links: {
                    self: '/comments/1/relationships/post',
                    related: '/comments/1/post'
                  }
                },
                tags: {
                  links: {
                    self: '/comments/1/relationships/tags',
                    related: '/comments/1/tags'
                  },
                  data: [
                    {type: 'tags', id: '1'},
                    {type: 'tags', id: '2'}
                  ]
                }
              }
            },
            {
              type: 'comments',
              id: '2',
              attributes: {
                body: 'i liked it'
              },
              links: {
                self: '/comments/2'
              },
              relationships: {
                author: {
                  links: {
                    self: '/comments/2/relationships/author',
                    related: '/comments/2/author'
                  }
                },
                post: {
                  links: {
                    self: '/comments/2/relationships/post',
                    related: '/comments/2/post'
                  }
                },
                tags: {
                  links: {
                    self: '/comments/2/relationships/tags',
                    related: '/comments/2/tags'
                  },
                  data: [
                    {type: 'tags', id: '1'},
                    {type: 'tags', id: '4'}
                  ]
                }
              }
            }
          ]
      },
      JSONAPI::ResourceSerializer.new(PostResource,
                                      include: ['comments', 'comments.tags']).serialize_to_hash(PostResource.new(@post, nil))
    )
  end

  def test_serializer_different_foreign_key
    serialized = JSONAPI::ResourceSerializer.new(
      PersonResource,
      include: ['comments']
    ).serialize_to_hash(PersonResource.new(@fred, nil))

    assert_hash_equals(
      {
        data: {
          type: 'people',
          id: '2',
          attributes: {
            name: 'Fred Reader',
            email: 'fred@xyz.fake',
            dateJoined: '2013-10-31 16:25:00 -0400'
          },
          links: {
            self: '/people/2'
          },
          relationships: {
            posts: {
              links: {
                self: '/people/2/relationships/posts',
                related: '/people/2/posts'
              }
            },
            comments: {
              links: {
                self: '/people/2/relationships/comments',
                related: '/people/2/comments'
              },
              data: [
                {type: 'comments', id: '2'},
                {type: 'comments', id: '3'}
              ]
            },
            preferences: {
              links: {
                self: "/people/2/relationships/preferences",
                related: "/people/2/preferences"
              }
            },
            hairCut: {
              links: {
                self: "/people/2/relationships/hairCut",
                related: "/people/2/hairCut"
              }
            },
            vehicles: {
              links: {
                self: "/people/2/relationships/vehicles",
                related: "/people/2/vehicles"
              }
            },
          }
        },
        included: [
          {
            type: 'comments',
            id: '2',
            attributes: {
              body: 'i liked it'
            },
            links: {
              self: '/comments/2'
            },
            relationships: {
              author: {
                links: {
                  self: '/comments/2/relationships/author',
                  related: '/comments/2/author'
                }
              },
              post: {
                links: {
                  self: '/comments/2/relationships/post',
                  related: '/comments/2/post'
                }
              },
              tags: {
                links: {
                  self: '/comments/2/relationships/tags',
                  related: '/comments/2/tags'
                }
              }
            }
          },
          {
            type: 'comments',
            id: '3',
            attributes: {
              body: 'Thanks man. Great post. But what is JR?'
            },
            links: {
              self: '/comments/3'
            },
            relationships: {
              author: {
                links: {
                  self: '/comments/3/relationships/author',
                  related: '/comments/3/author'
                }
              },
              post: {
                links: {
                  self: '/comments/3/relationships/post',
                  related: '/comments/3/post'
                }
              },
              tags: {
                links: {
                  self: '/comments/3/relationships/tags',
                  related: '/comments/3/tags'
                }
              }
            }
          }
        ]
      },
      serialized
    )
  end

  def test_serializer_array_of_resources_always_include_to_one_linkage_data

    posts = []
    Post.find(1, 2).each do |post|
      posts.push PostResource.new(post, nil)
    end

    JSONAPI.configuration.always_include_to_one_linkage_data = true

    assert_hash_equals(
      {
        data: [
          {
            type: 'posts',
            id: '1',
            attributes: {
              title: 'New post',
              body: 'A body!!!',
              subject: 'New post'
            },
            links: {
              self: '/posts/1'
            },
            relationships: {
              section: {
                links: {
                  self: '/posts/1/relationships/section',
                  related: '/posts/1/section'
                },
                data: nil
              },
              author: {
                links: {
                  self: '/posts/1/relationships/author',
                  related: '/posts/1/author'
                },
                data: {
                  type: 'people',
                  id: '1'
                }
              },
              tags: {
                links: {
                  self: '/posts/1/relationships/tags',
                  related: '/posts/1/tags'
                }
              },
              comments: {
                links: {
                  self: '/posts/1/relationships/comments',
                  related: '/posts/1/comments'
                },
                data: [
                  {type: 'comments', id: '1'},
                  {type: 'comments', id: '2'}
                ]
              }
            }
          },
          {
            type: 'posts',
            id: '2',
            attributes: {
              title: 'JR Solves your serialization woes!',
              body: 'Use JR',
              subject: 'JR Solves your serialization woes!'
            },
            links: {
              self: '/posts/2'
            },
            relationships: {
              section: {
                links: {
                  self: '/posts/2/relationships/section',
                  related: '/posts/2/section'
                },
                data: {
                  type: 'sections',
                  id: '2'
                }
              },
              author: {
                links: {
                  self: '/posts/2/relationships/author',
                  related: '/posts/2/author'
                },
                data: {
                  type: 'people',
                  id: '1'
                }
              },
              tags: {
                links: {
                  self: '/posts/2/relationships/tags',
                  related: '/posts/2/tags'
                }
              },
              comments: {
                links: {
                  self: '/posts/2/relationships/comments',
                  related: '/posts/2/comments'
                },
                data: [
                  {type: 'comments', id: '3'}
                ]
              }
            }
          }
        ],
        included: [
          {
            type: 'tags',
            id: '1',
            attributes: {
              name: 'short'
            },
            links: {
              self: '/tags/1'
            },
            relationships: {
              posts: {
                links: {
                  self: '/tags/1/relationships/posts',
                  related: '/tags/1/posts'
                }
              }
            }
          },
          {
            type: 'tags',
            id: '2',
            attributes: {
              name: 'whiny'
            },
            links: {
              self: '/tags/2'
            },
            relationships: {
              posts: {
                links: {
                  self: '/tags/2/relationships/posts',
                  related: '/tags/2/posts'
                }
              }
            }
          },
          {
            type: 'tags',
            id: '4',
            attributes: {
              name: 'happy'
            },
            links: {
              self: '/tags/4'
            },
            relationships: {
              posts: {
                links: {
                  self: '/tags/4/relationships/posts',
                  related: '/tags/4/posts'
                }
              }
            }
          },
          {
            type: 'tags',
            id: '5',
            attributes: {
              name: 'JR'
            },
            links: {
              self: '/tags/5'
            },
            relationships: {
              posts: {
                links: {
                  self: '/tags/5/relationships/posts',
                  related: '/tags/5/posts'
                }
              }
            }
          },
          {
            type: 'comments',
            id: '1',
            attributes: {
              body: 'what a dumb post'
            },
            links: {
              self: '/comments/1'
            },
            relationships: {
              author: {
                links: {
                  self: '/comments/1/relationships/author',
                  related: '/comments/1/author'
                },
                data: {
                  type: 'people',
                  id: '1'
                }
              },
              post: {
                links: {
                  self: '/comments/1/relationships/post',
                  related: '/comments/1/post'
                },
                data: {
                  type: 'posts',
                  id: '1'
                }
              },
              tags: {
                links: {
                  self: '/comments/1/relationships/tags',
                  related: '/comments/1/tags'
                },
                data: [
                  {type: 'tags', id: '1'},
                  {type: 'tags', id: '2'}
                ]
              }
            }
          },
          {
            type: 'comments',
            id: '2',
            attributes: {
              body: 'i liked it'
            },
            links: {
              self: '/comments/2'
            },
            relationships: {
              author: {
                links: {
                  self: '/comments/2/relationships/author',
                  related: '/comments/2/author'
                },
                data: {
                  type: 'people',
                  id: '2'
                }
              },
              post: {
                links: {
                  self: '/comments/2/relationships/post',
                  related: '/comments/2/post'
                },
                data: {
                  type: 'posts',
                  id: '1'
                }
              },
              tags: {
                links: {
                  self: '/comments/2/relationships/tags',
                  related: '/comments/2/tags'
                },
                data: [
                  {type: 'tags', id: '4'},
                  {type: 'tags', id: '1'}
                ]
              }
            }
          },
          {
            type: 'comments',
            id: '3',
            attributes: {
              body: 'Thanks man. Great post. But what is JR?'
            },
            links: {
              self: '/comments/3'
            },
            relationships: {
              author: {
                links: {
                  self: '/comments/3/relationships/author',
                  related: '/comments/3/author'
                },
                data: {
                  type: 'people',
                  id: '2'
                }
              },
              post: {
                links: {
                  self: '/comments/3/relationships/post',
                  related: '/comments/3/post'
                },
                data: {
                  type: 'posts',
                  id: '2'
                }
              },
              tags: {
                links: {
                  self: '/comments/3/relationships/tags',
                  related: '/comments/3/tags'
                },
                data: [
                  {type: 'tags', id: '5'}
                ]
              }
            }
          }
        ]
      },
      JSONAPI::ResourceSerializer.new(PostResource,
                                      include: ['comments', 'comments.tags']).serialize_to_hash(posts)
    )
  ensure
    JSONAPI.configuration.always_include_to_one_linkage_data = false
  end

  def test_serializer_array_of_resources

    posts = []
    Post.find(1, 2).each do |post|
      posts.push PostResource.new(post, nil)
    end

    assert_hash_equals(
      {
        data: [
          {
            type: 'posts',
            id: '1',
            attributes: {
              title: 'New post',
              body: 'A body!!!',
              subject: 'New post'
            },
            links: {
              self: '/posts/1'
            },
            relationships: {
              section: {
                links: {
                  self: '/posts/1/relationships/section',
                  related: '/posts/1/section'
                }
              },
              author: {
                links: {
                  self: '/posts/1/relationships/author',
                  related: '/posts/1/author'
                }
              },
              tags: {
                links: {
                  self: '/posts/1/relationships/tags',
                  related: '/posts/1/tags'
                }
              },
              comments: {
                links: {
                  self: '/posts/1/relationships/comments',
                  related: '/posts/1/comments'
                },
                data: [
                  {type: 'comments', id: '1'},
                  {type: 'comments', id: '2'}
                ]
              }
            }
          },
          {
            type: 'posts',
            id: '2',
            attributes: {
              title: 'JR Solves your serialization woes!',
              body: 'Use JR',
              subject: 'JR Solves your serialization woes!'
            },
            links: {
              self: '/posts/2'
            },
            relationships: {
              section: {
                links: {
                  self: '/posts/2/relationships/section',
                  related: '/posts/2/section'
                }
              },
              author: {
                links: {
                  self: '/posts/2/relationships/author',
                  related: '/posts/2/author'
                }
              },
              tags: {
                links: {
                  self: '/posts/2/relationships/tags',
                  related: '/posts/2/tags'
                }
              },
              comments: {
                links: {
                  self: '/posts/2/relationships/comments',
                  related: '/posts/2/comments'
                },
                data: [
                  {type: 'comments', id: '3'}
                ]
              }
            }
          }
        ],
        included: [
          {
            type: 'tags',
            id: '1',
            attributes: {
              name: 'short'
            },
            links: {
              self: '/tags/1'
            },
            relationships: {
              posts: {
                links: {
                  self: '/tags/1/relationships/posts',
                  related: '/tags/1/posts'
                }
              }
            }
          },
          {
            type: 'tags',
            id: '2',
            attributes: {
              name: 'whiny'
            },
            links: {
              self: '/tags/2'
            },
            relationships: {
              posts: {
                links: {
                  self: '/tags/2/relationships/posts',
                  related: '/tags/2/posts'
                }
              }
            }
          },
          {
            type: 'tags',
            id: '4',
            attributes: {
              name: 'happy'
            },
            links: {
              self: '/tags/4'
            },
            relationships: {
              posts: {
                links: {
                  self: '/tags/4/relationships/posts',
                  related: '/tags/4/posts'
                }
              }
            }
          },
          {
            type: 'tags',
            id: '5',
            attributes: {
              name: 'JR'
            },
            links: {
              self: '/tags/5'
            },
            relationships: {
              posts: {
                links: {
                  self: '/tags/5/relationships/posts',
                  related: '/tags/5/posts'
                }
              }
            }
          },
          {
            type: 'comments',
            id: '1',
            attributes: {
              body: 'what a dumb post'
            },
            links: {
              self: '/comments/1'
            },
            relationships: {
              author: {
                links: {
                  self: '/comments/1/relationships/author',
                  related: '/comments/1/author'
                }
              },
              post: {
                links: {
                  self: '/comments/1/relationships/post',
                  related: '/comments/1/post'
                }
              },
              tags: {
                links: {
                  self: '/comments/1/relationships/tags',
                  related: '/comments/1/tags'
                },
                data: [
                  {type: 'tags', id: '1'},
                  {type: 'tags', id: '2'}
                ]
              }
            }
          },
          {
            type: 'comments',
            id: '2',
            attributes: {
              body: 'i liked it'
            },
            links: {
              self: '/comments/2'
            },
            relationships: {
              author: {
                links: {
                  self: '/comments/2/relationships/author',
                  related: '/comments/2/author'
                }
              },
              post: {
                links: {
                  self: '/comments/2/relationships/post',
                  related: '/comments/2/post'
                }
              },
              tags: {
                links: {
                  self: '/comments/2/relationships/tags',
                  related: '/comments/2/tags'
                },
                data: [
                  {type: 'tags', id: '4'},
                  {type: 'tags', id: '1'}
                ]
              }
            }
          },
          {
            type: 'comments',
            id: '3',
            attributes: {
              body: 'Thanks man. Great post. But what is JR?'
            },
            links: {
              self: '/comments/3'
            },
            relationships: {
              author: {
                links: {
                  self: '/comments/3/relationships/author',
                  related: '/comments/3/author'
                }
              },
              post: {
                links: {
                  self: '/comments/3/relationships/post',
                  related: '/comments/3/post'
                }
              },
              tags: {
                links: {
                  self: '/comments/3/relationships/tags',
                  related: '/comments/3/tags'
                },
                data: [
                  {type: 'tags', id: '5'}
                ]
              }
            }
          }
        ]
      },
      JSONAPI::ResourceSerializer.new(PostResource,
                                      include: ['comments', 'comments.tags']).serialize_to_hash(posts)
    )
  end

  def test_serializer_array_of_resources_limited_fields

    posts = []
    Post.find(1, 2).each do |post|
      posts.push PostResource.new(post, nil)
    end

    assert_hash_equals(
      {
        data: [
          {
            type: 'posts',
            id: '1',
            attributes: {
              title: 'New post'
            },
            links: {
              self: '/posts/1'
            }
          },
          {
            type: 'posts',
            id: '2',
            attributes: {
              title: 'JR Solves your serialization woes!'
            },
            links: {
              self: '/posts/2'
            }
          }
        ],
        included: [
          {
            type: 'posts',
            id: '11',
            attributes: {
              title: 'JR How To'
            },
            links: {
              self: '/posts/11'
            }
          },
          {
            type: 'people',
            id: '1',
            attributes: {
              email: 'joe@xyz.fake'
            },
            links: {
              self: '/people/1'
            },
            relationships: {
              comments: {
                links: {
                  self: '/people/1/relationships/comments',
                  related: '/people/1/comments'
                }
              }
            }
          },
          {
            id: '1',
            type: 'tags',
            attributes: {
              name: 'short'
            },
            links: {
              self: '/tags/1'
            }
          },
          {
            id: '2',
            type: 'tags',
            attributes: {
              name: 'whiny'
            },
            links: {
              self: '/tags/2'
            }
          },
          {
            id: '4',
            type: 'tags',
            attributes: {
              name: 'happy'
            },
            links: {
              self: '/tags/4'
            }
          },
          {
            id: '5',
            type: 'tags',
            attributes: {
              name: 'JR'
            },
            links: {
              self: '/tags/5'
            }
          },
          {
            type: 'comments',
            id: '1',
            attributes: {
              body: 'what a dumb post'
            },
            links: {
              self: '/comments/1'
            },
            relationships: {
              post: {
                links: {
                  self: '/comments/1/relationships/post',
                  related: '/comments/1/post'
                }
              }
            }
          },
          {
            type: 'comments',
            id: '2',
            attributes: {
              body: 'i liked it'
            },
            links: {
              self: '/comments/2'
            },
            relationships: {
              post: {
                links: {
                  self: '/comments/2/relationships/post',
                  related: '/comments/2/post'
                }
              }
            }
          },
          {
            type: 'comments',
            id: '3',
            attributes: {
              body: 'Thanks man. Great post. But what is JR?'
            },
            links: {
              self: '/comments/3'
            },
            relationships: {
              post: {
                links: {
                  self: '/comments/3/relationships/post',
                  related: '/comments/3/post'
                }
              }
            }
          }
        ]
      },
      JSONAPI::ResourceSerializer.new(PostResource,
                                      include: ['comments', 'author', 'comments.tags', 'author.posts'],
                                      fields: {
                                        people: [:id, :email, :comments],
                                        posts: [:id, :title],
                                        tags: [:name],
                                        comments: [:id, :body, :post]
                                      }).serialize_to_hash(posts)
    )
  end

  def test_serializer_camelized_with_value_formatters
    assert_hash_equals(
      {
        data: {
          type: 'expenseEntries',
          id: '1',
          attributes: {
            transactionDate: '04/15/2014',
            cost: 12.05
          },
          links: {
            self: '/expenseEntries/1'
          },
          relationships: {
            isoCurrency: {
              links: {
                self: '/expenseEntries/1/relationships/isoCurrency',
                related: '/expenseEntries/1/isoCurrency'
              },
              data: {
                type: 'isoCurrencies',
                id: 'USD'
              }
            },
            employee: {
              links: {
                self: '/expenseEntries/1/relationships/employee',
                related: '/expenseEntries/1/employee'
              },
              data: {
                type: 'people',
                id: '3'
              }
            }
          }
        },
        included: [
          {
            type: 'isoCurrencies',
            id: 'USD',
            attributes: {
              countryName: 'United States',
              name: 'United States Dollar',
              minorUnit: 'cent'
            },
            links: {
              self: '/isoCurrencies/USD'
            }
          },
          {
            type: 'people',
            id: '3',
            attributes: {
              email: 'lazy@xyz.fake',
              name: 'Lazy Author',
              dateJoined: '2013-10-31 17:25:00 -0400'
            },
            links: {
              self: '/people/3',
            }
          }
        ]
      },
      JSONAPI::ResourceSerializer.new(ExpenseEntryResource,
                                      include: ['iso_currency', 'employee'],
                                      fields: {people: [:id, :name, :email, :date_joined]}).serialize_to_hash(
        ExpenseEntryResource.new(@expense_entry, nil))
    )
  end

  def test_serializer_empty_links_null_and_array
    planet_hash = JSONAPI::ResourceSerializer.new(PlanetResource).serialize_to_hash(
      PlanetResource.new(Planet.find(8), nil))

    assert_hash_equals(
      {
        data: {
          type: 'planets',
          id: '8',
          attributes: {
            name: 'Beta W',
            description: 'Newly discovered Planet W'
          },
          links: {
            self: '/planets/8'
          },
          relationships: {
            planetType: {
              links: {
                self: '/planets/8/relationships/planetType',
                related: '/planets/8/planetType'
              }
            },
            tags: {
              links: {
                self: '/planets/8/relationships/tags',
                related: '/planets/8/tags'
              }
            },
            moons: {
              links: {
                self: '/planets/8/relationships/moons',
                related: '/planets/8/moons'
              }
            }
          }
        }
      }, planet_hash)
  end

  def test_serializer_include_with_empty_links_null_and_array
    planets = []
    Planet.find(7, 8).each do |planet|
      planets.push PlanetResource.new(planet, nil)
    end

    planet_hash = JSONAPI::ResourceSerializer.new(PlanetResource,
                                                  include: ['planet_type'],
                                                  fields: { planet_types: [:id, :name] }).serialize_to_hash(planets)

    assert_hash_equals(
      {
        data: [{
          type: 'planets',
          id: '7',
          attributes: {
            name: 'Beta X',
            description: 'Newly discovered Planet Z'
          },
          links: {
            self: '/planets/7'
          },
          relationships: {
            planetType: {
              links: {
                self: '/planets/7/relationships/planetType',
                related: '/planets/7/planetType'
              },
              data: {
                type: 'planetTypes',
                id: '5'
              }
            },
            tags: {
              links: {
                self: '/planets/7/relationships/tags',
                related: '/planets/7/tags'
              }
            },
            moons: {
              links: {
                self: '/planets/7/relationships/moons',
                related: '/planets/7/moons'
              }
            }
          }
        },
        {
          type: 'planets',
          id: '8',
          attributes: {
            name: 'Beta W',
            description: 'Newly discovered Planet W'
          },
          links: {
            self: '/planets/8'
          },
          relationships: {
            planetType: {
              links: {
                self: '/planets/8/relationships/planetType',
                related: '/planets/8/planetType'
              },
              data: nil
            },
            tags: {
              links: {
                self: '/planets/8/relationships/tags',
                related: '/planets/8/tags'
              }
            },
            moons: {
              links: {
                self: '/planets/8/relationships/moons',
                related: '/planets/8/moons'
              }
            }
          }
        }
      ],
      included: [
        {
          type: 'planetTypes',
          id: '5',
          attributes: {
            name: 'unknown'
          },
          links: {
            self: '/planetTypes/5'
          }
        }
      ]
    }, planet_hash)
  end

  def test_serializer_booleans
    original_config = JSONAPI.configuration.dup
    JSONAPI.configuration.json_key_format = :underscored_key

    preferences = PreferencesResource.new(Preferences.find(1), nil)

    assert_hash_equals(
      {
        data: {
          type: 'preferences',
          id: '1',
          attributes: {
            advanced_mode: false
          },
          links: {
            self: '/preferences/1'
          },
          relationships: {
            author: {
              links: {
                self: '/preferences/1/relationships/author',
                related: '/preferences/1/author'
              }
            }
          }
        }
      },
      JSONAPI::ResourceSerializer.new(PreferencesResource).serialize_to_hash(preferences)
    )
  ensure
    JSONAPI.configuration = original_config
  end

  def test_serializer_data_types
    original_config = JSONAPI.configuration.dup
    JSONAPI.configuration.json_key_format = :underscored_key

    facts = FactResource.new(Fact.find(1), nil)

    assert_hash_equals(
      {
        data: {
          type: 'facts',
          id: '1',
          attributes: {
            spouse_name: 'Jane Author',
            bio: 'First man to run across Antartica.',
            quality_rating: 23.89/45.6,
            salary: BigDecimal('47000.56', 30),
            date_time_joined: DateTime.parse('2013-08-07 20:25:00 UTC +00:00'),
            birthday: Date.parse('1965-06-30'),
            bedtime: Time.parse('2000-01-01 20:00:00 UTC +00:00'), #DB seems to set the date to 2001-01-01 for time types
            photo: "abc",
            cool: false
          },
          links: {
            self: '/facts/1'
          }
        }
      },
      JSONAPI::ResourceSerializer.new(FactResource).serialize_to_hash(facts)
    )
  ensure
    JSONAPI.configuration = original_config
  end

  def test_serializer_to_one
    serialized = JSONAPI::ResourceSerializer.new(
      Api::V5::AuthorResource,
      include: ['author_detail']
    ).serialize_to_hash(Api::V5::AuthorResource.new(Person.find(1), nil))

    assert_hash_equals(
      {
        data: {
          type: 'authors',
          id: '1',
          attributes: {
            name: 'Joe Author',
          },
          links: {
            self: '/api/v5/authors/1'
          },
          relationships: {
            posts: {
              links: {
                self: '/api/v5/authors/1/relationships/posts',
                related: '/api/v5/authors/1/posts'
              }
            },
            authorDetail: {
              links: {
                self: '/api/v5/authors/1/relationships/authorDetail',
                related: '/api/v5/authors/1/authorDetail'
              },
              data: {type: 'authorDetails', id: '1'}
            }
          }
        },
        included: [
          {
            type: 'authorDetails',
            id: '1',
            attributes: {
              authorStuff: 'blah blah'
            },
            links: {
              self: '/api/v5/authorDetails/1'
            }
          }
        ]
      },
      serialized
    )
  end

  def test_serializer_resource_meta_fixed_value
    Api::V5::AuthorResource.class_eval do
      def meta(options)
        {
          fixed: 'Hardcoded value',
          computed: "#{self.class._type.to_s}: #{options[:serializer].link_builder.self_link(self)}"
        }
      end
    end

    serialized = JSONAPI::ResourceSerializer.new(
      Api::V5::AuthorResource,
      include: ['author_detail']
    ).serialize_to_hash(Api::V5::AuthorResource.new(Person.find(1), nil))

    assert_hash_equals(
      {
        data: {
          type: 'authors',
          id: '1',
          attributes: {
            name: 'Joe Author',
          },
          links: {
            self: '/api/v5/authors/1'
          },
          relationships: {
            posts: {
              links: {
                self: '/api/v5/authors/1/relationships/posts',
                related: '/api/v5/authors/1/posts'
              }
            },
            authorDetail: {
              links: {
                self: '/api/v5/authors/1/relationships/authorDetail',
                related: '/api/v5/authors/1/authorDetail'
              },
              data: {type: 'authorDetails', id: '1'}
            }
          },
          meta: {
            fixed: 'Hardcoded value',
            computed: 'authors: /api/v5/authors/1'
          }
        },
        included: [
          {
            type: 'authorDetails',
            id: '1',
            attributes: {
              authorStuff: 'blah blah'
            },
            links: {
              self: '/api/v5/authorDetails/1'
            }
          }
        ]
      },
      serialized
    )
  ensure
    Api::V5::AuthorResource.class_eval do
      def meta(options)
        # :nocov:
        { }
        # :nocov:
      end
    end
  end

  def test_serialize_model_attr
    @make = Make.first
    serialized = JSONAPI::ResourceSerializer.new(
      MakeResource,
    ).serialize_to_hash(MakeResource.new(@make, nil))

    assert_hash_equals(
      {
        "model" => "A model attribute"
      },
      serialized[:data]["attributes"]
    )
  end

  def test_confusingly_named_attrs
    @wp = WebPage.first
    serialized = JSONAPI::ResourceSerializer.new(
      WebPageResource,
    ).serialize_to_hash(WebPageResource.new(@wp, nil))

    assert_hash_equals(
      {
        :data=>{
          "id"=>"#{@wp.id}",
          "type"=>"webPages",
          "links"=>{
            :self=>"/webPages/#{@wp.id}"
          },
          "attributes"=>{
            "href"=>"http://example.com",
            "link"=>"http://link.example.com"
          }
        }
      },
      serialized
    )
  end

  def test_questionable_has_one
    # has_one
    out, err = capture_io do
      eval <<-CODE
          class ::Questionable < ActiveRecord::Base
            has_one :link
            has_one :href
          end
          class ::QuestionableResource < JSONAPI::Resource
            model_name '::Questionable'
            has_one :link
            has_one :href
          end
          cn = ::Questionable.new id: 1
          puts JSONAPI::ResourceSerializer.new(
            ::QuestionableResource,
          ).serialize_to_hash(::QuestionableResource.new(cn, nil))
      CODE
    end
    assert err.blank?
    assert_equal(
      {
        :data=>{
          "id"=>"1",
          "type"=>"questionables",
          "links"=>{
            :self=>"/questionables/1"
          },
          "relationships"=>{
            "link"=>{
              :links=>{
                :self=>"/questionables/1/relationships/link",
                :related=>"/questionables/1/link"
              }
            },
            "href"=>{
              :links=>{
                :self=>"/questionables/1/relationships/href",
                :related=>"/questionables/1/href"
              }
            }
          }
        }
      }.to_s,
      out.strip
    )
  end

  def test_questionable_has_many
    # has_one
    out, err = capture_io do
      eval <<-CODE
          class ::Questionable2 < ActiveRecord::Base
            self.table_name = 'questionables'
            has_many :links
            has_many :hrefs
          end
          class ::Questionable2Resource < JSONAPI::Resource
            model_name '::Questionable2'
            has_many :links
            has_many :hrefs
          end
          cn = ::Questionable2.new id: 1
          puts JSONAPI::ResourceSerializer.new(
            ::Questionable2Resource,
          ).serialize_to_hash(::Questionable2Resource.new(cn, nil))
      CODE
    end
    assert err.blank?
    assert_equal(
      {
        :data=>{
          "id"=>"1",
          "type"=>"questionable2s",
          "links"=>{
            :self=>"/questionable2s/1"
          },
          "relationships"=>{
            "links"=>{
              :links=>{
                :self=>"/questionable2s/1/relationships/links",
                :related=>"/questionable2s/1/links"
              }
            },
            "hrefs"=>{
              :links=>{
                :self=>"/questionable2s/1/relationships/hrefs",
                :related=>"/questionable2s/1/hrefs"
              }
            }
          }
        }
      }.to_s,
      out.strip
    )
  end
end
