# frozen_string_literal: true

require_relative 'test_helper'

module Dynflow
  CWE = Support::CodeWorkflowExample

  describe Testing do
    include Testing

    describe 'testing' do
      specify '#plan_action' do
        input  = { 'input' => 'input' }
        action = create_and_plan_action Support::DummyExample::WeightedPolling, input

        _(action).must_be_kind_of Support::DummyExample::WeightedPolling
        _(action.phase).must_equal Action::Plan
        _(action.input).must_equal input
        _(action.execution_plan).must_be_kind_of Testing::DummyExecutionPlan
        _(action.state).must_equal :success
        assert_run_phase action
        assert_finalize_phase action
        assert_action_planned action, Support::DummyExample::Polling
        refute_action_planned action, CWE::DummyAnotherTrigger
      end

      specify 'stub_plan_action' do
        action = create_action Support::DummyExample::WeightedPolling
        action.execution_plan.stub_planned_action(Support::DummyExample::Polling) do |sub_action|
          sub_action.define_singleton_method(:test) { "test" }
        end
        plan_action(action, {})
        stubbed_action = action.execution_plan.planned_plan_steps.first
        _(stubbed_action.test).must_equal "test"
      end

      specify '#create_action_presentation' do
        action = create_action_presentation(Support::DummyExample::WeightedPolling)
        action.output['message'] = 'make the world a better place'
        _(action.humanized_output).must_equal 'You should make the world a better place'
      end

      specify '#run_action without suspend' do
        input  = { 'input' => 'input' }
        plan   = create_and_plan_action Support::DummyExample::WeightedPolling, input
        action = run_action plan

        _(action).must_be_kind_of Support::DummyExample::WeightedPolling
        _(action.phase).must_equal Action::Run
        _(action.input).must_equal input
        _(action.world).must_equal plan.world
        _(action.run_step_id).wont_equal action.plan_step_id
        _(action.state).must_equal :success
      end

      specify '#run_action with suspend' do
        input  = { 'input' => 'input' }
        plan   = create_and_plan_action Support::DummyExample::Polling, input
        action = run_action plan

        _(action.output).must_equal 'task' => { 'progress' => 0, 'done' => false }
        _(action.run_progress).must_equal 0

        3.times { progress_action_time action }
        _(action.output).must_equal('task' => { 'progress' => 30, 'done' => false },
                                 'poll_attempts' => { 'total' => 2, 'failed' => 0 })
        _(action.run_progress).must_equal 0.3

        run_action action, Dynflow::Action::Polling::Poll
        run_action action, Dynflow::Action::Polling::Poll
        _(action.output).must_equal('task' => { 'progress' => 50, 'done' => false },
                                 'poll_attempts' => { 'total' => 4, 'failed' => 0 })
        _(action.run_progress).must_equal 0.5

        5.times { progress_action_time action }

        _(action.output).must_equal('task' => { 'progress' => 100, 'done' => true },
                                 'poll_attempts' => { 'total' => 9, 'failed' => 0 })
        _(action.run_progress).must_equal 1
      end

      specify '#finalize_action' do
        input                 = { 'input' => 'input' }
        plan                  = create_and_plan_action Support::DummyExample::WeightedPolling, input
        run                   = run_action plan
        $dummy_heavy_progress = false
        action                = finalize_action run

        _(action).must_be_kind_of Support::DummyExample::WeightedPolling
        _(action.phase).must_equal Action::Finalize
        _(action.input).must_equal input
        _(action.output).must_equal run.output
        _(action.world).must_equal plan.world
        _(action.finalize_step_id).wont_equal action.run_step_id
        _(action.state).must_equal :success

        _($dummy_heavy_progress).must_equal 'dummy_heavy_progress'
      end
    end

    describe 'testing examples' do
      describe CWE::Commit do
        it 'plans' do
          action = create_and_plan_action CWE::Commit, sha = 'commit-sha'

          _(action.input).must_equal({})
          refute_run_phase action
          refute_finalize_phase action

          assert_action_planned action, CWE::Ci
          assert_action_planned_with action, CWE::Review do |_, name, _|
            name == 'Morfeus'
          end
          assert_action_planned_with action, CWE::Review, sha, 'Neo', true
        end
      end

      describe CWE::Review do
        let(:plan_input) { ['sha', 'name', true] }
        let(:input) { { commit: 'sha', reviewer: 'name', result: true } }
        let(:planned_action) { create_and_plan_action CWE::Review, *plan_input }
        let(:runned_action) { run_action planned_action }

        it 'plans' do
          _(planned_action.input).must_equal Utils.stringify_keys(input)
          assert_run_phase planned_action, { commit: "sha", reviewer: "name", result: true }
          refute_finalize_phase planned_action

          _(planned_action.execution_plan.planned_plan_steps).must_be_empty
        end

        it 'runs' do
          _(runned_action.output.fetch(:passed)).must_equal runned_action.input.fetch(:result)
        end
      end

      describe CWE::Merge do
        let(:plan_input) { { commit: 'sha', ci_result: true, review_results: [true, true] } }
        let(:input) { plan_input }
        let(:planned_action) { create_and_plan_action CWE::Merge, plan_input }
        let(:runned_action) { run_action planned_action }

        it 'plans' do
          assert_run_phase planned_action do |input|
            _(input[:commit]).must_equal "sha"
          end
          refute_finalize_phase planned_action

          _(planned_action.execution_plan.planned_plan_steps).must_be_empty
        end

        it 'runs' do
          _(runned_action.output.fetch(:passed)).must_equal true
        end

        describe 'when something fails' do
          def plan_input
            super.update review_results: [true, false]
          end

          it 'runs' do
            _(runned_action.output.fetch(:passed)).must_equal false
          end
        end
      end
    end

    describe 'in thread executor with unrelated events in clock' do
      class PollingAction < ::Dynflow::Action
        def run(event = nil)
          if output[:suspended].nil?
            output[:suspended] = true
            suspend do |action|
              world.clock.ping(action, 1000, nil)
            end
          end
        end
      end

      let :world do
        WorldFactory.create_world(Dynflow::Testing::InThreadWorld)
      end

      let :execution_plan do
        world.plan(PollingAction)
      end

      it 'processes unrelated events' do
        q = Queue.new
        20.times { |i| world.clock.ping q, 0.0002, :periodic_check_inbox }

        f = world.execute(execution_plan.id)

        # This deadlocks the test
        f.wait

        f.value.tap do |plan|
          _(plan.state).must_equal :stopped
        end
      end
    end

    describe "in thread executor" do
      let :world do
        WorldFactory.create_world(Dynflow::Testing::InThreadWorld)
      end

      let :issues_data do
        [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
         { 'author' => 'John Doe', 'text' => 'Internal server error' }]
      end

      let :failing_issues_data do
        [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
         { 'author' => 'John Doe', 'text' => 'trolling' }]
      end

      let :finalize_failing_issues_data do
        [{ 'author' => 'Peter Smith', 'text' => 'Failing test' },
         { 'author' => 'John Doe', 'text' => 'trolling in finalize' }]
      end

      let :execution_plan do
        world.plan(Support::CodeWorkflowExample::IncomingIssues, issues_data)
      end

      let :failed_execution_plan do
        plan = world.plan(Support::CodeWorkflowExample::IncomingIssues, failing_issues_data)
        plan = world.execute(plan.id).value
        _(plan.state).must_equal :paused
        plan
      end

      let :polling_execution_plan do
        world.plan(Support::DummyExample::Polling, { :external_task_id => '123' })
      end

      it "is able to execute plans inside the thread" do
        world.execute(execution_plan.id).value.tap do |plan|
          _(plan.state).must_equal :stopped
        end
      end

      it "is able to handle errors in the plan" do
        world.execute(failed_execution_plan.id).value.tap do |plan|
          _(plan.state).must_equal :paused
        end
      end

      it "is able to handle when events" do
        world.execute(polling_execution_plan.id).value.tap do |plan|
          _(plan.state).must_equal :stopped
        end
      end

      describe 'auto rescue' do
        let(:world) do
          WorldFactory.create_world(Dynflow::Testing::InThreadWorld) do |config|
            config.auto_rescue = true
          end
        end

        describe 'of plan with skips' do
          let :execution_plan do
            plan = world.plan(Support::RescueExample::ComplexActionWithSkip, :error_on_run)
            world.execute(plan.id).value
          end

          it 'skips the action and continues automatically' do
            _(execution_plan.state).must_equal :stopped
            _(execution_plan.result).must_equal :warning
          end
        end
      end
    end
  end
end
