require File.expand_path('../../test_helper', __FILE__)

def set_content_type_header!
  @request.headers['Content-Type'] = JSONAPI::MEDIA_TYPE
end

class PostsControllerTest < ActionController::TestCase
  def test_index
    get :index
    assert_response :success
    assert json_response['data'].is_a?(Array)
  end

  def test_exception_class_whitelist
    original_config = JSONAPI.configuration.dup
    JSONAPI.configuration.operations_processor = :error_raising
    # test that the operations processor rescues the error when it
    # has not been added to the exception_class_whitelist
    get :index
    assert_response 500
    # test that the operations processor does not rescue the error when it
    # has been added to the exception_class_whitelist
    JSONAPI.configuration.exception_class_whitelist << PostsController::SpecialError
    get :index
    assert_response 403
  ensure
    JSONAPI.configuration = original_config
  end

  def test_index_filter_with_empty_result
    get :index, {filter: {title: 'post that does not exist'}}
    assert_response :success
    assert json_response['data'].is_a?(Array)
    assert_equal 0, json_response['data'].size
  end

  def test_index_filter_by_id
    get :index, {filter: {id: '1'}}
    assert_response :success
    assert json_response['data'].is_a?(Array)
    assert_equal 1, json_response['data'].size
  end

  def test_index_filter_by_title
    get :index, {filter: {title: 'New post'}}
    assert_response :success
    assert json_response['data'].is_a?(Array)
    assert_equal 1, json_response['data'].size
  end

  def test_index_filter_by_ids
    get :index, {filter: {ids: '1,2'}}
    assert_response :success
    assert json_response['data'].is_a?(Array)
    assert_equal 2, json_response['data'].size
  end

  def test_index_filter_by_ids_and_include_related
    get :index, {filter: {id: '2'}, include: 'comments'}
    assert_response :success
    assert_equal 1, json_response['data'].size
    assert_equal 1, json_response['included'].size
  end

  def test_index_filter_by_ids_and_include_related_different_type
    get :index, {filter: {id: '1,2'}, include: 'author'}
    assert_response :success
    assert_equal 2, json_response['data'].size
    assert_equal 1, json_response['included'].size
  end

  def test_index_include_one_level_query_count
    count_queries do
      get :index, {include: 'author'}
    end
    assert_response :success
    assert_query_count(2)
  end

  def test_index_include_two_levels_query_count
    count_queries do
      get :index, {include: 'author,author.comments'}
    end
    assert_response :success
    assert_query_count(3)
  end

  def test_index_filter_by_ids_and_fields
    get :index, {filter: {id: '1,2'}, fields: {posts: 'id,title,author'}}
    assert_response :success
    assert_equal 2, json_response['data'].size

    # type, id, links, attributes, relationships
    assert_equal 5, json_response['data'][0].size
    assert json_response['data'][0].key?('type')
    assert json_response['data'][0].key?('id')
    assert json_response['data'][0]['attributes'].key?('title')
    assert json_response['data'][0].key?('links')
  end

  def test_index_filter_by_ids_and_fields_specify_type
    get :index, {filter: {id: '1,2'}, 'fields' => {'posts' => 'id,title,author'}}
    assert_response :success
    assert_equal 2, json_response['data'].size

    # type, id, links, attributes, relationships
    assert_equal 5, json_response['data'][0].size
    assert json_response['data'][0].key?('type')
    assert json_response['data'][0].key?('id')
    assert json_response['data'][0]['attributes'].key?('title')
    assert json_response['data'][0].key?('links')
  end

  def test_index_filter_by_ids_and_fields_specify_unrelated_type
    get :index, {filter: {id: '1,2'}, 'fields' => {'currencies' => 'code'}}
    assert_response :bad_request
    assert_match /currencies is not a valid resource./, json_response['errors'][0]['detail']
  end

  def test_index_filter_by_ids_and_fields_2
    get :index, {filter: {id: '1,2'}, fields: {posts: 'author'}}
    assert_response :success
    assert_equal 2, json_response['data'].size

    # type, id, links, relationships
    assert_equal 4, json_response['data'][0].size
    assert json_response['data'][0].key?('type')
    assert json_response['data'][0].key?('id')
    assert json_response['data'][0]['relationships'].key?('author')
  end

  def test_filter_relationship_single
    count_queries do
      get :index, {filter: {tags: '5,1'}}
    end
    assert_query_count(1)
    assert_response :success
    assert_equal 3, json_response['data'].size
    assert_match /New post/, response.body
    assert_match /JR Solves your serialization woes!/, response.body
    assert_match /JR How To/, response.body
  end

  def test_filter_relationships_multiple
    count_queries do
      get :index, {filter: {tags: '5,1', comments: '3'}}
    end
    assert_query_count(1)
    assert_response :success
    assert_equal 1, json_response['data'].size
    assert_match /JR Solves your serialization woes!/, response.body
  end

  def test_filter_relationships_multiple_not_found
    get :index, {filter: {tags: '1', comments: '3'}}
    assert_response :success
    assert_equal 0, json_response['data'].size
  end

  def test_bad_filter
    get :index, {filter: {post_ids: '1,2'}}
    assert_response :bad_request
    assert_match /post_ids is not allowed/, response.body
  end

  def test_bad_filter_value_not_integer_array
    get :index, {filter: {id: 'asdfg'}}
    assert_response :bad_request
    assert_match /asdfg is not a valid value for id/, response.body
  end

  def test_bad_filter_value_not_integer
    get :index, {filter: {id: 'asdfg'}}
    assert_response :bad_request
    assert_match /asdfg is not a valid value for id/, response.body
  end

  def test_bad_filter_value_not_found_array
    get :index, {filter: {id: '5412333'}}
    assert_response :not_found
    assert_match /5412333 could not be found/, response.body
  end

  def test_bad_filter_value_not_found
    get :index, {filter: {id: '5412333'}}
    assert_response :not_found
    assert_match /5412333 could not be found/, json_response['errors'][0]['detail']
  end

  def test_field_not_supported
    get :index, {filter: {id: '1,2'}, 'fields' => {'posts' => 'id,title,rank,author'}}
    assert_response :bad_request
    assert_match /rank is not a valid field for posts./, json_response['errors'][0]['detail']
  end

  def test_resource_not_supported
    get :index, {filter: {id: '1,2'}, 'fields' => {'posters' => 'id,title'}}
    assert_response :bad_request
    assert_match /posters is not a valid resource./, json_response['errors'][0]['detail']
  end

  def test_index_filter_on_relationship
    get :index, {filter: {author: '1'}}
    assert_response :success
    assert_equal 3, json_response['data'].size
  end

  def test_sorting_asc
    get :index, {sort: 'title'}

    assert_response :success
    assert_equal "A First Post", json_response['data'][0]['attributes']['title']
  end

  def test_sorting_desc
    get :index, {sort: '-title'}

    assert_response :success
    assert_equal "Update This Later - Multiple", json_response['data'][0]['attributes']['title']
  end

  def test_sorting_by_multiple_fields
    get :index, {sort: 'title,body'}

    assert_response :success
    assert_equal '14', json_response['data'][0]['id']
  end

  def test_invalid_sort_param
    get :index, {sort: 'asdfg'}

    assert_response :bad_request
    assert_match /asdfg is not a valid sort criteria for post/, response.body
  end

  def test_excluded_sort_param
    get :index, {sort: 'id'}

    assert_response :bad_request
    assert_match /id is not a valid sort criteria for post/, response.body
  end

  def test_show_single
    get :show, {id: '1'}
    assert_response :success
    assert json_response['data'].is_a?(Hash)
    assert_equal 'New post', json_response['data']['attributes']['title']
    assert_equal 'A body!!!', json_response['data']['attributes']['body']
    assert_nil json_response['included']
  end

  def test_show_does_not_include_records_count_in_meta
    JSONAPI.configuration.top_level_meta_include_record_count = true
    get :show, { id: Post.first.id }
    assert_response :success
    assert_equal json_response['meta'], nil
  ensure
    JSONAPI.configuration.top_level_meta_include_record_count = false
  end

  def test_show_single_with_includes
    get :show, {id: '1', include: 'comments'}
    assert_response :success
    assert json_response['data'].is_a?(Hash)
    assert_equal 'New post', json_response['data']['attributes']['title']
    assert_equal 'A body!!!', json_response['data']['attributes']['body']
    assert_nil json_response['data']['relationships']['tags']['data']
    assert matches_array?([{'type' => 'comments', 'id' => '1'}, {'type' => 'comments', 'id' => '2'}],
                          json_response['data']['relationships']['comments']['data'])
    assert_equal 2, json_response['included'].size
  end

  def test_show_single_with_fields
    get :show, {id: '1', fields: {posts: 'author'}}
    assert_response :success
    assert json_response['data'].is_a?(Hash)
    assert_nil json_response['data']['attributes']
  end

  def test_show_single_with_fields_string
    get :show, {id: '1', fields: 'author'}
    assert_response :bad_request
    assert_match /Fields must specify a type./, json_response['errors'][0]['detail']
  end

  def test_show_single_invalid_id_format
    get :show, {id: 'asdfg'}
    assert_response :bad_request
    assert_match /asdfg is not a valid value for id/, response.body
  end

  def test_show_single_missing_record
    get :show, {id: '5412333'}
    assert_response :not_found
    assert_match /record identified by 5412333 could not be found/, response.body
  end

  def test_show_malformed_fields_not_list
    get :show, {id: '1', 'fields' => ''}
    assert_response :bad_request
    assert_match /Fields must specify a type./, json_response['errors'][0]['detail']
  end

  def test_show_malformed_fields_type_not_list
    get :show, {id: '1', 'fields' => {'posts' => ''}}
    assert_response :bad_request
    assert_match /nil is not a valid field for posts./, json_response['errors'][0]['detail']
  end

  def test_create_simple
    set_content_type_header!
    post :create,
         {
           data: {
             type: 'posts',
             attributes: {
               title: 'JR is Great',
               body: 'JSONAPIResources is the greatest thing since unsliced bread.'
             },
             relationships: {
               author: {data: {type: 'people', id: '3'}}
             }
           }
         }

    assert_response :created
    assert json_response['data'].is_a?(Hash)
    assert_equal 'JR is Great', json_response['data']['attributes']['title']
    assert_equal 'JSONAPIResources is the greatest thing since unsliced bread.', json_response['data']['attributes']['body']
  end

  def test_create_link_to_missing_object
    set_content_type_header!
    post :create,
         {
           data: {
             type: 'posts',
             attributes: {
               title: 'JR is Great',
               body: 'JSONAPIResources is the greatest thing since unsliced bread.'
             },
             relationships: {
               author: {data: {type: 'people', id: '304567'}}
             }
           }
         }

    assert_response :unprocessable_entity
    # TODO: check if this validation is working
    assert_match /author - can't be blank/, response.body
  end

  def test_create_extra_param
    set_content_type_header!
    post :create,
         {
           data: {
             type: 'posts',
             attributes: {
               asdfg: 'aaaa',
               title: 'JR is Great',
               body: 'JSONAPIResources is the greatest thing since unsliced bread.'
             },
             relationships: {
               author: {data: {type: 'people', id: '3'}}
             }
           }
         }

    assert_response :bad_request
    assert_match /asdfg is not allowed/, response.body
  end

  def test_create_with_invalid_data
    set_content_type_header!
    post :create,
         {
           data: {
             type: 'posts',
             attributes: {
               title: 'JSONAPIResources is the greatest thing...',
               body: 'JSONAPIResources is the greatest thing since unsliced bread.'
             },
             relationships: {
               author: nil
             }
           }
         }

    assert_response :unprocessable_entity

    assert_equal "/data/relationships/author", json_response['errors'][0]['source']['pointer']
    assert_equal "can't be blank", json_response['errors'][0]['detail']
    assert_equal "author - can't be blank", json_response['errors'][0]['title']

    assert_equal "/data/attributes/title", json_response['errors'][1]['source']['pointer']
    assert_equal "is too long (maximum is 35 characters)", json_response['errors'][1]['detail']
    assert_equal "title - is too long (maximum is 35 characters)", json_response['errors'][1]['title']
  end

  def test_create_multiple
    set_content_type_header!
    post :create,
         {
           data: [
             {
               type: 'posts',
               attributes: {
                 title: 'JR is Great',
                 body: 'JSONAPIResources is the greatest thing since unsliced bread.'
               },
               relationships: {
                 author: {data: {type: 'people', id: '3'}}
               }
             },
             {
               type: 'posts',
               attributes: {
                 title: 'Ember is Great',
                 body: 'Ember is the greatest thing since unsliced bread.'
               },
               relationships: {
                 author: {data: {type: 'people', id: '3'}}
               }
             }
           ]
         }

    assert_response :created
    assert json_response['data'].is_a?(Array)
    assert_equal json_response['data'].size, 2
    assert_nil json_response['data'][0]['relationships']['author']['data']
    assert_match /JR is Great/, response.body
    assert_match /Ember is Great/, response.body
  end

  def test_create_multiple_wrong_case
    set_content_type_header!
    post :create,
         {
           data: [
             {
               type: 'posts',
               attributes: {
                 Title: 'JR is Great',
                 body: 'JSONAPIResources is the greatest thing since unsliced bread.'
               },
               relationships: {
                 author: {data: {type: 'people', id: '3'}}
               }
             },
             {
               type: 'posts',
               attributes: {
                 title: 'Ember is Great',
                 BODY: 'Ember is the greatest thing since unsliced bread.'
               },
               relationships: {
                 author: {data: {type: 'people', id: '3'}}
               }
             }
           ]
         }

    assert_response :bad_request
    assert_match /Title/, json_response['errors'][0]['detail']
  end

  def test_create_simple_missing_posts
    set_content_type_header!
    post :create,
         {
           data_spelled_wrong: {
             type: 'posts',
             attributes: {
               title: 'JR is Great',
               body: 'JSONAPIResources is the greatest thing since unsliced bread.'
             },
             relationships: {
               author: {data: {type: 'people', id: '3'}}
             }
           }
         }

    assert_response :bad_request
    assert_match /The required parameter, data, is missing./, json_response['errors'][0]['detail']
  end

  def test_create_simple_wrong_type
    set_content_type_header!
    post :create,
         {
           data: {
             type: 'posts_spelled_wrong',
             attributes: {
               title: 'JR is Great',
               body: 'JSONAPIResources is the greatest thing since unsliced bread.'
             },
             relationships: {
               author: {data: {type: 'people', id: '3'}}
             }
           }
         }

    assert_response :bad_request
    assert_match /posts_spelled_wrong is not a valid resource./, json_response['errors'][0]['detail']
  end

  def test_create_simple_missing_type
    set_content_type_header!
    post :create,
         {
           data: {
             attributes: {
               title: 'JR is Great',
               body: 'JSONAPIResources is the greatest thing since unsliced bread.'
             },
             relationships: {
               author: {data: {type: 'people', id: '3'}}
             }
           }
         }

    assert_response :bad_request
    assert_match /The required parameter, type, is missing./, json_response['errors'][0]['detail']
  end

  def test_create_simple_unpermitted_attributes
    set_content_type_header!
    post :create,
         {
           data: {
             type: 'posts',
             attributes: {
               subject: 'JR is Great',
               body: 'JSONAPIResources is the greatest thing since unsliced bread.'
             },
             relationships: {
               author: {data: {type: 'people', id: '3'}}
             }
           }
         }

    assert_response :bad_request
    assert_match /subject/, json_response['errors'][0]['detail']
  end

  def test_create_with_links_to_many_type_ids
    set_content_type_header!
    post :create,
         {
           data: {
             type: 'posts',
             attributes: {
               title: 'JR is Great',
               body: 'JSONAPIResources is the greatest thing since unsliced bread.'
             },
             relationships: {
               author: {data: {type: 'people', id: '3'}},
               tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
             }
           },
           include: 'author'
         }

    assert_response :created
    assert json_response['data'].is_a?(Hash)
    assert_equal '3', json_response['data']['relationships']['author']['data']['id']
    assert_equal 'JR is Great', json_response['data']['attributes']['title']
    assert_equal 'JSONAPIResources is the greatest thing since unsliced bread.', json_response['data']['attributes']['body']
  end

  def test_create_with_links_to_many_array
    set_content_type_header!
    post :create,
         {
           data: {
             type: 'posts',
             attributes: {
               title: 'JR is Great',
               body: 'JSONAPIResources is the greatest thing since unsliced bread.'
             },
             relationships: {
               author: {data: {type: 'people', id: '3'}},
               tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
             }
           },
           include: 'author'
         }

    assert_response :created
    assert json_response['data'].is_a?(Hash)
    assert_equal '3', json_response['data']['relationships']['author']['data']['id']
    assert_equal 'JR is Great', json_response['data']['attributes']['title']
    assert_equal 'JSONAPIResources is the greatest thing since unsliced bread.', json_response['data']['attributes']['body']
  end

  def test_create_with_links_include_and_fields
    set_content_type_header!
    post :create,
         {
           data: {
             type: 'posts',
             attributes: {
               title: 'JR is Great!',
               body: 'JSONAPIResources is the greatest thing since unsliced bread!'
             },
             relationships: {
               author: {data: {type: 'people', id: '3'}},
               tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
             }
           },
           include: 'author,author.posts',
           fields: {posts: 'id,title,author'}
         }

    assert_response :created
    assert json_response['data'].is_a?(Hash)
    assert_equal '3', json_response['data']['relationships']['author']['data']['id']
    assert_equal 'JR is Great!', json_response['data']['attributes']['title']
    assert_not_nil json_response['included'].size
  end

  def test_update_with_links
    set_content_type_header!
    javascript = Section.find_by(name: 'javascript')

    put :update,
        {
          id: 3,
          data: {
            id: '3',
            type: 'posts',
            attributes: {
              title: 'A great new Post'
            },
            relationships: {
              section: {data: {type: 'sections', id: "#{javascript.id}"}},
              tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
            }
          },
          include: 'tags,author,section'
        }

    assert_response :success
    assert json_response['data'].is_a?(Hash)
    assert_equal '3', json_response['data']['relationships']['author']['data']['id']
    assert_equal javascript.id.to_s, json_response['data']['relationships']['section']['data']['id']
    assert_equal 'A great new Post', json_response['data']['attributes']['title']
    assert_equal 'AAAA', json_response['data']['attributes']['body']
    assert matches_array?([{'type' => 'tags', 'id' => '3'}, {'type' => 'tags', 'id' => '4'}],
                          json_response['data']['relationships']['tags']['data'])
  end

  def test_update_with_internal_server_error
    set_content_type_header!
    post_object = Post.find(3)
    title = post_object.title

    put :update,
        {
          id: 3,
          data: {
            id: '3',
            type: 'posts',
            attributes: {
              title: 'BOOM'
            }
          }
        }

    assert_response 500
    post_object = Post.find(3)
    assert_equal title, post_object.title
  end

  def test_update_remove_links
    set_content_type_header!
    put :update,
        {
          id: 3,
          data: {
            id: '3',
            type: 'posts',
            attributes: {
              title: 'A great new Post'
            },
            relationships: {
              section: {data: {type: 'sections', id: 1}},
              tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
            }
          },
          include: 'tags'
        }

    assert_response :success

    put :update,
        {
          id: 3,
          data: {
            type: 'posts',
            id: 3,
            attributes: {
              title: 'A great new Post'
            },
            relationships: {
              section: nil,
              tags: []
            }
          },
          include: 'tags,author,section'
        }

    assert_response :success
    assert json_response['data'].is_a?(Hash)
    assert_equal '3', json_response['data']['relationships']['author']['data']['id']
    assert_equal nil, json_response['data']['relationships']['section']['data']
    assert_equal 'A great new Post', json_response['data']['attributes']['title']
    assert_equal 'AAAA', json_response['data']['attributes']['body']
    assert matches_array?([],
                          json_response['data']['relationships']['tags']['data'])
  end

  def test_update_relationship_to_one
    set_content_type_header!
    ruby = Section.find_by(name: 'ruby')
    post_object = Post.find(4)
    assert_not_equal ruby.id, post_object.section_id

    put :update_relationship, {post_id: 4, relationship: 'section', data: {type: 'sections', id: "#{ruby.id}"}}

    assert_response :no_content
    post_object = Post.find(4)
    assert_equal ruby.id, post_object.section_id
  end

  def test_update_relationship_to_one_invalid_links_hash_keys_ids
    set_content_type_header!
    put :update_relationship, {post_id: 3, relationship: 'section', data: {type: 'sections', ids: 'foo'}}

    assert_response :bad_request
    assert_match /Invalid Links Object/, response.body
  end

  def test_update_relationship_to_one_invalid_links_hash_count
    set_content_type_header!
    put :update_relationship, {post_id: 3, relationship: 'section', data: {type: 'sections'}}

    assert_response :bad_request
    assert_match /Invalid Links Object/, response.body
  end

  def test_update_relationship_to_many_not_array
    set_content_type_header!
    put :update_relationship, {post_id: 3, relationship: 'tags', data: {type: 'tags', id: 2}}

    assert_response :bad_request
    assert_match /Invalid Links Object/, response.body
  end

  def test_update_relationship_to_one_invalid_links_hash_keys_type_mismatch
    set_content_type_header!
    put :update_relationship, {post_id: 3, relationship: 'section', data: {type: 'comment', id: '3'}}

    assert_response :bad_request
    assert_match /Type Mismatch/, response.body
  end

  def test_update_nil_to_many_links
    set_content_type_header!
    put :update,
        {
          id: 3,
          data: {
            type: 'posts',
            id: 3,
            relationships: {
              tags: nil
            }
          }
        }

    assert_response :bad_request
    assert_match /Invalid Links Object/, response.body
  end

  def test_update_bad_hash_to_many_links
    set_content_type_header!
    put :update,
        {
          id: 3,
          data: {
            type: 'posts',
            id: 3,
            relationships: {
              tags: {data: {typ: 'bad link', idd: 'as'}}
            }
          }
        }

    assert_response :bad_request
    assert_match /Invalid Links Object/, response.body
  end

  def test_update_other_to_many_links
    set_content_type_header!
    put :update,
        {
          id: 3,
          data: {
            type: 'posts',
            id: 3,
            relationships: {
              tags: 'bad link'
            }
          }
        }

    assert_response :bad_request
    assert_match /Invalid Links Object/, response.body
  end

  def test_update_other_to_many_links_data_nil
    set_content_type_header!
    put :update,
        {
          id: 3,
          data: {
            type: 'posts',
            id: 3,
            relationships: {
              tags: {data: nil}
            }
          }
        }

    assert_response :bad_request
    assert_match /Invalid Links Object/, response.body
  end

  def test_update_relationship_to_one_singular_param_id_nil
    set_content_type_header!
    ruby = Section.find_by(name: 'ruby')
    post_object = Post.find(3)
    post_object.section = ruby
    post_object.save!

    put :update_relationship, {post_id: 3, relationship: 'section', data: {type: 'sections', id: nil}}

    assert_response :no_content
    assert_equal nil, post_object.reload.section_id
  end

  def test_update_relationship_to_one_data_nil
    set_content_type_header!
    ruby = Section.find_by(name: 'ruby')
    post_object = Post.find(3)
    post_object.section = ruby
    post_object.save!

    put :update_relationship, {post_id: 3, relationship: 'section', data: nil}

    assert_response :no_content
    assert_equal nil, post_object.reload.section_id
  end

  def test_remove_relationship_to_one
    set_content_type_header!
    ruby = Section.find_by(name: 'ruby')
    post_object = Post.find(3)
    post_object.section_id = ruby.id
    post_object.save!

    put :destroy_relationship, {post_id: 3, relationship: 'section'}

    assert_response :no_content
    post_object = Post.find(3)
    assert_equal nil, post_object.section_id
  end

  def test_update_relationship_to_one_singular_param
    set_content_type_header!
    ruby = Section.find_by(name: 'ruby')
    post_object = Post.find(3)
    post_object.section_id = nil
    post_object.save!

    put :update_relationship, {post_id: 3, relationship: 'section', data: {type: 'sections', id: "#{ruby.id}"}}

    assert_response :no_content
    post_object = Post.find(3)
    assert_equal ruby.id, post_object.section_id
  end

  def test_update_relationship_to_many_join_table_single
    set_content_type_header!
    put :update_relationship, {post_id: 3, relationship: 'tags', data: []}
    assert_response :no_content

    post_object = Post.find(3)
    assert_equal 0, post_object.tags.length

    put :update_relationship, {post_id: 3, relationship: 'tags', data: [{type: 'tags', id: 2}]}

    assert_response :no_content
    post_object = Post.find(3)
    assert_equal 1, post_object.tags.length

    put :update_relationship, {post_id: 3, relationship: 'tags', data: [{type: 'tags', id: 5}]}

    assert_response :no_content
    post_object = Post.find(3)
    tags = post_object.tags.collect { |tag| tag.id }
    assert_equal 1, tags.length
    assert matches_array? [5], tags
  end

  def test_update_relationship_to_many
    set_content_type_header!
    put :update_relationship, {post_id: 3, relationship: 'tags', data: [{type: 'tags', id: 2}, {type: 'tags', id: 3}]}

    assert_response :no_content
    post_object = Post.find(3)
    assert_equal 2, post_object.tags.collect { |tag| tag.id }.length
    assert matches_array? [2, 3], post_object.tags.collect { |tag| tag.id }
  end

  def test_create_relationship_to_many_join_table
    set_content_type_header!
    put :update_relationship, {post_id: 3, relationship: 'tags', data: [{type: 'tags', id: 2}, {type: 'tags', id: 3}]}

    assert_response :no_content
    post_object = Post.find(3)
    assert_equal 2, post_object.tags.collect { |tag| tag.id }.length
    assert matches_array? [2, 3], post_object.tags.collect { |tag| tag.id }

    post :create_relationship, {post_id: 3, relationship: 'tags', data: [{type: 'tags', id: 5}]}

    assert_response :no_content
    post_object = Post.find(3)
    assert_equal 3, post_object.tags.collect { |tag| tag.id }.length
    assert matches_array? [2, 3, 5], post_object.tags.collect { |tag| tag.id }
  end

  def test_create_relationship_to_many_mismatched_type
    set_content_type_header!
    post :create_relationship, {post_id: 3, relationship: 'tags', data: [{type: 'comments', id: 5}]}

    assert_response :bad_request
    assert_match /Type Mismatch/, response.body
  end

  def test_create_relationship_to_many_missing_id
    set_content_type_header!
    post :create_relationship, {post_id: 3, relationship: 'tags', data: [{type: 'tags', idd: 5}]}

    assert_response :bad_request
    assert_match /Data is not a valid Links Object./, response.body
  end

  def test_create_relationship_to_many_not_array
    set_content_type_header!
    post :create_relationship, {post_id: 3, relationship: 'tags', data: {type: 'tags', id: 5}}

    assert_response :bad_request
    assert_match /Data is not a valid Links Object./, response.body
  end

  def test_create_relationship_to_many_missing_data
    set_content_type_header!
    post :create_relationship, {post_id: 3, relationship: 'tags'}

    assert_response :bad_request
    assert_match /The required parameter, data, is missing./, response.body
  end

  def test_create_relationship_to_many_join
    set_content_type_header!
    post :create_relationship, {post_id: 4, relationship: 'tags', data: [{type: 'tags', id: 1}, {type: 'tags', id: 2}, {type: 'tags', id: 3}]}
    assert_response :no_content
  end

  def test_create_relationship_to_many_join_table_record_exists
    set_content_type_header!
    put :update_relationship, {post_id: 3, relationship: 'tags', data: [{type: 'tags', id: 2}, {type: 'tags', id: 3}]}

    assert_response :no_content
    post_object = Post.find(3)
    assert_equal 2, post_object.tags.collect { |tag| tag.id }.length
    assert matches_array? [2, 3], post_object.tags.collect { |tag| tag.id }

    post :create_relationship, {post_id: 3, relationship: 'tags', data: [{type: 'tags', id: 2}, {type: 'tags', id: 5}]}

    assert_response :bad_request
    assert_match /The relation to 2 already exists./, response.body
  end

  def test_update_relationship_to_many_missing_tags
    set_content_type_header!
    put :update_relationship, {post_id: 3, relationship: 'tags'}

    assert_response :bad_request
    assert_match /The required parameter, data, is missing./, response.body
  end

  def test_delete_relationship_to_many
    set_content_type_header!
    put :update_relationship, {post_id: 14, relationship: 'tags', data: [{type: 'tags', id: 2}, {type: 'tags', id: 3}]}
    assert_response :no_content
    p = Post.find(14)
    assert_equal [2, 3], p.tag_ids

    delete :destroy_relationship, {post_id: 14, relationship: 'tags', keys: '3'}

    p.reload
    assert_response :no_content
    assert_equal [2], p.tag_ids
  end

  def test_delete_relationship_to_many_does_not_exist
    set_content_type_header!
    put :update_relationship, {post_id: 14, relationship: 'tags', data: [{type: 'tags', id: 2}, {type: 'tags', id: 3}]}
    assert_response :no_content
    p = Post.find(14)
    assert_equal [2, 3], p.tag_ids

    delete :destroy_relationship, {post_id: 14, relationship: 'tags', keys: '4'}

    p.reload
    assert_response :not_found
    assert_equal [2, 3], p.tag_ids
  end

  def test_delete_relationship_to_many_with_empty_data
    set_content_type_header!
    put :update_relationship, {post_id: 14, relationship: 'tags', data: [{type: 'tags', id: 2}, {type: 'tags', id: 3}]}
    assert_response :no_content
    p = Post.find(14)
    assert_equal [2, 3], p.tag_ids

    put :update_relationship, {post_id: 14, relationship: 'tags', data: [] }

    p.reload
    assert_response :no_content
    assert_equal [], p.tag_ids
  end

  def test_update_mismatched_keys
    set_content_type_header!
    javascript = Section.find_by(name: 'javascript')

    put :update,
        {
          id: 3,
          data: {
            type: 'posts',
            id: 2,
            attributes: {
              title: 'A great new Post'
            },
            relationships: {
              section: {type: 'sections', id: "#{javascript.id}"},
              tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
            }
          }
        }

    assert_response :bad_request
    assert_match /The URL does not support the key 2/, response.body
  end

  def test_update_extra_param
    set_content_type_header!
    javascript = Section.find_by(name: 'javascript')

    put :update,
        {
          id: 3,
          data: {
            type: 'posts',
            id: '3',
            attributes: {
              asdfg: 'aaaa',
              title: 'A great new Post'
            },
            relationships: {
              section: {type: 'sections', id: "#{javascript.id}"},
              tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
            }
          }
        }

    assert_response :bad_request
    assert_match /asdfg is not allowed/, response.body
  end

  def test_update_extra_param_in_links
    set_content_type_header!
    javascript = Section.find_by(name: 'javascript')

    put :update,
        {
          id: 3,
          data: {
            type: 'posts',
            id: '3',
            attributes: {
              title: 'A great new Post'
            },
            relationships: {
              asdfg: 'aaaa',
              section: {type: 'sections', id: "#{javascript.id}"},
              tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
            }
          }
        }

    assert_response :bad_request
    assert_match /asdfg is not allowed/, response.body
  end

  def test_update_missing_param
    set_content_type_header!
    javascript = Section.find_by(name: 'javascript')

    put :update,
        {
          id: 3,
          data_spelled_wrong: {
            type: 'posts',
            attributes: {
              title: 'A great new Post'
            },
            relationships: {
              section: {type: 'sections', id: "#{javascript.id}"},
              tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
            }
          }
        }

    assert_response :bad_request
    assert_match /The required parameter, data, is missing./, response.body
  end

  def test_update_missing_key
    set_content_type_header!

    put :update,
        {
          id: 3,
          data: {
            type: 'posts',
            attributes: {
              title: 'A great new Post'
            }
          }
        }

    assert_response :bad_request
    assert_match /The resource object does not contain a key/, response.body
  end

  def test_update_missing_type
    set_content_type_header!
    javascript = Section.find_by(name: 'javascript')

    put :update,
        {
          id: 3,
          data: {
            id: '3',
            type_spelled_wrong: 'posts',
            attributes: {
              title: 'A great new Post'
            },
            relationships: {
              section: {type: 'sections', id: "#{javascript.id}"},
              tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
            }
          }
        }

    assert_response :bad_request
    assert_match /The required parameter, type, is missing./, response.body
  end

  def test_update_unknown_key
    set_content_type_header!
    javascript = Section.find_by(name: 'javascript')

    put :update,
        {
          id: 3,
          data: {
            id: '3',
            type: 'posts',
            body: 'asdfg',
            attributes: {
              title: 'A great new Post'
            },
            relationships: {
              section: {type: 'sections', id: "#{javascript.id}"},
              tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
            }
          }
        }

    assert_response :bad_request
    assert_match /body is not allowed/, response.body
  end

  def test_update_multiple
    set_content_type_header!
    javascript = Section.find_by(name: 'javascript')

    put :update,
        {
          id: [3, 16],
          data: [
            {
              type: 'posts',
              id: 3,
              attributes: {
                title: 'A great new Post QWERTY'
              },
              relationships: {
                section: {data: {type: 'sections', id: "#{javascript.id}"}},
                tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
              }
            },
            {
              type: 'posts',
              id: 16,
              attributes: {
                title: 'A great new Post ASDFG'
              },
              relationships: {
                section: {data: {type: 'sections', id: "#{javascript.id}"}},
                tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
              }
            }
          ],
          include: 'tags'
        }

    assert_response :success
    assert_equal json_response['data'].size, 2
    assert_equal json_response['data'][0]['attributes']['title'], 'A great new Post QWERTY'
    assert_equal json_response['data'][0]['attributes']['body'], 'AAAA'
    assert matches_array?([{'type' => 'tags', 'id' => '3'}, {'type' => 'tags', 'id' => '4'}],
                          json_response['data'][0]['relationships']['tags']['data'])

    assert_equal json_response['data'][1]['attributes']['title'], 'A great new Post ASDFG'
    assert_equal json_response['data'][1]['attributes']['body'], 'Not First!!!!'
    assert matches_array?([{'type' => 'tags', 'id' => '3'}, {'type' => 'tags', 'id' => '4'}],
                          json_response['data'][1]['relationships']['tags']['data'])
  end

  def test_update_multiple_missing_keys
    set_content_type_header!
    javascript = Section.find_by(name: 'javascript')

    put :update,
        {
          id: [3, 9],
          data: [
            {
              type: 'posts',
              attributes: {
                title: 'A great new Post ASDFG'
              },
              relationships: {
                section: {type: 'sections', id: "#{javascript.id}"},
                tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
              }
            },
            {
              type: 'posts',
              attributes: {
                title: 'A great new Post QWERTY'
              },
              relationships: {
                section: {type: 'sections', id: "#{javascript.id}"},
                tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
              }
            }
          ]}

    assert_response :bad_request
    assert_match /A key is required/, response.body
  end

  def test_update_mismatch_keys
    set_content_type_header!
    javascript = Section.find_by(name: 'javascript')

    put :update,
        {
          id: [3, 9],
          data: [
            {
              type: 'posts',
              id: 3,
              attributes: {
                title: 'A great new Post ASDFG'
              },
              relationships: {
                section: {data: {type: 'sections', id: "#{javascript.id}"}},
                tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
              }
            },
            {
              type: 'posts',
              id: 8,
              attributes: {
                title: 'A great new Post QWERTY'
              },
              relationships: {
                section: {data: {type: 'sections', id: "#{javascript.id}"}},
                tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
              }
            }
          ]}

    assert_response :bad_request
    assert_match /The URL does not support the key 8/, response.body
  end

  def test_update_multiple_count_mismatch
    set_content_type_header!
    javascript = Section.find_by(name: 'javascript')

    put :update,
        {
          id: [3, 9, 2],
          data: [
            {
              type: 'posts',
              id: 3,
              attributes: {
                title: 'A great new Post QWERTY'
              },
              relationships: {
                section: {type: 'sections', id: "#{javascript.id}"},
                tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
              }
            },
            {
              type: 'posts',
              id: 9,
              attributes: {
                title: 'A great new Post ASDFG'
              },
              relationships: {
                section: {type: 'sections', id: "#{javascript.id}"},
                tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
              }
            }
          ]}

    assert_response :bad_request
    assert_match /Count to key mismatch/, response.body
  end

  def test_update_unpermitted_attributes
    set_content_type_header!
    put :update,
        {
          id: 3,
          data: {
            type: 'posts',
            id: '3',
            attributes: {
              subject: 'A great new Post'
            },
            relationships: {
              author: {type: 'people', id: '1'},
              tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
            }
          }
        }

    assert_response :bad_request
    assert_match /author is not allowed./, response.body
    assert_match /subject is not allowed./, response.body
  end

  def test_update_bad_attributes
    set_content_type_header!
    put :update,
        {
          id: 3,
          data: {
            type: 'posts',
            attributes: {
              subject: 'A great new Post'
            },
            linked_objects: {
              author: {type: 'people', id: '1'},
              tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
            }
          }
        }

    assert_response :bad_request
  end

  def test_delete_single
    initial_count = Post.count
    delete :destroy, {id: '4'}
    assert_response :no_content
    assert_equal initial_count - 1, Post.count
  end

  def test_delete_multiple
    initial_count = Post.count
    delete :destroy, {id: '5,6'}
    assert_response :no_content
    assert_equal initial_count - 2, Post.count
  end

  def test_delete_multiple_one_does_not_exist
    initial_count = Post.count
    delete :destroy, {id: '5,6,99999'}
    assert_response :not_found
    assert_equal initial_count, Post.count
  end

  def test_show_to_one_relationship
    get :show_relationship, {post_id: '1', relationship: 'author'}
    assert_response :success
    assert_hash_equals json_response,
                       {data: {
                          type: 'people',
                          id: '1'
                        },
                        links: {
                          self: 'http://test.host/posts/1/relationships/author',
                          related: 'http://test.host/posts/1/author'
                        }
                       }
  end

  def test_show_to_many_relationship
    get :show_relationship, {post_id: '2', relationship: 'tags'}
    assert_response :success
    assert_hash_equals json_response,
                       {
                         data: [
                           {type: 'tags', id: '5'}
                         ],
                         links: {
                           self: 'http://test.host/posts/2/relationships/tags',
                           related: 'http://test.host/posts/2/tags'
                         }
                       }
  end

  def test_show_to_many_relationship_invalid_id
    get :show_relationship, {post_id: '2,1', relationship: 'tags'}
    assert_response :bad_request
    assert_match /2,1 is not a valid value for id/, response.body
  end

  def test_show_to_one_relationship_nil
    get :show_relationship, {post_id: '17', relationship: 'author'}
    assert_response :success
    assert_hash_equals json_response,
                       {
                         data: nil,
                         links: {
                           self: 'http://test.host/posts/17/relationships/author',
                           related: 'http://test.host/posts/17/author'
                         }
                       }
  end
