require 'test_helper'

module Spyke
  class OrmTest < Minitest::Test
    def test_find
      stub_request(:get, 'http://sushi.com/recipes/1').to_return_json(result: { id: 1, title: 'Sushi' })
      stub_request(:get, 'http://sushi.com/users/1').to_return_json(result: { id: 1, name: 'Bob' })

      recipe = Recipe.find(1)
      user = User.find(1)

      assert_equal 1, recipe.id
      assert_equal 'Sushi', recipe.title
      assert_equal 'Bob', user.name
    end

    def test_find_with_block
      stub_request(:get, 'http://sushi.com/users').to_return_json(result: [{ id: 1, name: 'Bob' }, id: 2, name: 'Alice'])

      user = User.find do |u|
        u.name == 'Bob'
      end

      assert_equal 'Bob', user.name
    end

    def test_reload
      stub_request(:get, 'http://sushi.com/recipes/1').to_return_json(result: { id: 1, title: 'Sushi' })

      recipe = Recipe.find(1)
      assert_equal 'Sushi', recipe.title

      stub_request(:get, 'http://sushi.com/recipes/1').to_return_json(result: { id: 1, title: 'Sashimi' })
      recipe.reload
      assert_equal 'Sashimi', recipe.title
    end

    def test_404
      stub_request(:get, 'http://sushi.com/recipes/1').to_return(status: 404, body: { message: 'Not found' }.to_json)

      assert_raises(ResourceNotFound) { Recipe.find(1) }
      assert_raises(ResourceNotFound) { Recipe.find(nil) }
      assert_raises(ResourceNotFound) { Recipe.find('') }
    end

    def test_save_new_record
      endpoint = stub_request(:post, 'http://sushi.com/recipes').with(body: { recipe: { title: 'Sushi' } }).to_return_json(result: { id: 1, title: 'Sushi (created)' })

      recipe = Recipe.new(title: 'Sushi')
      recipe.save

      assert_equal 'Sushi (created)', recipe.title
      assert_requested endpoint
    end

    def test_save_persisted_record
      endpoint = stub_request(:put, 'http://sushi.com/recipes/1').with(body: { recipe: { title: 'Sushi' } }).to_return_json(result: { id: 1, title: 'Sushi (saved)' })

      recipe = Recipe.new(id: 1, title: 'Sashimi')
      recipe.title = 'Sushi'
      recipe.save

      assert_equal 'Sushi (saved)', recipe.title
      assert_requested endpoint
    end

    def test_save_hash_attribute
      endpoint = stub_request(:post, 'http://sushi.com/recipes').with(body: { recipe: { meta: { foo: 'bar' } } })

      Recipe.create(meta: { foo: 'bar' })

      assert_requested endpoint
    end

    def test_save_hash_attribute_with_strong_params
      endpoint = stub_request(:post, 'http://sushi.com/recipes').with(body: { recipe: { meta: { foo: 'bar' } } })

      Recipe.create(strong_params(meta: { foo: 'bar' }).permit!)

      assert_requested endpoint
    end

    def test_update_attributes
      endpoint = stub_request(:put, 'http://sushi.com/recipes/1').with(body: { recipe: { title: 'Sushi' } }).to_return_json(result: { id: 1, title: 'Sushi (saved)' })

      recipe = Recipe.new(id: 1, title: 'Sashimi')
      recipe.update_attributes(title: 'Sushi')

      assert_equal 'Sushi (saved)', recipe.title
      assert_requested endpoint
    end

    def test_update
      endpoint = stub_request(:put, 'http://sushi.com/recipes/1').with(body: { recipe: { title: 'Sushi' } }).to_return_json(result: { id: 1, title: 'Sushi (saved)' })

      recipe = Recipe.new(id: 1, title: 'Sashimi')
      recipe.update(title: 'Sushi')

      assert_equal 'Sushi (saved)', recipe.title
      assert_requested endpoint
    end

    def test_create
      endpoint = stub_request(:post, 'http://sushi.com/recipes').with(body: { recipe: { title: 'Sushi' } }).to_return_json(result: { id: 1, title: 'Sushi' })

      recipe = Recipe.create(title: 'Sushi')

      assert_equal 'Sushi', recipe.title
      assert_requested endpoint
    end

    def test_create_with_server_returning_validation_errors
      endpoint = stub_request(:put, 'http://sushi.com/recipes/1').to_return_json(id: 'write_error:400', errors: { title: [{ error: 'too_short', count: 4 }], groups: [{ error: 'blank' }] })

      recipe = Recipe.create(id: 1, title: 'sus')

      assert_equal 'sus', recipe.title
      assert_equal ['Title is too short (minimum is 4 characters)', "Groups can't be blank"], recipe.errors.full_messages
      assert_requested endpoint
    end

    def test_create_with_server_returning_string_validation_errors
      endpoint = stub_request(:put, 'http://sushi.com/recipes/1').to_return_json(id: 'write_error:400', errors: { title: ["is too short"], groups: ["can't be blank"] })

      recipe = Recipe.create(id: 1, title: 'sus')

      assert_equal 'sus', recipe.title
      assert_equal ['Title is too short', "Groups can't be blank"], recipe.errors.full_messages
      assert_requested endpoint
    end

    def test_find_using_custom_uri_template
      endpoint = stub_request(:get, 'http://sushi.com/images/photos/1').to_return_json(result: { id: 1 })
      Photo.find(1)
      assert_requested endpoint
    end

    def test_create_using_custom_uri_template
      endpoint = stub_request(:post, 'http://sushi.com/images/photos')
      Photo.create
      assert_requested endpoint
    end

    def test_create_using_nested_custom_uri_template
      endpoint = stub_request(:post, 'http://sushi.com/recipes/1/ingredients')
      Ingredient.new(recipe_id: 1).save
      assert_requested endpoint
    end

    def test_create_using_custom_method
      endpoint = stub_request(:put, 'http://sushi.com/images')
      Image.create
      assert_requested endpoint
    end

    def test_inheritance_not_overwriting_custom_uri
      endpoint = stub_request(:put, 'http://sushi.com/recipes/1/image')
      RecipeImage.where(recipe_id: 1).create
      assert_requested endpoint
    end

    def test_to_params_without_root
      assert_equal({ 'url' => 'bob.jpg' }, RecipeImage.new(url: 'bob.jpg').to_params)
    end

    def test_to_params_with_custom_root
      assert_equal({ 'step_image_root' => { 'url' => 'bob.jpg' } }, StepImage.new(url: 'bob.jpg').to_params)
      assert_equal({ 'foto' => { 'url' => 'bob.jpg' } }, Cookbook::Photo.new(url: 'bob.jpg').to_params)
    end

    def test_destroy
      endpoint = stub_request(:delete, 'http://sushi.com/recipes/1').to_return_json(result: { id: 1, deleted: true })
      recipe = Recipe.new(id: 1)
      recipe.destroy
      assert recipe.deleted
      assert_requested endpoint
    end

    def test_destroy_class_method
      endpoint = stub_request(:delete, 'http://sushi.com/recipes/1')
      Recipe.destroy(1)
      assert_requested endpoint
    end

    def test_scoped_destroy_class_method
      endpoint = stub_request(:delete, 'http://sushi.com/recipes/1/ingredients/2')
      Ingredient.where(recipe_id: 1).destroy(2)
      assert_requested endpoint
    end

    def test_scoped_destroy_class_method_without_param
      endpoint = stub_request(:delete, 'http://sushi.com/recipes/1/image')
      RecipeImage.where(recipe_id: 1).destroy
      assert_requested endpoint
    end

    def test_relative_uris
      previous = Spyke::Base.connection.url_prefix
      Spyke::Base.connection.url_prefix = 'http://sushi.com/api/v2/'

      endpoint = stub_request(:get, 'http://sushi.com/api/v2/recipes')
      Recipe.all.to_a
      assert_requested endpoint

      Spyke::Base.connection.url_prefix = previous
    end

    def test_custom_primary_key_on_collection
      endpoint = stub_request(:get, 'http://sushi.com/users').to_return_json(result: [{ uuid: 1 }])
      user = User.all.first
      assert_requested endpoint
      assert_equal 1, user.id
      assert_equal 1, user.uuid
    end

    def test_custom_primary_key_and_id_are_the_same
      endpoint = stub_request(:get, 'http://sushi.com/users').to_return_json(result: [{ uuid: 1 }])
      user = User.all.first
      assert_requested endpoint
      assert_equal 1, user.id
      assert_equal 1, user.uuid
      assert_equal({ "uuid" => 1 }, user.attributes)
    end

    def test_custom_primary_key_with_response_that_also_has_id_attribute
      endpoint = stub_request(:get, 'http://sushi.com/users').to_return_json(result: [{ uuid: 1, id: 42 }])
      user = User.all.to_a.first
      assert_requested endpoint

      assert_equal 1, user.id
      assert_equal 1, user.uuid
      assert_equal 1, user[:uuid]
      assert_equal 42, user[:id]
    end

    def test_custom_primary_key_used_for_persistence_check
      user = User.new
      refute user.persisted?

      user.uuid = 1
      assert user.persisted?
    end
  end
end
