require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe ReRule do
  def valid_attributes
    {
      :rule_class_name => "MockRuleClass",
      :title => "Mock Title",      
      :summary => "Mock Summary",
      :data => '["Rule Title", ["one", "two"], "start_workflow", "Other Pipeline"]'
    }
  end
  
  before(:each) do
    @rule = mock("MockRule", :title => "rule title", :summary => "rule summary", :data => "rule data")
    @rule.stub!(:title=)
    @rule.stub!(:summary=)
    @rule.stub!(:data=)
    @rule.stub!(:after_add_to_workflow)
    @rule.stub!(:before_remove_from_workflow)
    @rule.stub!(:expected_outcomes).and_return([])
    @rule.stub!(:valid?).and_return(true)
    @rule_class = mock("MockRuleClass")
    @rule_class.stub!(:new).and_return(@rule)      
    RulesEngine::Discovery.stub!(:rule_class).and_return(@rule_class)
    
    @re_workflow = mock('workflow', :valid? => true)
    @re_workflow.stub!(:code).and_return('mock code')
    @re_workflow.stub!(:changed!)
  end
  
  it "should be valid with valid attributes" do
    ReRule.new(valid_attributes).should be_valid
  end
  
  should_belong_to :re_workflow
  should_have_many :re_rule_expected_outcomes
  should_validate_presence_of :rule_class_name

  describe "validate the rule" do
    it "should be false if the rule class not found" do
      re_rule = ReRule.new(valid_attributes)
      RulesEngine::Discovery.stub!(:rule_class).and_return(nil)
      re_rule.should_not be_valid
      re_rule.should have(1).error_on(:rule_class)
    end
  
    it "should be false if the rule is not valid" do
      re_rule = ReRule.new(valid_attributes)
      @rule.should_receive(:valid?).and_return(false)    
      re_rule.should_not be_valid
      # re_rule.should have(1).error_on(:rule)      
    end
  end
    
  describe "loading a rule" do
    before(:each) do
      @re_rule = ReRule.new(valid_attributes)
    end
  
    it "should return an instance of the rule" do
      @re_rule.rule.should == @rule
    end
    
    it "should load the rule from the discovery" do
      RulesEngine::Discovery.should_receive(:rule_class).with("MockRuleClass")
      @re_rule.rule
    end
  
    it "should only load the rule once" do
      RulesEngine::Discovery.should_receive(:rule_class).once
      @re_rule.rule.should == @rule
      @re_rule.rule.should == @rule
    end
  
    it "should return nil if the rule class not found" do
      RulesEngine::Discovery.should_receive(:rule_class).with("MockRuleClass").and_return(nil)
      @re_rule.rule.should be_nil
    end
  
    it "should create a new instance of the rule class" do
      @rule_class.should_receive(:new)
      @re_rule.rule
    end
  
    it "should set the rule data with the model data attribute" do
      @rule.should_receive(:data=).with(valid_attributes[:data])
      @re_rule.rule
    end
  end
  
  describe "setting the rule attributes" do
    it "should raise an error if the rule does not exist" do
      re_rule = ReRule.new(valid_attributes)
      RulesEngine::Discovery.should_receive(:rule_class).with("MockRuleClass").and_return(nil)
      lambda { re_rule.rule_attributes={} }.should raise_error('rule class not found')
    end
    
    it "should pass the parameters to the rule" do
      re_rule = ReRule.new(valid_attributes)
      @rule.should_receive("attributes=").with({:mock => "param"})
      re_rule.rule_attributes = {:mock => "param"}
    end    
  end
    
  describe "saving a re_rule" do
    before(:each) do
      @re_rule = ReRule.new(valid_attributes)
      @re_rule.stub!(:re_workflow).and_return(@re_workflow)
    end
    
    it "should fail if the rule does not exist" do
      @re_rule.should_receive(:rule).and_return(nil)
      @re_rule.save.should == false
    end

    it "should set tell the workflow the rule has changed" do
      @re_workflow.should_receive(:changed!)
      @re_rule.save
    end
          
    describe "a valid rule" do
      before(:each) do
        @re_rule.stub(:rule).and_return(@rule)
      end
  
      it "should be successful" do
        @re_rule.save.should == true
      end
  
      it "should set the re_rule title to the title generated by the rule" do
        @re_rule.save
        @re_rule.title.should == "rule title"      
      end
  
      it "should set the re_rule summary to the summary generated by the rule" do
        @re_rule.save
        @re_rule.summary.should == "rule summary"      
      end
      
      it "should set the re_rule data to the data generated by the rule" do
        @re_rule.save
        @re_rule.data.should == "rule data"      
      end      
      
      it "should set the expected outcomes to the expected outcomes generated by the rule" do
        re_rule_expected_outcome = ReRuleExpectedOutcome.new
        ReRuleExpectedOutcome.should_receive(:new).with(:outcome => 101, :workflow_code => "one").and_return(re_rule_expected_outcome)
        ReRuleExpectedOutcome.should_receive(:new).with(:outcome => 202, :workflow_code => "two").and_return(re_rule_expected_outcome)
        @rule.stub(:expected_outcomes).and_return([{:outcome => 101, :workflow_code => "one"}, {:outcome => 202, :workflow_code => "two"}])
        @re_rule.save
        @re_rule.re_rule_expected_outcomes.should == [re_rule_expected_outcome, re_rule_expected_outcome]
      end      
    end    
  end
  
  describe "after saving a re_rule" do
    it "should notify the rule" do
      re_rule = ReRule.new(valid_attributes)
      re_rule.stub!(:re_workflow).and_return(@re_workflow)

      @re_workflow.stub!(:code).and_return('1001')
      re_rule.stub!(:rule).and_return(@rule)

      updated_code = nil
      @rule.should_receive(:after_add_to_workflow) do |re_workflow_code|
        updated_code = re_workflow_code
      end
      re_rule.save
      updated_code.should == '1001'
    end  
  end
  
  describe "before destroying a re_rule" do
    it "should notify the rule" do
      @re_workflow = mock('re_workflow', :code => "1001", :valid? => true)
      @re_rule = ReRule.new(valid_attributes)
      @re_rule.stub!(:re_workflow).and_return(@re_workflow)

      @rule.should_receive(:before_remove_from_workflow).with("1001")
      @re_rule.destroy
    end  
  end
  
  describe "rule outcomes available" do
    before(:each) do
      @re_rule = ReRule.new      
    end
    
    it "should return the first outcome that is a OUTCOME_NEXT" do
      outcome_1 = ReRuleExpectedOutcome.new(:outcome => RulesEngine::Rule::Outcome::NEXT)
      outcome_2 = ReRuleExpectedOutcome.new(:outcome => RulesEngine::Rule::Outcome::NEXT)
      @re_rule.re_rule_expected_outcomes << outcome_1
      @re_rule.re_rule_expected_outcomes << outcome_2
      @re_rule.re_rule_expected_outcome_next.should == outcome_1
    end
  
    it "should return the first outcome that is a OUTCOME_STOP_SUCCESS" do
      outcome_1 = ReRuleExpectedOutcome.new(:outcome => RulesEngine::Rule::Outcome::STOP_SUCCESS)
      outcome_2 = ReRuleExpectedOutcome.new(:outcome => RulesEngine::Rule::Outcome::STOP_SUCCESS)
      @re_rule.re_rule_expected_outcomes << outcome_1
      @re_rule.re_rule_expected_outcomes << outcome_2
      @re_rule.re_rule_expected_outcome_success.should == outcome_1
    end
  
    it "should return the first outcome that is a OUTCOME_STOP_FAILURE" do
      outcome_1 = ReRuleExpectedOutcome.new(:outcome => RulesEngine::Rule::Outcome::STOP_FAILURE)
      outcome_2 = ReRuleExpectedOutcome.new(:outcome => RulesEngine::Rule::Outcome::STOP_FAILURE)
      @re_rule.re_rule_expected_outcomes << outcome_1
      @re_rule.re_rule_expected_outcomes << outcome_2
      @re_rule.re_rule_expected_outcome_failure.should == outcome_1
    end
  
    it "should return all outcomes that are a OUTCOME_START_WORKFLOW" do
      outcome_1 = ReRuleExpectedOutcome.new(:outcome => RulesEngine::Rule::Outcome::START_WORKFLOW)
      outcome_2 = ReRuleExpectedOutcome.new(:outcome => RulesEngine::Rule::Outcome::START_WORKFLOW)
      @re_rule.re_rule_expected_outcomes << outcome_1
      @re_rule.re_rule_expected_outcomes << outcome_2
      @re_rule.re_rule_expected_outcomes_start_workflow.should == [outcome_1, outcome_2]
    end
  end
  
  describe "checking for rule errors" do
    before(:each) do
      @rule = mock("Rule", :valid? => true)
      
      @re_rule = ReRule.new(valid_attributes)
      @re_rule.stub!(:rule).and_return(@rule)
    end
    
    it "should return '[title] class [class] invalid' if the rule is missing" do
      @re_rule.should_receive(:rule).and_return(nil)
      @re_rule.rule_error.should == "class MockRuleClass missing"
    end
    
    it "should ignore outcomes where the workflow_code is nil" do
      re_rule_expected_outcome = mock_model(ReRuleExpectedOutcome, :workflow_code => nil)
      @re_rule.stub!(:re_rule_expected_outcomes).and_return([re_rule_expected_outcome])
      @re_rule.rule_error.should be_nil
    end
  
    it "should validate the workflow is present and activated" do
      re_rule_expected_outcome = mock_model(ReRuleExpectedOutcome, :workflow_code => "mock_workflow_code", :outcome => RulesEngine::Rule::Outcome::START_WORKFLOW)
      ReWorkflow.should_receive(:find_by_code).and_return(mock("ReWorkflow", :workflow_error => nil))      
      @re_rule.stub!(:re_rule_expected_outcomes).and_return([re_rule_expected_outcome])
      @re_rule.rule_error.should be_nil
    end
      
    it "should return '[workflow_code] missing' if the required workflow is missing" do
      re_rule_expected_outcome = mock_model(ReRuleExpectedOutcome, :workflow_code => "mock_workflow_code", :outcome => RulesEngine::Rule::Outcome::START_WORKFLOW)
      ReWorkflow.should_receive(:find_by_code).and_return(nil)
      @re_rule.stub!(:re_rule_expected_outcomes).and_return([re_rule_expected_outcome])
      @re_rule.rule_error.should == "mock_workflow_code missing"
    end
    
    it "should return '[workflow_code] invalid' if the required workflow has errors" do
      re_rule_expected_outcome = mock_model(ReRuleExpectedOutcome, :workflow_code => "mock_workflow_code", :outcome => RulesEngine::Rule::Outcome::START_WORKFLOW)
      ReWorkflow.should_receive(:find_by_code).and_return(mock("ReWorkflow", :workflow_error => "workflow error"))      
      @re_rule.stub!(:re_rule_expected_outcomes).and_return([re_rule_expected_outcome])
      @re_rule.rule_error.should == "mock_workflow_code invalid"
    end
    
  end
  
  describe "moving items in a list" do
    it "should move a rule down in the list" do
      re_workflow = ReWorkflow.create!(:code => "AA-MOCK",:title => "Mock Title")
      
      re_rule_1 = ReRule.new(valid_attributes)
      re_rule_2 = ReRule.new(valid_attributes)
      re_workflow.re_rules << re_rule_1
      re_workflow.re_rules << re_rule_2
      
      re_workflow.reload    
      
      re_workflow.re_rules.should == [re_rule_1, re_rule_2]
      re_workflow.re_rules[1].move_higher
      re_workflow.reload    
  
      re_workflow.re_rules.should == [re_rule_2, re_rule_1]
    end
  end    
  
  describe "publish" do
    it "should convert the rule to a hash" do
      re_rule = ReRule.new(valid_attributes)
      
      publish_data = re_rule.publish
      publish_data["rule_class_name"].should == valid_attributes[:rule_class_name]
      publish_data["title"].should == valid_attributes[:title]
      publish_data["summary"].should == valid_attributes[:summary]
      publish_data["data"].should == valid_attributes[:data]
    end
  end

  describe "revert!" do
    it "should return self" do
      re_rule = ReRule.new
      re_rule.revert!({}).should == re_rule
    end

    it "should set the rule based on the data" do
      re_rule = ReRule.new
      RulesEngine::Discovery.should_receive(:rule_class).with('mock_rule_class_name').and_return(@rule_class)      
      
      re_rule.revert!({
        "rule_class_name" => 'mock_rule_class_name',
        "title" => 'mock title',
        "summary" => 'mock summary',
        "data" => 'mock data'
      })
      
      re_rule[:rule_class_name].should == 'mock_rule_class_name'
      re_rule[:title].should == 'mock title'
      re_rule[:summary].should == 'mock summary'
      re_rule[:data].should == 'mock data'
    end
  end
end