end

class TagsControllerTest < ActionController::TestCase
  def test_tags_index
    get :index, {filter: {id: '6,7,8,9'}, include: 'posts.tags,posts.author.posts'}
    assert_response :success
    assert_equal 4, json_response['data'].size
    assert_equal 3, json_response['included'].size
  end

  def test_tags_show_multiple
    get :show, {id: '6,7,8,9'}
    assert_response :bad_request
    assert_match /6,7,8,9 is not a valid value for id/, response.body
  end

  def test_tags_show_multiple_with_include
    get :show, {id: '6,7,8,9', include: 'posts.tags,posts.author.posts'}
    assert_response :bad_request
    assert_match /6,7,8,9 is not a valid value for id/, response.body
  end

  def test_tags_show_multiple_with_nonexistent_ids
    get :show, {id: '6,99,9,100'}
    assert_response :bad_request
    assert_match /6,99,9,100 is not a valid value for id/, response.body
  end

  def test_tags_show_multiple_with_nonexistent_ids_at_the_beginning
    get :show, {id: '99,9,100'}
    assert_response :bad_request
    assert_match /99,9,100 is not a valid value for id/, response.body
  end
end

class ExpenseEntriesControllerTest < ActionController::TestCase
  def setup
    JSONAPI.configuration.json_key_format = :camelized_key
  end

  def test_text_error
    JSONAPI.configuration.use_text_errors = true
    get :index, {sort: 'not_in_record'}
    assert_response 400
    assert_equal 'INVALID_SORT_CRITERIA', json_response['errors'][0]['code']
    JSONAPI.configuration.use_text_errors = false
  end

  def test_expense_entries_index
    get :index
    assert_response :success
    assert json_response['data'].is_a?(Array)
    assert_equal 2, json_response['data'].size
  end

  def test_expense_entries_show
    get :show, {id: 1}
    assert_response :success
    assert json_response['data'].is_a?(Hash)
  end

  def test_expense_entries_show_include
    get :show, {id: 1, include: 'isoCurrency,employee'}
    assert_response :success
    assert json_response['data'].is_a?(Hash)
    assert_equal 2, json_response['included'].size
  end

  def test_expense_entries_show_bad_include_missing_relationship
    get :show, {id: 1, include: 'isoCurrencies,employees'}
    assert_response :bad_request
    assert_match /isoCurrencies is not a valid relationship of expenseEntries/, json_response['errors'][0]['detail']
    assert_match /employees is not a valid relationship of expenseEntries/, json_response['errors'][1]['detail']
  end

  def test_expense_entries_show_bad_include_missing_sub_relationship
    get :show, {id: 1, include: 'isoCurrency,employee.post'}
    assert_response :bad_request
    assert_match /post is not a valid relationship of people/, json_response['errors'][0]['detail']
  end

  def test_expense_entries_show_fields
    get :show, {id: 1, include: 'isoCurrency,employee', 'fields' => {'expenseEntries' => 'transactionDate'}}
    assert_response :success
    assert json_response['data'].is_a?(Hash)
    assert json_response['data']['attributes'].key?('transactionDate')
    assert_equal 2, json_response['included'].size
  end

  def test_expense_entries_show_fields_type_many
    get :show, {id: 1, include: 'isoCurrency,employee', 'fields' => {'expenseEntries' => 'transactionDate',
                                                                     'isoCurrencies' => 'id,name'}}
    assert_response :success
    assert json_response['data'].is_a?(Hash)
    assert json_response['data']['attributes'].key?('transactionDate')
    assert_equal 2, json_response['included'].size
  end

  def test_create_expense_entries_underscored
    set_content_type_header!
    JSONAPI.configuration.json_key_format = :underscored_key

    post :create,
         {
           data: {
             type: 'expense_entries',
             attributes: {
               transaction_date: '2014/04/15',
               cost: 50.58
             },
             relationships: {
               employee: {data: {type: 'people', id: '3'}},
               iso_currency: {data: {type: 'iso_currencies', id: 'USD'}}
             }
           },
           include: 'iso_currency,employee',
           fields: {expense_entries: 'id,transaction_date,iso_currency,cost,employee'}
         }

    assert_response :created
    assert json_response['data'].is_a?(Hash)
    assert_equal '3', json_response['data']['relationships']['employee']['data']['id']
    assert_equal 'USD', json_response['data']['relationships']['iso_currency']['data']['id']
    assert_equal '50.58', json_response['data']['attributes']['cost']

    delete :destroy, {id: json_response['data']['id']}
    assert_response :no_content
  end

  def test_create_expense_entries_camelized_key
    set_content_type_header!
    JSONAPI.configuration.json_key_format = :camelized_key

    post :create,
         {
           data: {
             type: 'expense_entries',
             attributes: {
               transactionDate: '2014/04/15',
               cost: 50.58
             },
             relationships: {
               employee: {data: {type: 'people', id: '3'}},
               isoCurrency: {data: {type: 'iso_currencies', id: 'USD'}}
             }
           },
           include: 'isoCurrency,employee',
           fields: {expenseEntries: 'id,transactionDate,isoCurrency,cost,employee'}
         }

    assert_response :created
    assert json_response['data'].is_a?(Hash)
    assert_equal '3', json_response['data']['relationships']['employee']['data']['id']
    assert_equal 'USD', json_response['data']['relationships']['isoCurrency']['data']['id']
    assert_equal '50.58', json_response['data']['attributes']['cost']

    delete :destroy, {id: json_response['data']['id']}
    assert_response :no_content
  end

  def test_create_expense_entries_dasherized_key
    set_content_type_header!
    JSONAPI.configuration.json_key_format = :dasherized_key

    post :create,
         {
           data: {
             type: 'expense_entries',
             attributes: {
               'transaction-date' => '2014/04/15',
               cost: 50.58
             },
             relationships: {
               employee: {data: {type: 'people', id: '3'}},
               'iso-currency' => {data: {type: 'iso_currencies', id: 'USD'}}
             }
           },
           include: 'iso-currency,employee',
           fields: {'expense-entries' => 'id,transaction-date,iso-currency,cost,employee'}
         }

    assert_response :created
    assert json_response['data'].is_a?(Hash)
    assert_equal '3', json_response['data']['relationships']['employee']['data']['id']
    assert_equal 'USD', json_response['data']['relationships']['iso-currency']['data']['id']
    assert_equal '50.58', json_response['data']['attributes']['cost']

    delete :destroy, {id: json_response['data']['id']}
    assert_response :no_content
  end
