require 'test_plugin_helper'
require 'securerandom'

module ForemanRemoteExecution
  class RunHostsJobTest < ActiveSupport::TestCase
    include Dynflow::Testing

    # Adding run_step_id wich is needed in RunHostsJob as a quick fix
    # it will be added to dynflow in the future see https://github.com/Dynflow/dynflow/pull/391
    # rubocop:disable Style/ClassAndModuleChildren
    class Dynflow::Testing::DummyPlannedAction
      def run_step_id
        Dynflow::Testing.get_id
      end
    end
    # rubocop:enable Style/ClassAndModuleChildren

    let(:host) { FactoryBot.create(:host, :with_execution) }
    let(:proxy) { host.remote_execution_proxies('SSH')[:subnet].first }
    let(:targeting) { FactoryBot.create(:targeting, :search_query => "name = #{host.name}", :user => User.current) }
    let(:job_invocation) do
      FactoryBot.build(:job_invocation, :with_template).tap do |invocation|
        invocation.targeting = targeting
        invocation.description = 'Some short description'
        invocation.password = 'changeme'
        invocation.key_passphrase = 'changemetoo'
        invocation.effective_user_password = 'sudopassword'
        invocation.save
      end
    end

    let(:uuid) { SecureRandom.uuid }
    let(:task) do
      OpenStruct.new(:id => uuid).tap do |o|
        o.stubs(:add_missing_task_groups)
        o.stubs(:task_groups).returns([])
        o.stubs(:pending?).returns(true)
      end
    end
    let(:action) do
      create_action(Actions::RemoteExecution::RunHostsJob).tap do |action|
        action.expects(:action_subject).with(job_invocation, job_features: [])
        ForemanTasks::Task::DynflowTask.stubs(:where).returns(mock.tap { |m| m.stubs(:first! => task) })
      end
    end
    let(:planned) do
      plan_action action, job_invocation
    end

    let(:delayed) do
      action.delay({ :start_at => Time.now.getlocal }, job_invocation)
      action
    end

    before do
      ProxyAPI::ForemanDynflow::DynflowProxy.any_instance.stubs(:tasks_count).returns(0)
      User.current = users :admin
      action
    end

    context 'targeting resolving' do
      it 'resolves dynamic targeting in plan' do
        targeting.targeting_type = 'dynamic_query'
        assert_not targeting.resolved?
        delayed
        assert_not targeting.resolved?
        planned
        assert_includes targeting.hosts, host
      end

      it 'resolves the hosts on static targeting in delay' do
        assert_not targeting.resolved?
        delayed
        assert_includes targeting.hosts, host
        # Verify Targeting#resolve_hosts! won't be hit again
        targeting.expects(:resolve_hosts!).never
        planned
      end

      it 'resolves the hosts on static targeting in plan phase if not resolved yet' do
        planned
        assert_includes targeting.hosts, host
      end
    end

    it 'triggers the RunHostJob actions on the resolved hosts in run phase' do
      planned.expects(:output).at_most(5).returns(:planned_count => 0)
      planned.expects(:trigger).with { |*args| args[0] == Actions::RemoteExecution::RunHostJob }
      planned.create_sub_plans
    end

    it 'uses the BindJobInvocation middleware' do
      planned
      assert_equal job_invocation.task_id, uuid
    end

    # In plan phase this is handled by #action_subject
    #   which is expected in tests
    it 'sets input in delay phase when delayed' do
      job_invocation_hash = delayed.input[:job_invocation]
      assert_equal job_invocation_hash['id'], job_invocation.id
      assert_equal job_invocation_hash['name'], job_invocation.job_category
      assert_equal job_invocation_hash['description'], job_invocation.description
      planned # To make the expectations happy
    end

    describe '#proxy_batch_size' do
      it 'defaults to Setting[foreman_tasks_proxy_batch_size]' do
        Setting.expects(:[]).with('foreman_tasks_proxy_batch_size').returns(14)
        planned
        assert_equal 14, planned.proxy_batch_size
      end

      it 'gets the provider value' do
        provider = mock('provider')
        provider.expects(:proxy_batch_size).returns(15)
        JobTemplate.any_instance.expects(:provider).returns(provider)

        assert_equal 15, planned.proxy_batch_size
      end
    end

    describe 'concurrency control' do
      let(:level) { 5 }

      it 'can be disabled' do
        assert_nil planned.concurrency_limit
      end

      it 'can limit concurrency level' do
        job_invocation.expects(:concurrency_level).twice.returns(level)
        assert_equal level, planned.concurrency_limit
      end
    end

    describe 'notifications' do
      it 'creates drawer notification on succeess' do
        blueprint = planned.job_invocation.build_notification
        blueprint.expects(:deliver!)
        planned.job_invocation.expects(:build_notification).returns(blueprint)
        planned.notify_on_success(nil)
      end

      it 'creates drawer notification on failure' do
        blueprint = planned.job_invocation.build_notification
        blueprint.expects(:deliver!)
        planned.job_invocation.expects(:build_notification).returns(blueprint)
        planned.notify_on_failure(nil)
      end

      describe 'ignoring drawer notification' do
        before do
          blueprint = planned.job_invocation.build_notification
          blueprint.expects(:deliver!)
          planned.job_invocation.expects(:build_notification).returns(blueprint)
        end

        let(:mail) do
          object = mock
          object.stubs(:deliver_now)
          object
        end

        describe 'for user subscribed to all' do
          before do
            planned.expects(:mail_notification_preference).returns(UserMailNotification.new(:interval => RexMailNotification::ALL_JOBS))
          end

          it 'sends the mail notification on success' do
            RexJobMailer.expects(:job_finished).returns(mail)
            planned.notify_on_success(nil)
          end

          it 'sends the mail notification on failure' do
            RexJobMailer.expects(:job_finished).returns(mail)
            planned.notify_on_failure(nil)
          end
        end

        describe 'for user subscribed to failures' do
          before do
            planned.expects(:mail_notification_preference).returns(UserMailNotification.new(:interval => RexMailNotification::FAILED_JOBS))
          end

          it 'it does not send the mail notification on success' do
            RexJobMailer.expects(:job_finished).never
            planned.notify_on_success(nil)
          end

          it 'sends the mail notification on failure' do
            RexJobMailer.expects(:job_finished).returns(mail)
            planned.notify_on_failure(nil)
          end
        end

        describe 'for user subscribed to successful jobs' do
          before do
            planned.expects(:mail_notification_preference).returns(UserMailNotification.new(:interval => RexMailNotification::SUCCEEDED_JOBS))
          end

          it 'sends the mail notification on success' do
            RexJobMailer.expects(:job_finished).returns(mail)
            planned.notify_on_success(nil)
          end

          it 'does not send the mail notification on failure' do
            RexJobMailer.expects(:job_finished).never
            planned.notify_on_failure(nil)
          end
        end
      end
    end
  end
end
