require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
require 'hardmock'
class HardmockTest < Test::Unit::TestCase

  def setup
  end

  def teardown
  end

  #
  # HELPERS
  #

  def assert_mock_exists(name)
    assert_not_nil @all_mocks, "@all_mocks not here yet"
    mo = @all_mocks[name]
    assert_not_nil mo, "Mock '#{name}' not in @all_mocks"
    assert_kind_of Mock, mo, "Wrong type of object, wanted a Mock"
    assert_equal name.to_s, mo._name, "Mock '#{name}' had wrong name"
    ivar = self.instance_variable_get("@#{name}")
    assert_not_nil ivar, "Mock '#{name}' not set as ivar"
    assert_same mo, ivar, "Mock '#{name}' ivar not same as instance in @all_mocks"
    assert_same @main_mock_control, mo._control, "Mock '#{name}' doesn't share the main mock control"
  end
  
  #
  # TESTS
  # 

  def test_create_mock_and_create_mocks
    assert_nil @main_mock_control, "@main_mock_control not expected yet"

    h = create_mock :donkey
    assert_equal [ :donkey ], h.keys
    assert_not_nil @main_mock_control, "@main_mock_control should be here"

    assert_mock_exists :donkey
    assert_same @donkey, h[:donkey]

    assert_equal [ :donkey ], @all_mocks.keys, "Wrong keyset for @all_mocks"

    h2 = create_mocks :cat, 'dog' # symbol/string indifference at this level
    assert_equal [:cat,:dog].to_set, h2.keys.to_set, "Wrong keyset for second hash"
    assert_equal [:cat,:dog,:donkey].to_set, @all_mocks.keys.to_set, "@all_mocks wrong"   

    assert_mock_exists :cat
    assert_same @cat, h2[:cat]
    assert_mock_exists :dog
    assert_same @dog, h2[:dog]

    assert_mock_exists :donkey
  end

  def test_expect
    assert_nil @order, "Should be no @order yet"
    create_mock :order
    assert_not_nil @order, "@order should be built"

    # Setup an expectation
    @order.expects.update_stuff :key1 => 'val1', :key2 => 'val2'

    # Use the mock
    @order.update_stuff :key1 => 'val1', :key2 => 'val2'

    # Verify
    verify_mocks

    # See that it's ok to do it again
    verify_mocks
  end

  def test_typical_multi_mock_use
    create_mocks :order_builder, :order, :customer

    @order_builder.expects.create_new_order.returns @order
    @customer.expects.account_number.returns(1234)
    @order.expects.account_no = 1234
    @order.expects.save!

    # Run "the code"
    o = @order_builder.create_new_order
    o.account_no = @customer.account_number
    o.save!

    verify_mocks
  end

  def test_typical_multi_mock_use_out_of_order
    create_mocks :order_builder, :order, :customer

    @order_builder.expects.create_new_order.returns @order
    @customer.expects.account_number.returns(1234)
    @order.expects.account_no = 1234
    @order.expects.save!

    # Run "the code"
    o = @order_builder.create_new_order
    err = assert_raise ExpectationError do
      o.save!
    end
    assert_match(/wrong object/i, err.message) 
    assert_match(/order.save!/i, err.message) 
    assert_match(/customer.account_number/i, err.message) 

    assert_error VerifyError, /unmet expectations/i do
      verify_mocks
    end

    # Appease the verifier
    @order.account_no = 1234
    @order.save!
  end

  class UserPresenter
    def initialize(args)
      view = args[:view]
      model = args[:model]
      model.when :data_changes do
        view.user_name = model.user_name
      end
      view.when :user_edited do
        model.user_name = view.user_name
      end
    end
  end

  def test_mvp_usage_pattern
    mox = create_mocks :model, :view

    data_change = @model.expects.when(:data_changes) { |evt,block| block }
    user_edit = @view.expects.when(:user_edited) { |evt,block| block }
    
    UserPresenter.new mox

    # Expect user name transfer from model to view
    @model.expects.user_name.returns 'Da Croz'
    @view.expects.user_name = 'Da Croz'
    # Trigger data change event in model
    data_change.block_value.call

    # Expect user name transfer from view to model
    @view.expects.user_name.returns '6:8'
    @model.expects.user_name = '6:8'
    # Trigger edit event in view
    user_edit.block_value.call

    verify_mocks 
  end

  def test_verify_mocks_repeated_anger
    mox = create_mocks :model, :view
    data_change = @model.expects.when(:data_changes) { |evt,block| block }
    user_edit = @view.expects.when(:user_edited) { |evt,block| block }
    UserPresenter.new mox

    # Expect user name transfer from model to view
    @model.expects.user_name.returns 'Da Croz'
    @view.expects.user_name = 'Da Croz'

    assert_error ExpectationError, /model.monkey_wrench/i do
      @model.monkey_wrench
    end

    # This should raise because of unmet expectations
    assert_error VerifyError, /unmet expectations/i, /user_name/i do
      verify_mocks
    end

    # See that the non-forced verification remains quiet
    assert_nothing_raised VerifyError do
      verify_mocks(false)
    end
    
    # Finish meeting expectations and see good verification behavior
    @view.user_name = "Da Croz"    
    verify_mocks
    
    @model.expects.never_gonna_happen
    
    assert_error VerifyError, /unmet expectations/i, /never_gonna_happen/i do
      verify_mocks
    end

    # Appease the verifier
    @model.never_gonna_happen
  end

  class UserPresenterBroken
    def initialize(args)
      view = args[:view]
      model = args[:model]
      model.when :data_changes do
        view.user_name = model.user_name
      end
      # no view stuff, will break appropriately
    end
  end

  def test_mvp_usage_with_failures_in_constructor
    mox = create_mocks :model, :view

    data_change = @model.expects.when(:data_changes) { |evt,block| block }
    user_edit = @view.expects.when(:user_edited) { |evt,block| block }
    
    UserPresenterBroken.new mox

    err = assert_raise VerifyError do
      verify_mocks
    end
    assert_match(/unmet expectations/i, err.message) 
    assert_match(/view.when\(:user_edited\)/i, err.message) 

    assert_error VerifyError, /unmet expectations/i do
      verify_mocks
    end 

    # Appease the verifier
    @view.when(:user_edited)

  end

  def test_mvp_usage_pattern_with_convenience_trap
    mox = create_mocks :model, :view

    data_change = @model.trap.when(:data_changes) 
    user_edit = @view.trap.when(:user_edited) 
    
    UserPresenter.new mox

    # Expect user name transfer from model to view
    @model.expects.user_name.returns 'Da Croz'
    @view.expects.user_name = 'Da Croz'
    # Trigger data change event in model
    data_change.trigger

    # Expect user name transfer from view to model
    @view.expects.user_name.returns '6:8'
    @model.expects.user_name = '6:8'
    # Trigger edit event in view
    user_edit.trigger

    verify_mocks 
  end

  class Grinder
    def initialize(objects)
      @chute = objects[:chute]
      @bucket = objects[:bucket]
      @blade = objects[:blade]
    end

    def grind(slot)
      @chute.each_bean(slot) do |bean|
        @bucket << @blade.chop(bean)
      end
    end
  end

  def test_internal_iteration_usage
    grinder = Grinder.new create_mocks(:blade, :chute, :bucket)
    
    # Style 1: assertions on method args is done explicitly in block
    @chute.expects.each_bean { |slot,block| 
      assert_equal :side_slot, slot, "Wrong slot"
      block.call :bean1
      block.call :bean2
    }

    @blade.expects.chop(:bean1).returns(:grounds1)
    @bucket.expects('<<', :grounds1)

    @blade.expects.chop(:bean2).returns(:grounds2)
    @bucket.expects('<<', :grounds2)

    # Run "the code"
    grinder.grind(:side_slot)

    verify_mocks

    # Style 2: assertions on method arguments done implicitly in the expectation code
    @chute.expects.each_bean(:main_slot) { |slot,block| 
      block.call :bean3
    }
    @blade.expects.chop(:bean3).returns(:grounds3)
    @bucket.expects('<<', :grounds3)
    grinder.grind :main_slot
    verify_mocks
  end

  def test_internal_iteration_using_yield
    grinder = Grinder.new create_mocks(:blade, :chute, :bucket)
    
    @chute.expects.each_bean(:side_slot).yields :bean1, :bean2

    @blade.expects.chop(:bean1).returns(:grounds1)
    @bucket.expects('<<', :grounds1)

    @blade.expects.chop(:bean2).returns(:grounds2)
    @bucket.expects('<<', :grounds2)

    grinder.grind :side_slot

    verify_mocks
  end

  class HurtLocker
    attr_reader :caught
    def initialize(opts)
      @locker = opts[:locker]
      @store = opts[:store]
    end

    def do_the_thing(area,data)
      @locker.with_lock(area) do
        @store.eat(data)
      end
    rescue => oops
      @caught = oops
    end
  end

  def test_internal_locking_scenario
    hurt = HurtLocker.new create_mocks(:locker, :store)

    @locker.expects.with_lock(:main).yields
    @store.expects.eat("some info")

    hurt.do_the_thing(:main, "some info")

    verify_mocks
  end

  def test_internal_locking_scenario_with_inner_error
    hurt = HurtLocker.new create_mocks(:locker, :store)
    err = StandardError.new('fmshooop')  
    @locker.expects.with_lock(:main).yields
    @store.expects.eat("some info").raises(err)

    hurt.do_the_thing(:main, "some info")

    assert_same err, hurt.caught, "Expected that error to be handled internally"
    verify_mocks
  end
	
	def test_returning_false_actually_returns_false_and_not_nil
		create_mock :car
		@car.expects.ignition_on?.returns(true)
		assert_equal true, @car.ignition_on?, "Should be true"
		@car.expects.ignition_on?.returns(false)
		assert_equal false, @car.ignition_on?, "Should be false"
	end

  def test_should_raise_deprecation_error_when_old_expect_called
    create_mock :foo
    assert_error Hardmock::DeprecationError, /expect/,/expects/,/instead of/,/sorry/i do
      @foo.expect.something
    end
  end

  def test_should_be_able_to_mock_methods_inherited_from_object
    target_methods = %w|to_s inspect instance_eval instance_variables id clone display dup eql? ==|
    create_mock :foo
    target_methods.each do |m|
      eval %{@foo.expects(m, "some stuff")}
      eval %{@foo.#{m} "some stuff"}
    end
  end

end