end

class IsoCurrenciesControllerTest < ActionController::TestCase
  def after_teardown
    JSONAPI.configuration.json_key_format = :camelized_key
  end

  def test_currencies_show
    get :show, {id: 'USD'}
    assert_response :success
    assert json_response['data'].is_a?(Hash)
  end

  def test_create_currencies_client_generated_id
    set_content_type_header!
    JSONAPI.configuration.json_key_format = :underscored_route

    post :create,
         {
           data: {
             type: 'iso_currencies',
             id: 'BTC',
             attributes: {
               name: 'Bit Coin',
               'country_name' => 'global',
               'minor_unit' => 'satoshi'
             }
           }
         }

    assert_response :created
    assert_equal 'BTC', json_response['data']['id']
    assert_equal 'Bit Coin', json_response['data']['attributes']['name']
    assert_equal 'global', json_response['data']['attributes']['country_name']
    assert_equal 'satoshi', json_response['data']['attributes']['minor_unit']

    delete :destroy, {id: json_response['data']['id']}
    assert_response :no_content
  end

  def test_currencies_primary_key_sort
    get :index, {sort: 'id'}
    assert_response :success
    assert_equal 3, json_response['data'].size
    assert_equal 'CAD', json_response['data'][0]['id']
    assert_equal 'EUR', json_response['data'][1]['id']
    assert_equal 'USD', json_response['data'][2]['id']
  end

  def test_currencies_code_sort
    get :index, {sort: 'code'}
    assert_response :bad_request
  end

  def test_currencies_json_key_underscored_sort
    JSONAPI.configuration.json_key_format = :underscored_key
    get :index, {sort: 'country_name'}
    assert_response :success
    assert_equal 3, json_response['data'].size
    assert_equal 'Canada', json_response['data'][0]['attributes']['country_name']
    assert_equal 'Euro Member Countries', json_response['data'][1]['attributes']['country_name']
    assert_equal 'United States', json_response['data'][2]['attributes']['country_name']

    # reverse sort
    get :index, {sort: '-country_name'}
    assert_response :success
    assert_equal 3, json_response['data'].size
    assert_equal 'United States', json_response['data'][0]['attributes']['country_name']
    assert_equal 'Euro Member Countries', json_response['data'][1]['attributes']['country_name']
    assert_equal 'Canada', json_response['data'][2]['attributes']['country_name']
  end

  def test_currencies_json_key_dasherized_sort
    JSONAPI.configuration.json_key_format = :dasherized_key
    get :index, {sort: 'country-name'}
    assert_response :success
    assert_equal 3, json_response['data'].size
    assert_equal 'Canada', json_response['data'][0]['attributes']['country-name']
    assert_equal 'Euro Member Countries', json_response['data'][1]['attributes']['country-name']
    assert_equal 'United States', json_response['data'][2]['attributes']['country-name']

    # reverse sort
    get :index, {sort: '-country-name'}
    assert_response :success
    assert_equal 3, json_response['data'].size
    assert_equal 'United States', json_response['data'][0]['attributes']['country-name']
    assert_equal 'Euro Member Countries', json_response['data'][1]['attributes']['country-name']
    assert_equal 'Canada', json_response['data'][2]['attributes']['country-name']
  end

  def test_currencies_json_key_custom_json_key_sort
    JSONAPI.configuration.json_key_format = :upper_camelized_key
    get :index, {sort: 'CountryName'}
    assert_response :success
    assert_equal 3, json_response['data'].size
    assert_equal 'Canada', json_response['data'][0]['attributes']['CountryName']
    assert_equal 'Euro Member Countries', json_response['data'][1]['attributes']['CountryName']
    assert_equal 'United States', json_response['data'][2]['attributes']['CountryName']

    # reverse sort
    get :index, {sort: '-CountryName'}
    assert_response :success
    assert_equal 3, json_response['data'].size
    assert_equal 'United States', json_response['data'][0]['attributes']['CountryName']
    assert_equal 'Euro Member Countries', json_response['data'][1]['attributes']['CountryName']
    assert_equal 'Canada', json_response['data'][2]['attributes']['CountryName']
  end

  def test_currencies_json_key_underscored_filter
    JSONAPI.configuration.json_key_format = :underscored_key
    get :index, {filter: {country_name: 'Canada'}}
    assert_response :success
    assert_equal 1, json_response['data'].size
    assert_equal 'Canada', json_response['data'][0]['attributes']['country_name']
  end

  def test_currencies_json_key_camelized_key_filter
    JSONAPI.configuration.json_key_format = :camelized_key
    get :index, {filter: {'countryName' => 'Canada'}}
    assert_response :success
    assert_equal 1, json_response['data'].size
    assert_equal 'Canada', json_response['data'][0]['attributes']['countryName']
  end

  def test_currencies_json_key_custom_json_key_filter
    JSONAPI.configuration.json_key_format = :upper_camelized_key
    get :index, {filter: {'CountryName' => 'Canada'}}
    assert_response :success
    assert_equal 1, json_response['data'].size
    assert_equal 'Canada', json_response['data'][0]['attributes']['CountryName']
  end
