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

class RequestTest < ActionDispatch::IntegrationTest

  def setup
    JSONAPI.configuration.json_key_format = :underscored_key
    JSONAPI.configuration.route_format = :underscored_route
  end

  def after_teardown
    Api::V2::BookResource.paginator :offset
    JSONAPI.configuration.route_format = :underscored_route
  end

  def test_get
    get '/posts'
    assert_equal 200, status
  end

  def test_get_nested_has_one
    get '/posts/1/author'
    assert_equal 200, status
  end

  def test_get_nested_has_many
    get '/posts/1/comments'
    assert_equal 200, status
  end

  def test_get_nested_has_many_bad_param
    get '/posts/1/comments?association=books'
    assert_equal 200, status
  end

  def test_get_underscored_key
    JSONAPI.configuration.json_key_format = :underscored_key
    get '/iso_currencies'
    assert_equal 200, status
    assert_equal 3, json_response['data'].size
  end

  def test_get_underscored_key_filtered
    JSONAPI.configuration.json_key_format = :underscored_key
    get '/iso_currencies?filter[country_name]=Canada'
    assert_equal 200, status
    assert_equal 1, json_response['data'].size
    assert_equal 'Canada', json_response['data'][0]['country_name']
  end

  def test_get_camelized_key_filtered
    JSONAPI.configuration.json_key_format = :camelized_key
    get '/iso_currencies?filter[countryName]=Canada'
    assert_equal 200, status
    assert_equal 1, json_response['data'].size
    assert_equal 'Canada', json_response['data'][0]['countryName']
  end

  def test_get_camelized_route_and_key_filtered
    JSONAPI.configuration.json_key_format = :camelized_key
    get '/api/v4/isoCurrencies?filter[countryName]=Canada'
    assert_equal 200, status
    assert_equal 1, json_response['data'].size
    assert_equal 'Canada', json_response['data'][0]['countryName']
  end

  def test_get_camelized_route_and_links
    JSONAPI.configuration.json_key_format = :camelized_key
    JSONAPI.configuration.route_format = :camelized_route
    get '/api/v4/expenseEntries/1/links/isoCurrency'
    assert_equal 200, status
    assert_hash_equals({'links' => {
                         'self' => 'http://www.example.com/api/v4/expenseEntries/1/links/isoCurrency',
                         'related' => 'http://www.example.com/api/v4/expenseEntries/1/isoCurrency'
                       },
                       'data' => {
                          'type' => 'isoCurrencies',
                          'id' => 'USD'
                         }
                       }, json_response)
  end

  def test_put_single_without_content_type
    put '/posts/3',
        {
          'data' => {
            'linkage' => {
              'type' => 'posts',
              'id' => '3',
            },
            'title' => 'A great new Post',
            'links' => {
              'tags' => [
                {type: 'tags', id: 3},
                {type: 'tags', id: 4}
              ]
            }
          }
        }.to_json, "CONTENT_TYPE" => "application/json"

    assert_equal 415, status
  end

  def test_put_single
    put '/posts/3',
        {
          'data' => {
            'type' => 'posts',
            'id' => '3',
            'title' => 'A great new Post',
            'links' => {
              'tags' => {
                'linkage' => [
                  {type: 'tags', id: 3},
                  {type: 'tags', id: 4}
                ]
              }
            }
          }
        }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_equal 200, status
  end

  def test_post_single_without_content_type
    post '/posts',
      {
        'posts' => {
          'title' => 'A great new Post',
          'links' => {
            'tags' => {
              'linkage' => [
                  {type: 'tags', id: 3},
                  {type: 'tags', id: 4}
                ]
            }
          }
        }
      }.to_json, "CONTENT_TYPE" => "application/json"

    assert_equal 415, status
  end

  def test_post_single
    post '/posts',
      {
        'data' => {
          'type' => 'posts',
          'title' => 'A great new Post',
          'body' => 'JSONAPIResources is the greatest thing since unsliced bread.',
          'links' => {
            'author' => {'linkage' => {type: 'people', id: '3'}}
          }
        }
      }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_equal 201, status
  end

  def test_post_single_missing_data_contents
    post '/posts',
         {
           'data' => {
           }
         }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_equal 400, status
  end

  def test_post_single_minimal_valid
    post '/comments',
         {
           'data' => {
             'type' => 'comments'
           }
         }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_equal 201, status
    assert_nil json_response['data']['body']
    assert_nil json_response['data']['links']['post']['linkage']
    assert_nil json_response['data']['links']['author']['linkage']
  end

  def test_post_single_minimal_invalid
    post '/posts',
      {
        'data' => {
          'type' => 'posts'
        }
      }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_equal 422, status
  end

  def test_update_association_without_content_type
    ruby = Section.find_by(name: 'ruby')
    patch '/posts/3/links/section', { 'data' => {type: 'sections', id: ruby.id.to_s }}.to_json

    assert_equal 415, status
  end

  def test_patch_update_association_has_one
    ruby = Section.find_by(name: 'ruby')
    patch '/posts/3/links/section', { 'data' => {type: 'sections', id: ruby.id.to_s }}.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_equal 204, status
  end

  def test_put_update_association_has_one
    ruby = Section.find_by(name: 'ruby')
    put '/posts/3/links/section', { 'data' => {type: 'sections', id: ruby.id.to_s }}.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_equal 204, status
  end

  def test_patch_update_association_has_many_acts_as_set
    # Comments are acts_as_set=false so PUT/PATCH should respond with 403

    rogue = Comment.find_by(body: 'Rogue Comment Here')
    patch '/posts/5/links/comments', { 'data' => [{type: 'comments', id: rogue.id.to_s }]}.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_equal 403, status
  end

  def test_post_update_association_has_many
    rogue = Comment.find_by(body: 'Rogue Comment Here')
    post '/posts/5/links/comments', { 'data' => [{type: 'comments', id: rogue.id.to_s }]}.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_equal 204, status
  end

  def test_put_update_association_has_many_acts_as_set
    # Comments are acts_as_set=false so PUT/PATCH should respond with 403. Note: JR currently treats PUT and PATCH as equivalent

    rogue = Comment.find_by(body: 'Rogue Comment Here')
    put '/posts/5/links/comments', { 'data' => [{type: 'comments', id: rogue.id.to_s }]}.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_equal 403, status
  end

  def test_index_content_type
    get '/posts'
    assert_match JSONAPI::MEDIA_TYPE, headers['Content-Type']
  end

  def test_get_content_type
    get '/posts/3'
    assert_match JSONAPI::MEDIA_TYPE, headers['Content-Type']
  end

  def test_put_content_type
    put '/posts/3',
        {
          'data' => {
            'type' => 'posts',
            'id' => '3',
            'title' => 'A great new Post',
            'links' => {
              'tags' => {
                'linkage' => [
                  {type: 'tags', id: 3},
                  {type: 'tags', id: 4}
                ]
              }
            }
          }
        }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_match JSONAPI::MEDIA_TYPE, headers['Content-Type']
  end

  def test_patch_content_type
    patch '/posts/3',
        {
          'data' => {
            'type' => 'posts',
            'id' => '3',
            'title' => 'A great new Post',
            'links' => {
              'tags' => {
                'linkage' => [
                  {type: 'tags', id: 3},
                  {type: 'tags', id: 4}
                ]
              }
            }
          }
        }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_match JSONAPI::MEDIA_TYPE, headers['Content-Type']
  end

  def test_post_correct_content_type
    post '/posts',
      {
       'data' => {
         'type' => 'posts',
         'title' => 'A great new Post',
         'links' => {
           'author' => {'linkage' => {type: 'people', id: '3'}}
         }
       }
     }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_match JSONAPI::MEDIA_TYPE, headers['Content-Type']
  end

  def test_destroy_single
    delete '/posts/7'
    assert_equal 204, status
    assert_nil headers['Content-Type']
  end

  def test_destroy_multiple
    delete '/posts/8,9'
    assert_equal 204, status
  end

  def test_pagination_none
    Api::V2::BookResource.paginator :none
    get '/api/v2/books'
    assert_equal 200, status
    assert_equal 1000, json_response['data'].size
  end

  def test_pagination_offset_style
    Api::V2::BookResource.paginator :offset
    get '/api/v2/books'
    assert_equal 200, status
    assert_equal JSONAPI.configuration.default_page_size, json_response['data'].size
    assert_equal 'Book 0', json_response['data'][0]['title']
  end

  def test_pagination_offset_style_offset
    Api::V2::BookResource.paginator :offset
    get '/api/v2/books?page[offset]=50'
    assert_equal 200, status
    assert_equal JSONAPI.configuration.default_page_size, json_response['data'].size
    assert_equal 'Book 50', json_response['data'][0]['title']
  end

  def test_pagination_offset_style_offset_limit
    Api::V2::BookResource.paginator :offset
    get '/api/v2/books?page[offset]=50&page[limit]=20'
    assert_equal 200, status
    assert_equal 20, json_response['data'].size
    assert_equal 'Book 50', json_response['data'][0]['title']
  end

  def test_pagination_offset_bad_param
    Api::V2::BookResource.paginator :offset
    get '/api/v2/books?page[irishsetter]=50&page[limit]=20'
    assert_equal 400, status
  end

  def test_pagination_related_resources_link
    Api::V2::BookResource.paginator :offset
    get '/api/v2/books?page[limit]=2'
    assert_equal 200, status
    assert_equal 2, json_response['data'].size
    assert_equal 'http://www.example.com/api/v2/books/1/book_comments',
                 json_response['data'][1]['links']['book_comments']['related']
  end

  def test_pagination_related_resources_data
    Api::V2::BookResource.paginator :offset
    Api::V2::BookCommentResource.paginator :offset
    get '/api/v2/books/1/book_comments?page[limit]=10'
    assert_equal 200, status
    assert_equal 10, json_response['data'].size
    assert_equal 'This is comment 9 on book 1.', json_response['data'][9]['body']
  end

  def test_pagination_related_resources_data_includes
    Api::V2::BookResource.paginator :offset
    Api::V2::BookCommentResource.paginator :offset
    get '/api/v2/books/1/book_comments?page[limit]=10&include=author,book'
    assert_equal 200, status
    assert_equal 10, json_response['data'].size
    assert_equal 'This is comment 9 on book 1.', json_response['data'][9]['body']
  end

  def test_flow_self
    get '/posts'
    assert_equal 200, status
    post_1 = json_response['data'][0]

    get post_1['links']['self']
    assert_equal 200, status
    assert_hash_equals post_1, json_response['data']
  end

  def test_flow_link_has_one_self_link
    get '/posts'
    assert_equal 200, status
    post_1 = json_response['data'][0]

    get post_1['links']['author']['self']
    assert_equal 200, status
    assert_hash_equals(json_response, {
                                      'links' => {
                                        'self' => 'http://www.example.com/posts/1/links/author',
                                        'related' => 'http://www.example.com/posts/1/author'
                                      },
                                      'data' => {type: 'people', id: '1'}
                                    })
  end

  def test_flow_link_has_many_self_link
    get '/posts'
    assert_equal 200, status
    post_1 = json_response['data'][0]

    get post_1['links']['tags']['self']
    assert_equal 200, status
    assert_hash_equals(json_response,
                       {
                         'links' => {
                           'self' => 'http://www.example.com/posts/1/links/tags',
                           'related' => 'http://www.example.com/posts/1/tags'
                          },
                          'data' => [
                            {type: 'tags', id: '1'},
                            {type: 'tags', id: '2'},
                            {type: 'tags', id: '3'}
                          ]
                       })
  end

  def test_flow_link_has_many_self_link_put
    get '/posts'
    assert_equal 200, status
    post_1 = json_response['data'][4]

    post post_1['links']['tags']['self'],
         {'data' => [{'type' => 'tags', 'id' => '10'}]}.to_json,
         "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_equal 204, status

    get post_1['links']['tags']['self']
    assert_equal 200, status
    assert_hash_equals(json_response,
                       {
                         'links' => {
                           'self' => 'http://www.example.com/posts/5/links/tags',
                           'related' => 'http://www.example.com/posts/5/tags'
                         },
                         'data' => [
                           {type: 'tags', id: '10'}
                         ]
                       })
  end

  def test_flow_self_formatted_route_1
    JSONAPI.configuration.route_format = :dasherized_route
    JSONAPI.configuration.json_key_format = :dasherized_key
    get '/api/v6/purchase-orders'
    assert_equal 200, status
    po_1 = json_response['data'][0]
    assert_equal 'purchase-orders', json_response['data'][0]['type']

    get po_1['links']['self']
    assert_equal 200, status
    assert_hash_equals po_1, json_response['data']
  end

  def test_flow_self_formatted_route_2
    JSONAPI.configuration.route_format = :underscored_route
    JSONAPI.configuration.json_key_format = :dasherized_key
    get '/api/v7/purchase_orders'
    assert_equal 200, status
    assert_equal 'purchase-orders', json_response['data'][0]['type']

    po_1 = json_response['data'][0]

    get po_1['links']['self']
    assert_equal 200, status
    assert_hash_equals po_1, json_response['data']
  end

  def test_flow_self_formatted_route_3
    JSONAPI.configuration.route_format = :underscored_route
    JSONAPI.configuration.json_key_format = :underscored_key
    get '/api/v7/purchase_orders'
    assert_equal 200, status
    assert_equal 'purchase_orders', json_response['data'][0]['type']

    po_1 = json_response['data'][0]

    get po_1['links']['self']
    assert_equal 200, status
    assert_hash_equals po_1, json_response['data']
  end

  def test_post_formatted_keys
    JSONAPI.configuration.route_format = :dasherized_route
    JSONAPI.configuration.json_key_format = :dasherized_key
    post '/api/v6/purchase-orders',
         {
           'data' => {
             'delivery-name' => 'ASDFG Corp',
             'type' => 'purchase-orders'
           }
         }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_equal 201, status
  end

  def test_post_formatted_keys_different_route_key_1
    JSONAPI.configuration.route_format = :dasherized_route
    JSONAPI.configuration.json_key_format = :underscored_key
    post '/api/v6/purchase-orders',
         {
           'data' => {
             'delivery_name' => 'ASDFG Corp',
             'type' => 'purchase_orders'
           }
         }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_equal 201, status
  end

  def test_post_formatted_keys_different_route_key_2
    JSONAPI.configuration.route_format = :underscored_route
    JSONAPI.configuration.json_key_format = :dasherized_key
    post '/api/v7/purchase_orders',
         {
           'data' => {
             'delivery-name' => 'ASDFG Corp',
             'type' => 'purchase-orders'
           }
         }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_equal 201, status
  end

  def test_post_formatted_keys_wrong_format
    JSONAPI.configuration.route_format = :dasherized_route
    JSONAPI.configuration.json_key_format = :dasherized_key
    post '/api/v6/purchase-orders',
         {
           'data' => {
             'delivery_name' => 'ASDFG Corp',
             'type' => 'purchase-orders'
           }
         }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_equal 400, status
  end

  def test_patch_formatted_dasherized
    JSONAPI.configuration.route_format = :dasherized_route
    JSONAPI.configuration.json_key_format = :dasherized_key
    patch '/api/v6/purchase-orders/1',
         {
           'data' => {
             'id' => '1',
             'delivery-name' => 'ASDFG Corp',
             'type' => 'purchase-orders'
           }
         }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_equal 200, status
  end

  def test_patch_formatted_dasherized_links
    JSONAPI.configuration.route_format = :dasherized_route
    JSONAPI.configuration.json_key_format = :dasherized_key
    patch '/api/v6/line-items/1',
          {
            'data' => {
              'id' => '1',
              'type' => 'line-items',
              'item-cost' => '23.57',
              'links' => {
                'purchase-order' => {
                  'linkage' => {'type' => 'purchase-orders', 'id' => '2'}
                }
              }
            }
          }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_equal 200, status
  end

  def test_patch_formatted_dasherized_replace_has_many
    JSONAPI.configuration.route_format = :dasherized_route
    JSONAPI.configuration.json_key_format = :dasherized_key
    patch '/api/v6/purchase-orders/2?include=line-items,order-flags',
          {
            'data' => {
              'id' => '2',
              'type' => 'purchase-orders',
              'links' => {
                'line-items' => {
                  'linkage' => [
                    {'type' => 'line-items', 'id' => '3'},
                    {'type' => 'line-items', 'id' => '4'}
                  ]
                },
                'order-flags' => {
                  'linkage' => [
                    {'type' => 'order-flags', 'id' => '1'},
                    {'type' => 'order-flags', 'id' => '2'}
                  ]
                }
              }
            }
          }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE

    assert_equal 200, status
  end
end