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

describe "RulesEngine::Process::Runner" do

  describe "setting the runner" do
    it "should set the runner as a instance of a class" do
      mock_runner = mock('mock_runner')
      RulesEngine::Process.runner = mock_runner
      RulesEngine::Process.runner.should == mock_runner
    end

    it "should set the runner to the database plan runner" do
      RulesEngine::Process.runner = :db_runner
      RulesEngine::Process.runner.should be_instance_of(RulesEngine::Process::DbRunner)
    end
  end
  
  describe "getting the runner" do
    it "should throw an exception if the runner is not set" do
      RulesEngine::Process.runner = nil
      
      lambda {
        RulesEngine::Process.runner
      }.should raise_error
    end        
  end
  
  describe "creating a process" do
    it "should throw an error if not overwritten" do
      lambda {
        RulesEngine::Process::Runner.new.create()
      }.should raise_error
    end
  end

  describe "getting the run status" do
    it "should throw an error if not overwritten" do
      lambda {
        RulesEngine::Process::Runner.new.status(10001)
      }.should raise_error
    end
  end

  describe "getting the run history" do
    it "should throw an error if not overwritten" do
      lambda {
        RulesEngine::Process::Runner.new.history(10001, {})
      }.should raise_error
    end
  end

  describe "running the plan" do
    before(:each) do
      @rule_class = mock("Rule Class")
      @rule_class.stub!(:new).and_return(@rule_class)
      @rule_class.stub!(:title).and_return('mock title')
      @rule_class.stub!(:data=)
      @rule_class.stub!(:process).and_return(RulesEngine::RuleOutcome.new(RulesEngine::RuleOutcome::OUTCOME_NEXT))
      RulesEngine::Discovery.stub!(:rule_class).and_return(@rule_class)

      RulesEngine::Process.auditor.stub!(:audit)# {|one, two, three| puts two }

      @runner = RulesEngine::Process::Runner.new      
      
      @plan = {"code" => "plan_code",
               "first_workflow" => "one",
               
               "workflow_one" => {
                 "rules" => [{
                   "rule_class_name" => "one_one",
                   "data" => "one_one_data",
                 },{
                   "rule_class_name" => "one_two",
                   "data" => "one_two_data",
                 },{
                   "rule_class_name" => "one_three",
                   "data" => "one_two_data",
                 }],
                 "next_workflow" => "two"
               },
               "workflow_two" => {
                 "rules" => [{
                   "rule_class_name" => "two_one",
                   "data" => "two_one_data",
                 }],
                 "next_workflow" => "three"
               },
               "workflow_three" => {
                 "rules" => [{
                   "rule_class_name" => "three_one",
                   "data" => "three_one_data",
                 }],
                 "next_workflow" => ""
               }               
              }
    end
    
    it "should use the _run_plan method" do
      @runner.should_receive(:_run_plan)
      @runner.run(10001, @plan, {})
    end
    
    it "should be successfull with a valid plan" do
      @runner.run(10001, @plan, {}).should == true
    end
        
    it "should audit the plan has started" do
      RulesEngine::Process.auditor.should_receive(:audit).once.with(10001, "Plan : plan_code : started", RulesEngine::Process::AUDIT_INFO)
      @runner.run(10001, @plan, {})
    end

    describe "first_workflow missing" do
      it "should audit the failure" do
        RulesEngine::Process.auditor.should_receive(:audit).once.with(10001, anything, RulesEngine::Process::AUDIT_FAILURE)
        @runner.run(10001, @plan.except("first_workflow"), {})
      end
      
      it "should return false" do
        @runner.run(10001, @plan.except("first_workflow"), {}).should == false
      end
    end    
    
    describe "endless loop" do
      it "should return false" do
        @plan["workflow_one"].merge!("next_workflow" => "one")
        @runner.run(10001, @plan, {}).should == false
      end
    end
    
    describe "workflow missing" do
      it "should return false" do
        @plan["workflow_one"].merge!("next_workflow" => "not_there")
        @runner.run(10001, @plan, {}).should == false
      end
    end
    
    describe "processing the workflows" do
      it "should process each follow the next_workflow " do
        # testing this by checking it runs one rule in each workflow
        RulesEngine::Discovery.should_receive(:rule_class).with("one_one").and_return(@rule_class)
        RulesEngine::Discovery.should_receive(:rule_class).with("two_one").and_return(@rule_class)
        RulesEngine::Discovery.should_receive(:rule_class).with("three_one").and_return(@rule_class)
         @runner.run(10001, @plan, {})
      end          
    end
    
    describe "processing the rules" do
      it "should load the rule" do
        RulesEngine::Discovery.should_receive(:rule_class).with("one_one").and_return(@rule_class)
        @rule_class.should_receive(:new).and_return(@rule_class)
        @rule_class.should_receive(:data=).with("one_one_data")
        @runner.run(10001, @plan, {}).should == true
      end
      
      describe "the rules does not exist" do
        it "should return an error" do
          RulesEngine::Discovery.should_receive(:rule_class).with("one_one").and_return(nil)
          @runner.run(10001, @plan, {}).should == false
        end            
      end
      
      it "should proces the rule" do
        @rule_class.should_receive(:process)
        @runner.run(10001, @plan, {})
      end
      
      describe "processing a rule" do
        before(:each) do
          @test_rule = mock("Test Rule")
          @test_rule.stub!(:new).and_return(@test_rule)
          @test_rule.stub!(:title).and_return('mock title')
          @test_rule.stub!(:data=)
          RulesEngine::Discovery.stub!(:rule_class).with("one_one").and_return(@test_rule)
        end
        
        describe "rule returns nil" do
          it "should proceed to the next rule" do
            @test_rule.stub!(:process).and_return(nil)
            RulesEngine::Discovery.should_receive(:rule_class).with("one_two")
            @runner.run(10001, @plan, {})
          end
        end

        describe "rule returns NEXT" do
          it "should proceed to the next rule" do
            @test_rule.stub!(:process).and_return(RulesEngine::RuleOutcome.new(RulesEngine::RuleOutcome::OUTCOME_NEXT))
            RulesEngine::Discovery.should_receive(:rule_class).with("one_two")
            @runner.run(10001, @plan, {})
          end
        end
        
        describe "rule returns STOP_SUCCESS" do
          it "should not proceed to the next rule" do
            @test_rule.stub!(:process).and_return(RulesEngine::RuleOutcome.new(RulesEngine::RuleOutcome::OUTCOME_STOP_SUCCESS))
            RulesEngine::Discovery.should_not_receive(:rule_class).with("one_two")
            @runner.run(10001, @plan, {})
          end

          it "should return success" do
            @test_rule.stub!(:process).and_return(RulesEngine::RuleOutcome.new(RulesEngine::RuleOutcome::OUTCOME_STOP_SUCCESS))
            @runner.run(10001, @plan, {}) == true
          end
        end

        describe "rule returns STOP_FAILURE" do
          it "should not proceed to the next rule" do
            @test_rule.stub!(:process).and_return(RulesEngine::RuleOutcome.new(RulesEngine::RuleOutcome::OUTCOME_STOP_FAILURE))
            RulesEngine::Discovery.should_not_receive(:rule_class).with("one_two")
            @runner.run(10001, @plan, {})
          end

          it "should return failure" do
            @test_rule.stub!(:process).and_return(RulesEngine::RuleOutcome.new(RulesEngine::RuleOutcome::OUTCOME_STOP_FAILURE))
            @runner.run(10001, @plan, {}) == false
          end
        end

        describe "rule returns START_WORKFLOW" do
          it "should not proceed to the next rule" do
            @test_rule.stub!(:process).and_return(RulesEngine::RuleOutcome.new(RulesEngine::RuleOutcome::OUTCOME_START_WORKFLOW, 'three'))
            RulesEngine::Discovery.should_not_receive(:rule_class).with("one_two")
            @runner.run(10001, @plan, {})
          end

          it "should start the defined workflow" do
            @test_rule.stub!(:process).and_return(RulesEngine::RuleOutcome.new(RulesEngine::RuleOutcome::OUTCOME_START_WORKFLOW, 'three'))
            RulesEngine::Discovery.should_not_receive(:rule_class).with("two_one")
            RulesEngine::Discovery.should_receive(:rule_class).with("three_one")
            @runner.run(10001, @plan, {})
          end
        end
      end
    end
  end
  
end