end

class PeopleControllerTest < ActionController::TestCase
  def setup
    JSONAPI.configuration.json_key_format = :camelized_key
  end

  def test_create_validations
    set_content_type_header!
    post :create,
         {
           data: {
             type: 'people',
             attributes: {
               name: 'Steve Jobs',
               email: 'sj@email.zzz',
               dateJoined: DateTime.parse('2014-1-30 4:20:00 UTC +00:00')
             }
           }
         }

    assert_response :success
  end

  def test_update_link_with_dasherized_type
    JSONAPI.configuration.json_key_format = :dasherized_key
    set_content_type_header!
    put :update,
        {
          id: 3,
          data: {
            id: '3',
            type: 'people',
            relationships: {
              'hair-cut' => {
                data: {
                  type: 'hair-cuts',
                  id: '1'
                }
              }
            }
          }
        }
    assert_response :success
  end

  def test_create_validations_missing_attribute
    set_content_type_header!
    post :create,
         {
           data: {
             type: 'people',
             attributes: {
               email: 'sj@email.zzz'
             }
           }
         }

    assert_response :unprocessable_entity
    assert_equal 2, json_response['errors'].size
    assert_equal JSONAPI::VALIDATION_ERROR, json_response['errors'][0]['code']
    assert_equal JSONAPI::VALIDATION_ERROR, json_response['errors'][1]['code']
    assert_match /dateJoined - can't be blank/, response.body
    assert_match /name - can't be blank/, response.body
  end

  def test_update_validations_missing_attribute
    set_content_type_header!
    put :update,
        {
          id: 3,
          data: {
            id: '3',
            type: 'people',
            attributes: {
              name: ''
            }
          }
        }

    assert_response :unprocessable_entity
    assert_equal 1, json_response['errors'].size
    assert_equal JSONAPI::VALIDATION_ERROR, json_response['errors'][0]['code']
    assert_match /name - can't be blank/, response.body
  end

  def test_delete_locked
    initial_count = Person.count
    delete :destroy, {id: '3'}
    assert_response :locked
    assert_equal initial_count, Person.count
  end

  def test_invalid_filter_value
    get :index, {filter: {name: 'L'}}
    assert_response :bad_request
  end

  def test_valid_filter_value
    get :index, {filter: {name: 'Joe Author'}}
    assert_response :success
    assert_equal json_response['data'].size, 1
    assert_equal json_response['data'][0]['id'], '1'
    assert_equal json_response['data'][0]['attributes']['name'], 'Joe Author'
  end

  def test_get_related_resource
    JSONAPI.configuration.json_key_format = :dasherized_key
    JSONAPI.configuration.route_format = :underscored_key
    get :get_related_resource, {post_id: '2', relationship: 'author', source:'posts'}
    assert_response :success
    assert_hash_equals(
      {
        data: {
         id: '1',
         type: 'people',
         attributes: {
           name: 'Joe Author',
           email: 'joe@xyz.fake',
           "date-joined" => '2013-08-07 16:25:00 -0400'
         },
         links: {
           self: 'http://test.host/people/1'
         },
         relationships: {
           comments: {
             links: {
               self: 'http://test.host/people/1/relationships/comments',
               related: 'http://test.host/people/1/comments'
             }
           },
           posts: {
             links: {
               self: 'http://test.host/people/1/relationships/posts',
               related: 'http://test.host/people/1/posts'
             }
           },
           preferences: {
             links: {
               self: 'http://test.host/people/1/relationships/preferences',
               related: 'http://test.host/people/1/preferences'
             }
           },
           "hair-cut" => {
             "links" => {
               "self" => "http://test.host/people/1/relationships/hair_cut",
               "related" => "http://test.host/people/1/hair_cut"
             }
           },
           vehicles: {
             links: {
               self: "http://test.host/people/1/relationships/vehicles",
               related: "http://test.host/people/1/vehicles"
             }
           }
         }
        }
      },
      json_response
    )
  end

  def test_get_related_resource_nil
    get :get_related_resource, {post_id: '17', relationship: 'author', source:'posts'}
    assert_response :success
    assert_hash_equals json_response,
                       {
                         data: nil
                       }

  end
