require "helper"

class CreateTrafficLights < ActiveRecord::Migration
  def self.up
    create_table(:traffic_lights, :force => true) do |t|
      t.string :state
      t.string :name
    end
  end
end

set_up_db CreateTrafficLights

class CreateDifferentTrafficLights < ActiveRecord::Migration
  def self.up
    create_table(:different_traffic_lights) do |t|
      t.string :different_state
      t.string :name
    end
  end
end

set_up_db CreateDifferentTrafficLights

class TrafficLight < ActiveRecord::Base
  include ActiveModel::Transitions
  attr_reader :power

  state_machine :auto_scopes => true do
    state :off, enter: :turn_power_on

    state :red
    state :green
    state :yellow

    event :red_on do
      transitions :to => :red, :from => [:yellow]
    end

    event :green_on do
      transitions :to => :green, :from => [:red]
    end

    event :yellow_on do
      transitions :to => :yellow, :from => [:green]
    end

    event :reset do
      transitions :to => :red, :from => [:off]
    end
  end

  def turn_power_on
    raise "the power should not have been on already" if @power == :on
    @power = :on
  end
end

class ProtectedTrafficLight < TrafficLight
  attr_protected :state
end

class ValidatingTrafficLight < TrafficLight
  validate {|t| errors.add(:base, 'This TrafficLight will never validate after creation') unless t.new_record? }
end

class ConditionalValidatingTrafficLight < TrafficLight
  validates(:name, :presence => true, :if => :red?)
end

class TestActiveRecord < Test::Unit::TestCase
  def setup
    set_up_db CreateTrafficLights
    @light = TrafficLight.create!
  end

  test "new record has the initial state set" do
    @light = TrafficLight.new
    assert_equal "off", @light.state
  end

  test "new active records defaults current state to the initial state" do
    assert_equal :off, @light.current_state
  end

  test "states initial state" do
    assert @light.off?
    assert_equal :off, @light.current_state
  end

  test "calls enter when setting the initial state" do
    @new_light = TrafficLight.new
    assert_equal :on, @new_light.power
  end

  test "transition to a valid state" do
    @light.reset
    assert @light.red?
    assert_equal :red, @light.current_state

    @light.green_on
    assert @light.green?
    assert_equal :green, @light.current_state
  end

  test "transition does not persist state" do
    @light.reset
    assert_equal :red, @light.current_state
    @light.reload
    assert_equal "off", @light.state
  end

  test "transition does persists state" do
    @light.reset!
    assert_equal :red, @light.current_state
    @light.reload
    assert_equal "red", @light.state
  end

  test "transition to an invalid state" do
    assert_raise(Transitions::InvalidTransition) { @light.yellow_on }
    assert_equal :off, @light.current_state
  end

  test "transition does persists state when state is protected" do
    protected_light = ProtectedTrafficLight.create!
    protected_light.reset!
    assert_equal :red, protected_light.current_state
    protected_light.reload
    assert_equal "red", protected_light.state
  end

  test "transition with wrong state will not validate" do
    for s in @light.class.get_state_machine.states
      @light.state = s.name
      assert @light.valid?
    end
    @light.state = "invalid_one"
    assert_false @light.valid?
  end

  test "transition raises exception when model validation fails" do
    validating_light = ValidatingTrafficLight.create!(:name => 'Foobar')
    assert_raise(ActiveRecord::RecordInvalid) do
      validating_light.reset!
    end
  end

  test "state query method used in a validation condition" do
    validating_light = ConditionalValidatingTrafficLight.create!
    assert_raise(ActiveRecord::RecordInvalid) do
      validating_light.reset!
    end
    assert(validating_light.off?)
  end

  test "reloading model resets current state" do
    @light.reset
    assert @light.red?
    @light.update_attribute(:state, 'green')
    assert @light.reload.green?, "reloaded state should come from database, not instance variable"
  end
  
  test "calling non-bang event updates state attribute" do
    @light.reset!
    assert @light.red?
    @light.green_on
    assert_equal "green", @light.state
    assert_equal "red", @light.reload.state
  end
end

class TestNewActiveRecord < TestActiveRecord

  def setup
    set_up_db CreateTrafficLights
    @light = TrafficLight.new
  end

  test "new active records defaults current state to the initial state" do
    assert_equal :off, @light.current_state
  end

end

class TestScopes < Test::Unit::TestCase
  test "scope returns correct object" do
    @light = TrafficLight.create!
    assert_respond_to TrafficLight, :off
    assert_equal TrafficLight.off.first, @light
    assert TrafficLight.red.empty?
  end

  test "scopes exist" do
    assert_respond_to TrafficLight, :off
    assert_respond_to TrafficLight, :red
    assert_respond_to TrafficLight, :green
    assert_respond_to TrafficLight, :yellow
  end

  test 'scopes are only generated if we explicitly say so' do
    assert_not_respond_to LightBulb, :off
    assert_not_respond_to LightBulb, :on
  end

  test 'scope generation raises an exception if we try to overwrite an existing method' do
    assert_raise(Transitions::InvalidMethodOverride) {
      class Light < ActiveRecord::Base
        include ActiveModel::Transitions

        state_machine :auto_scopes => true do
          state :new
          state :broken
        end
      end
    }
  end
end

class DifferentTrafficLight < ActiveRecord::Base
  include ActiveModel::Transitions

  state_machine :attribute_name => :different_state, :auto_scopes => true do
    state :off

    state :red
    state :green
    state :yellow

    event :red_on do
      transitions :to => :red, :from => [:yellow]
    end

    event :green_on do
      transitions :to => :green, :from => [:red]
    end

    event :yellow_on do
      transitions :to => :yellow, :from => [:green]
    end

    event :reset do
      transitions :to => :red, :from => [:off]
    end
  end
end

class TestActiveRecordWithDifferentColumnName < Test::Unit::TestCase
  def setup
    set_up_db CreateDifferentTrafficLights
    @light = DifferentTrafficLight.create!
  end

  test "new record has the initial state set" do
    @light = DifferentTrafficLight.new
    assert_equal "off", @light.different_state
  end

  test "states initial state" do
    assert @light.off?
    assert_equal :off, @light.current_state
  end

  test "transition to a valid state" do
    @light.reset
    assert @light.red?
    assert_equal :red, @light.current_state

    @light.green_on
    assert @light.green?
    assert_equal :green, @light.current_state
  end

  test "transition does not persist state" do
    @light.reset
    assert_equal :red, @light.current_state
    @light.reload
    assert_equal "off", @light.different_state
  end

  test "transition does persists state" do
    @light.reset!
    assert_equal :red, @light.current_state
    @light.reload
    assert_equal "red", @light.different_state
  end

  test "transition to an invalid state" do
    assert_raise(Transitions::InvalidTransition) { @light.yellow_on }
    assert_equal :off, @light.current_state
  end

  test "transition with wrong state will not validate" do
    for s in @light.class.state_machine.states
      @light.different_state = s.name
      assert @light.valid?
    end
    @light.different_state = "invalid_one"
    assert_false @light.valid?
  end

  test "reloading model resets current state" do
    @light.reset
    assert @light.red?
    @light.update_attribute(:different_state, 'green')
    assert @light.reload.green?, "reloaded state should come from database, not instance variable"
  end
  
  test "calling non-bang event updates state attribute" do
    @light.reset!
    assert @light.red?
    @light.green_on
    assert_equal "green", @light.different_state
    assert_equal "red", @light.reload.different_state
  end
end