end

class Api::V5::AuthorsControllerTest < ActionController::TestCase
  def test_get_person_as_author
    get :index, {filter: {id: '1'}}
    assert_response :success
    assert_equal 1, json_response['data'].size
    assert_equal '1', json_response['data'][0]['id']
    assert_equal 'authors', json_response['data'][0]['type']
    assert_equal 'Joe Author', json_response['data'][0]['attributes']['name']
    assert_equal nil, json_response['data'][0]['attributes']['email']
  end

  def test_get_person_as_author_by_name_filter
    get :index, {filter: {name: 'thor'}}
    assert_response :success
    assert_equal 3, json_response['data'].size
    assert_equal '1', json_response['data'][0]['id']
    assert_equal 'Joe Author', json_response['data'][0]['attributes']['name']
  end
end

class BreedsControllerTest < ActionController::TestCase
  # Note: Breed names go through the TitleValueFormatter

  def test_poro_index
    get :index
    assert_response :success
    assert_equal '0', json_response['data'][0]['id']
    assert_equal 'Persian', json_response['data'][0]['attributes']['name']
  end

  def test_poro_show
    get :show, {id: '0'}
    assert_response :success
    assert json_response['data'].is_a?(Hash)
    assert_equal '0', json_response['data']['id']
    assert_equal 'Persian', json_response['data']['attributes']['name']
  end

  def test_poro_show_multiple
    get :show, {id: '0,2'}

    assert_response :bad_request
    assert_match /0,2 is not a valid value for id/, response.body
  end

  def test_poro_create_simple
    set_content_type_header!
    post :create,
         {
           data: {
             type: 'breeds',
             attributes: {
               name: 'tabby'
             }
           }
         }

    assert_response :accepted
    assert json_response['data'].is_a?(Hash)
    assert_equal 'Tabby', json_response['data']['attributes']['name']
  end

  def test_poro_create_validation_error
    set_content_type_header!
    post :create,
         {
           data: {
             type: 'breeds',
             attributes: {
               name: ''
             }
           }
         }

    assert_equal 1, json_response['errors'].size
    assert_equal JSONAPI::VALIDATION_ERROR, json_response['errors'][0]['code']
    assert_match /name - can't be blank/, response.body
  end

  def test_poro_create_update
    set_content_type_header!
    post :create,
         {
           data: {
             type: 'breeds',
             attributes: {
               name: 'CALIC'
             }
           }
         }

    assert_response :accepted
    assert json_response['data'].is_a?(Hash)
    assert_equal 'Calic', json_response['data']['attributes']['name']

    put :update,
        {
          id: json_response['data']['id'],
          data: {
            id: json_response['data']['id'],
            type: 'breeds',
            attributes: {
              name: 'calico'
            }
          }
        }
    assert_response :success
    assert json_response['data'].is_a?(Hash)
    assert_equal 'Calico', json_response['data']['attributes']['name']
  end

  def test_poro_delete
    initial_count = $breed_data.breeds.keys.count
    delete :destroy, {id: '3'}
    assert_response :no_content
    assert_equal initial_count - 1, $breed_data.breeds.keys.count
  end

end

class Api::V2::PreferencesControllerTest < ActionController::TestCase
  def test_show_singleton_resource_without_id
    get :show
    assert_response :success
  end
end

class Api::V1::PostsControllerTest < ActionController::TestCase
  def test_show_post_namespaced
    get :show, {id: '1'}
    assert_response :success
    assert_equal 'http://test.host/api/v1/posts/1/relationships/writer', json_response['data']['relationships']['writer']['links']['self']
  end

  def test_show_post_namespaced_include
    get :show, {id: '1', include: 'writer'}
    assert_response :success
    assert_equal '1', json_response['data']['relationships']['writer']['data']['id']
    assert_nil json_response['data']['relationships']['tags']
    assert_equal '1', json_response['included'][0]['id']
    assert_equal 'writers', json_response['included'][0]['type']
    assert_equal 'joe@xyz.fake', json_response['included'][0]['attributes']['email']
  end

  def test_index_filter_on_relationship_namespaced
    get :index, {filter: {writer: '1'}}
    assert_response :success
    assert_equal 3, json_response['data'].size
  end

  def test_sorting_desc_namespaced
    get :index, {sort: '-title'}

    assert_response :success
    assert_equal "Update This Later - Multiple", json_response['data'][0]['attributes']['title']
  end

  def test_create_simple_namespaced
    set_content_type_header!
    post :create,
         {
           data: {
             type: 'posts',
             attributes: {
               title: 'JR - now with Namespacing',
               body: 'JSONAPIResources is the greatest thing since unsliced bread now that it has namespaced resources.'
             },
             relationships: {
               writer: { data: {type: 'writers', id: '3'}}
             }
           }
         }

    assert_response :created
    assert json_response['data'].is_a?(Hash)
    assert_equal 'JR - now with Namespacing', json_response['data']['attributes']['title']
    assert_equal 'JSONAPIResources is the greatest thing since unsliced bread now that it has namespaced resources.',
                 json_response['data']['attributes']['body']
  end

end

class FactsControllerTest < ActionController::TestCase
  def test_type_formatting
    JSONAPI.configuration.json_key_format = :camelized_key
    get :show, {id: '1'}
    assert_response :success
    assert json_response['data'].is_a?(Hash)
    assert_equal 'Jane Author', json_response['data']['attributes']['spouseName']
    assert_equal 'First man to run across Antartica.', json_response['data']['attributes']['bio']
    assert_equal 23.89/45.6, json_response['data']['attributes']['qualityRating']
    assert_equal '47000.56', json_response['data']['attributes']['salary']
    assert_equal '2013-08-07T20:25:00Z', json_response['data']['attributes']['dateTimeJoined']
    assert_equal '1965-06-30', json_response['data']['attributes']['birthday']
    assert_equal '2000-01-01T20:00:00Z', json_response['data']['attributes']['bedtime']
    assert_equal 'abc', json_response['data']['attributes']['photo']
    assert_equal false, json_response['data']['attributes']['cool']
  end

  def test_create_with_invalid_data
    JSONAPI.configuration.json_key_format = :dasherized_key
    set_content_type_header!
    post :create,
         {
           data: {
             type: 'facts',
             attributes: {
               bio: '',
               :"quality-rating" => '',
               :"spouse-name" => '',
               salary: 100000,
               :"date-time-joined" => '',
               birthday: '',
               bedtime: '',
               photo: 'abc',
               cool: false
             },
             relationships: {
             }
           }
         }

    assert_response :unprocessable_entity

    assert_equal "/data/attributes/spouse-name", json_response['errors'][0]['source']['pointer']
    assert_equal "can't be blank", json_response['errors'][0]['detail']
    assert_equal "spouse-name - can't be blank", json_response['errors'][0]['title']

    assert_equal "/data/attributes/bio", json_response['errors'][1]['source']['pointer']
    assert_equal "can't be blank", json_response['errors'][1]['detail']
    assert_equal "bio - can't be blank", json_response['errors'][1]['title']
  end
end

class Api::V2::BooksControllerTest < ActionController::TestCase
  def setup
    JSONAPI.configuration.json_key_format = :dasherized_key
    $test_user = Person.find(1)
  end

  def after_teardown
    Api::V2::BookResource.paginator :offset
  end

  def test_books_offset_pagination_no_params
    Api::V2::BookResource.paginator :offset

    get :index
    assert_response :success
    assert_equal 10, json_response['data'].size
    assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
  end

  def test_books_record_count_in_meta
    Api::V2::BookResource.paginator :offset
    JSONAPI.configuration.top_level_meta_include_record_count = true
    get :index, {include: 'book-comments'}
    JSONAPI.configuration.top_level_meta_include_record_count = false

    assert_response :success
    assert_equal 901, json_response['meta']['record-count']
    assert_equal 10, json_response['data'].size
    assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
  end

  def test_books_record_count_in_meta_custom_name
    Api::V2::BookResource.paginator :offset
    JSONAPI.configuration.top_level_meta_include_record_count = true
    JSONAPI.configuration.top_level_meta_record_count_key = 'total_records'

    get :index, {include: 'book-comments'}
    JSONAPI.configuration.top_level_meta_include_record_count = false
    JSONAPI.configuration.top_level_meta_record_count_key = :record_count

    assert_response :success
    assert_equal 901, json_response['meta']['total-records']
    assert_equal 10, json_response['data'].size
    assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
  end

  def test_books_offset_pagination_no_params_includes_query_count_one_level
    Api::V2::BookResource.paginator :offset

    count_queries do
      get :index, {include: 'book-comments'}
    end
    assert_response :success
    assert_equal 10, json_response['data'].size
    assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
    assert_query_count(3)
  end

  def test_books_offset_pagination_no_params_includes_query_count_two_levels
    Api::V2::BookResource.paginator :offset

    count_queries do
      get :index, {include: 'book-comments,book-comments.author'}
    end
    assert_response :success
    assert_equal 10, json_response['data'].size
    assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
    assert_query_count(4)
  end

  def test_books_offset_pagination
    Api::V2::BookResource.paginator :offset

    get :index, {page: {offset: 50, limit: 12}}
    assert_response :success
    assert_equal 12, json_response['data'].size
    assert_equal 'Book 50', json_response['data'][0]['attributes']['title']
  end

  def test_books_offset_pagination_bad_page_param
    Api::V2::BookResource.paginator :offset

    get :index, {page: {offset_bad: 50, limit: 12}}
    assert_response :bad_request
    assert_match /offset_bad is not an allowed page parameter./, json_response['errors'][0]['detail']
  end

  def test_books_offset_pagination_bad_param_value_limit_to_large
    Api::V2::BookResource.paginator :offset

    get :index, {page: {offset: 50, limit: 1000}}
    assert_response :bad_request
    assert_match /Limit exceeds maximum page size of 20./, json_response['errors'][0]['detail']
  end

  def test_books_offset_pagination_bad_param_value_limit_too_small
    Api::V2::BookResource.paginator :offset

    get :index, {page: {offset: 50, limit: -1}}
    assert_response :bad_request
    assert_match /-1 is not a valid value for limit page parameter./, json_response['errors'][0]['detail']
  end

  def test_books_offset_pagination_bad_param_offset_less_than_zero
    Api::V2::BookResource.paginator :offset

    get :index, {page: {offset: -1, limit: 20}}
    assert_response :bad_request
    assert_match /-1 is not a valid value for offset page parameter./, json_response['errors'][0]['detail']
  end

  def test_books_offset_pagination_invalid_page_format
    Api::V2::BookResource.paginator :offset

    get :index, {page: 50}
    assert_response :bad_request
    assert_match /Invalid Page Object./, json_response['errors'][0]['detail']
  end

  def test_books_paged_pagination_no_params
    Api::V2::BookResource.paginator :paged

    get :index
    assert_response :success
    assert_equal 10, json_response['data'].size
    assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
  end

  def test_books_paged_pagination_no_page
    Api::V2::BookResource.paginator :paged

    get :index, {page: {size: 12}}
    assert_response :success
    assert_equal 12, json_response['data'].size
    assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
  end

  def test_books_paged_pagination
    Api::V2::BookResource.paginator :paged

    get :index, {page: {number: 3, size: 12}}
    assert_response :success
    assert_equal 12, json_response['data'].size
    assert_equal 'Book 24', json_response['data'][0]['attributes']['title']
  end

  def test_books_paged_pagination_bad_page_param
    Api::V2::BookResource.paginator :paged

    get :index, {page: {number_bad: 50, size: 12}}
    assert_response :bad_request
    assert_match /number_bad is not an allowed page parameter./, json_response['errors'][0]['detail']
  end

  def test_books_paged_pagination_bad_param_value_limit_to_large
    Api::V2::BookResource.paginator :paged

    get :index, {page: {number: 50, size: 1000}}
    assert_response :bad_request
    assert_match /size exceeds maximum page size of 20./, json_response['errors'][0]['detail']
  end

  def test_books_paged_pagination_bad_param_value_limit_too_small
    Api::V2::BookResource.paginator :paged

    get :index, {page: {number: 50, size: -1}}
    assert_response :bad_request
    assert_match /-1 is not a valid value for size page parameter./, json_response['errors'][0]['detail']
  end

  def test_books_paged_pagination_invalid_page_format_incorrect
    Api::V2::BookResource.paginator :paged

    get :index, {page: 'qwerty'}
    assert_response :bad_request
    assert_match /0 is not a valid value for number page parameter./, json_response['errors'][0]['detail']
  end

  def test_books_paged_pagination_invalid_page_format_interpret_int
    Api::V2::BookResource.paginator :paged

    get :index, {page: 3}
    assert_response :success
    assert_equal 10, json_response['data'].size
    assert_equal 'Book 20', json_response['data'][0]['attributes']['title']
  end

  def test_books_included_paged
    Api::V2::BookResource.paginator :offset

    count_queries do
      get :index, {filter: {id: '0'}, include: 'book-comments'}
    end
    assert_response :success
    assert_equal 1, json_response['data'].size
    assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
    assert_query_count(3)
  end

  def test_books_banned_non_book_admin
    $test_user = Person.find(1)
    Api::V2::BookResource.paginator :offset
    count_queries do
      JSONAPI.configuration.top_level_meta_include_record_count = true
      get :index, {page: {offset: 50, limit: 12}}
      JSONAPI.configuration.top_level_meta_include_record_count = false
    end
    assert_response :success
    assert_equal 12, json_response['data'].size
    assert_equal 'Book 50', json_response['data'][0]['attributes']['title']
    assert_equal 901, json_response['meta']['record-count']
    assert_query_count(2)
  end

  def test_books_banned_non_book_admin_includes_switched
    $test_user = Person.find(1)
    Api::V2::BookResource.paginator :offset
    count_queries do
      JSONAPI.configuration.top_level_meta_include_record_count = true
      get :index, {page: {offset: 0, limit: 12}, include: 'book-comments'}
      JSONAPI.configuration.top_level_meta_include_record_count = false
    end

    assert_response :success
    assert_equal 12, json_response['data'].size
    assert_equal 130, json_response['included'].size
    assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
    assert_equal 26, json_response['data'][0]['relationships']['book-comments']['data'].size
    assert_equal 'book-comments', json_response['included'][0]['type']
    assert_equal 901, json_response['meta']['record-count']
    assert_query_count(3)
  end

  def test_books_banned_non_book_admin_includes_nested_includes
    $test_user = Person.find(1)
    Api::V2::BookResource.paginator :offset
    count_queries do
      JSONAPI.configuration.top_level_meta_include_record_count = true
      get :index, {page: {offset: 0, limit: 12}, include: 'book-comments.author'}
      JSONAPI.configuration.top_level_meta_include_record_count = false
    end
    assert_response :success
    assert_equal 12, json_response['data'].size
    assert_equal 131, json_response['included'].size
    assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
    assert_equal 901, json_response['meta']['record-count']
    assert_query_count(4)
  end

  def test_books_banned_admin
    $test_user = Person.find(5)
    Api::V2::BookResource.paginator :offset
    query_count = count_queries do
      JSONAPI.configuration.top_level_meta_include_record_count = true
      get :index, {page: {offset: 50, limit: 12}, filter: {banned: 'true'}}
      JSONAPI.configuration.top_level_meta_include_record_count = false
    end
    assert_response :success
    assert_equal 12, json_response['data'].size
    assert_equal 'Book 651', json_response['data'][0]['attributes']['title']
    assert_equal 99, json_response['meta']['record-count']
    assert_query_count(2)
  end

  def test_books_not_banned_admin
    $test_user = Person.find(5)
    Api::V2::BookResource.paginator :offset
    count_queries do
      JSONAPI.configuration.top_level_meta_include_record_count = true
      get :index, {page: {offset: 50, limit: 12}, filter: {banned: 'false'}}
      JSONAPI.configuration.top_level_meta_include_record_count = false
    end
    assert_response :success
    assert_equal 12, json_response['data'].size
    assert_equal 'Book 50', json_response['data'][0]['attributes']['title']
    assert_equal 901, json_response['meta']['record-count']
    assert_query_count(2)
  end

  def test_books_banned_non_book_admin_overlapped
    $test_user = Person.find(1)
    Api::V2::BookResource.paginator :offset
    count_queries do
      JSONAPI.configuration.top_level_meta_include_record_count = true
      get :index, {page: {offset: 590, limit: 20}}
      JSONAPI.configuration.top_level_meta_include_record_count = false
    end
    assert_response :success
    assert_equal 20, json_response['data'].size
    assert_equal 'Book 590', json_response['data'][0]['attributes']['title']
    assert_equal 901, json_response['meta']['record-count']
    assert_query_count(2)
  end

  def test_books_included_exclude_unapproved
    $test_user = Person.find(1)
    Api::V2::BookResource.paginator :none

    count_queries do
      get :index, {filter: {id: '0,1,2,3,4'}, include: 'book-comments'}
    end
    assert_response :success
    assert_equal 5, json_response['data'].size
    assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
    assert_equal 130, json_response['included'].size
    assert_equal 26, json_response['data'][0]['relationships']['book-comments']['data'].size
    assert_query_count(2)
  end

  def test_books_included_all_comments_for_admin
    $test_user = Person.find(5)
    Api::V2::BookResource.paginator :none

    get :index, {filter: {id: '0,1,2,3,4'}, include: 'book-comments'}
    assert_response :success
    assert_equal 5, json_response['data'].size
    assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
    assert_equal 255, json_response['included'].size
    assert_equal 51, json_response['data'][0]['relationships']['book-comments']['data'].size
  end

  def test_books_filter_by_book_comment_id_limited_user
    $test_user = Person.find(1)
    get :index, {filter: {book_comments: '0,52' }}
    assert_response :success
    assert_equal 1, json_response['data'].size
  end

  def test_books_filter_by_book_comment_id_admin_user
    $test_user = Person.find(5)
    get :index, {filter: {book_comments: '0,52' }}
    assert_response :success
    assert_equal 2, json_response['data'].size
  end
end

class Api::V2::BookCommentsControllerTest < ActionController::TestCase
  def setup
    JSONAPI.configuration.json_key_format = :dasherized_key
    Api::V2::BookCommentResource.paginator :none
    $test_user = Person.find(1)
  end

  def test_book_comments_all_for_admin
    $test_user = Person.find(5)
    count_queries do
      get :index
    end
    assert_response :success
    assert_equal 255, json_response['data'].size
    assert_query_count(1)
  end

  def test_book_comments_unapproved_context_based
    $test_user = Person.find(5)
    count_queries do
      get :index, {filter: {approved: 'false'}}
    end
    assert_response :success
    assert_equal 125, json_response['data'].size
    assert_query_count(1)
  end

  def test_book_comments_exclude_unapproved_context_based
    $test_user = Person.find(1)
    count_queries do
      get :index
    end
    assert_response :success
    assert_equal 130, json_response['data'].size
    assert_query_count(1)
  end
end

class Api::V4::BooksControllerTest < ActionController::TestCase
  def setup
    JSONAPI.configuration.json_key_format = :camelized_key
  end

  def test_books_offset_pagination_meta
    JSONAPI.configuration.operations_processor = :counting_active_record
    Api::V4::BookResource.paginator :offset
    get :index, {page: {offset: 50, limit: 12}}
    assert_response :success
    assert_equal 12, json_response['data'].size
    assert_equal 'Book 50', json_response['data'][0]['attributes']['title']
    assert_equal 901, json_response['meta']['totalRecords']
    JSONAPI.configuration.operations_processor = :active_record
  end

  def test_books_operation_links
    JSONAPI.configuration.operations_processor = :counting_active_record
    Api::V4::BookResource.paginator :offset
    get :index, {page: {offset: 50, limit: 12}}
    assert_response :success
    assert_equal 12, json_response['data'].size
    assert_equal 'Book 50', json_response['data'][0]['attributes']['title']
    assert_equal 5, json_response['links'].size
    assert_equal 'https://test_corp.com', json_response['links']['spec']
    JSONAPI.configuration.operations_processor = :active_record
  end
end

class CategoriesControllerTest < ActionController::TestCase
  def test_index_default_filter
    get :index
    assert_response :success
    assert json_response['data'].is_a?(Array)
    assert_equal 3, json_response['data'].size
  end

  def test_index_default_filter_override
    get :index, { filter: { status: 'inactive' } }
    assert_response :success
    assert json_response['data'].is_a?(Array)
    assert_equal 4, json_response['data'].size
  end
end

class Api::V1::PlanetsControllerTest < ActionController::TestCase
  def test_save_model_callbacks
    set_content_type_header!
    post :create,
         {
           data: {
             type: 'planets',
             attributes: {
               name: 'Zeus',
               description: 'The largest planet in the solar system. Discovered in 2015.'
             }
           }
         }

    assert_response :created
    assert json_response['data'].is_a?(Hash)
    assert_equal 'Zeus', json_response['data']['attributes']['name']
  end

  def test_save_model_callbacks_fail
    set_content_type_header!
    post :create,
         {
           data: {
             type: 'planets',
             attributes: {
               name: 'Pluto',
               description: 'Yes, it is a planet.'
             }
           }
         }

    assert_response :unprocessable_entity
    assert_match /Save failed or was cancelled/, json_response['errors'][0]['detail']
  end
end

class Api::V1::MoonsControllerTest < ActionController::TestCase
   def test_get_related_resource
      get :get_related_resource, {crater_id: 'S56D', relationship: 'moon', source: "api/v1/craters"}
      assert_response :success
      assert_hash_equals json_response,
                              {
                                data: {
                                  id: "1",
                                  type: "moons",
                                  links: {self: "http://test.host/moons/1"},
                                  attributes: {name: "Titan", description: "Best known of the Saturn moons."},
                                  relationships: {
                                    planet: {links: {self: "http://test.host/moons/1/relationships/planet", related: "http://test.host/moons/1/planet"}},
                                    craters: {links: {self: "http://test.host/moons/1/relationships/craters", related: "http://test.host/moons/1/craters"}}}
                                  }
                                }

   end
end

class Api::V1::CratersControllerTest < ActionController::TestCase
  def test_show_single
    get :show, {id: 'S56D'}
    assert_response :success
    assert json_response['data'].is_a?(Hash)
    assert_equal 'S56D', json_response['data']['attributes']['code']
    assert_equal 'Very large crater', json_response['data']['attributes']['description']
    assert_nil json_response['included']
  end

  def test_get_related_resources
    get :get_related_resources, {moon_id: '1', relationship: 'craters', source: "api/v1/moons"}
    assert_response :success
    assert_hash_equals json_response,
                      {
                        data: [
                          {id:"A4D3",
                           type:"craters",
                           links:{self: "http://test.host/craters/A4D3"},
                           attributes:{code: "A4D3", description: "Small crater"},
                           relationships:{moon: {links: {self: "http://test.host/craters/A4D3/relationships/moon", related: "http://test.host/craters/A4D3/moon"}}}
                          },
                          {id: "S56D",
                           type: "craters",
                           links:{self: "http://test.host/craters/S56D"},
                           attributes:{code: "S56D", description: "Very large crater"},
                           relationships:{moon: {links: {self: "http://test.host/craters/S56D/relationships/moon", related: "http://test.host/craters/S56D/moon"}}}
                          }
                        ]
                      }
  end

  def test_show_relationship
    get :show_relationship, {crater_id: 'S56D', relationship: 'moon'}

    assert_response :success
    assert_equal "moons", json_response['data']['type']
    assert_equal "1", json_response['data']['id']
  end
end