require 'test_plugin_helper'

class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
  let(:provider) { 'SSH' }

  before { User.current = FactoryBot.build(:user, :admin) }
  after { User.current = nil }

  describe 'ssh specific params' do
    let(:host) { FactoryBot.create(:host, :with_execution) }
    let(:sshkey) { 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQ foo@example.com' }

    before do
      SmartProxy.any_instance.stubs(:pubkey).returns(sshkey)
      Setting[:remote_execution_ssh_user] = 'root'
      Setting[:remote_execution_effective_user_method] = 'sudo'
    end

    it 'has ssh user in the parameters' do
      assert_equal Setting[:remote_execution_ssh_user], host.host_param('remote_execution_ssh_user')
    end

    it 'can override ssh user' do
      host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_user', :value => 'amy')
      assert_equal 'amy', host.host_param('remote_execution_ssh_user')
    end

    it 'has effective user method in the parameters' do
      assert_equal Setting[:remote_execution_effective_user_method], host.host_param('remote_execution_effective_user_method')
    end

    it 'can override effective user method' do
      host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_effective_user_method', :value => 'su')
      assert_equal 'su', host.host_param('remote_execution_effective_user_method')
    end

    it 'has ssh keys in the parameters' do
      assert_includes host.remote_execution_ssh_keys, sshkey
    end

    it 'merges ssh keys from host parameters and proxies' do
      key = 'ssh-rsa not-even-a-key something@somewhere.com'
      host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_keys', :value => [key])
      assert_includes host.host_param('remote_execution_ssh_keys'), key
      assert_includes host.host_param('remote_execution_ssh_keys'), sshkey
    end

    it 'merges ssh key as a string from host parameters and proxies' do
      key = 'ssh-rsa not-even-a-key something@somewhere.com'
      host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_keys', :value => key)
      assert_includes host.host_param('remote_execution_ssh_keys'), key
      assert_includes host.host_param('remote_execution_ssh_keys'), sshkey
    end

    it 'has ssh keys in the parameters even when no user specified' do
      # this is a case, when using the helper in provisioning templates
      FactoryBot.create(:smart_proxy, :ssh)
      host.interfaces.first.subnet.remote_execution_proxies.clear
      User.current = nil
      assert_includes host.remote_execution_ssh_keys, sshkey
    end
  end

  context 'host has multiple nics' do
    let(:host) { FactoryBot.build(:host, :with_execution) }

    it 'should only have one execution interface' do
      host.interfaces << FactoryBot.build(:nic_managed)
      host.interfaces.each { |interface| interface.execution = true }
      assert_predicate host, :valid?
      assert_equal 1, host.interfaces.count(&:execution?)
    end

    it 'returns the execution interface' do
      assert_kind_of Nic::Managed, host.execution_interface
    end
  end

  context 'scoped search' do
    let(:job) do
      job = FactoryBot.create(:job_invocation, :with_task)
      job.template_invocations << FactoryBot.create(:template_invocation, :with_host, :with_failed_task)
      job
    end

    let(:job2) do
      job = FactoryBot.create(:job_invocation, :with_task)
      job.template_invocations << FactoryBot.create(:template_invocation, :with_host, :with_failed_task)
      job
    end

    it 'finds hosts for job_invocation.id' do
      found_ids = Host.search_for("job_invocation.id = #{job.id}").map(&:id).sort
      assert_equal job.template_invocations_host_ids.sort, found_ids
    end

    it 'finds hosts by job_invocation.result' do
      success, failed = job.template_invocations
                           .partition { |template| template.run_host_job_task.result == 'success' }
      found_ids = Host.search_for('job_invocation.result = success').map(&:id)
      assert_equal success.map(&:host_id), found_ids
      found_ids = Host.search_for('job_invocation.result = failed').map(&:id)
      assert_equal failed.map(&:host_id), found_ids
    end

    it 'finds hosts by job_invocation.id and job_invocation.result' do
      # Force evaluation of the jobs
      job
      job2

      assert_equal 2, Host.search_for("job_invocation.id = #{job.id}").count
      assert_equal 2, Host.search_for("job_invocation.id = #{job2.id}").count
      assert_equal 2, Host.search_for('job_invocation.result = success').count
      assert_equal 2, Host.search_for('job_invocation.result = failed').count

      success, failed = job.template_invocations
                           .partition { |template| template.run_host_job_task.result == 'success' }
      found_ids = Host.search_for("job_invocation.id = #{job.id} AND job_invocation.result = success").map(&:id)
      assert_equal success.map(&:host_id), found_ids
      found_ids = Host.search_for("job_invocation.id = #{job.id} AND job_invocation.result = failed").map(&:id)
      assert_equal failed.map(&:host_id), found_ids
    end
  end

  describe 'proxy determination strategies' do
    context 'subnet strategy' do
      let(:host) { FactoryBot.build(:host, :with_execution) }
      it { assert_includes host.remote_execution_proxies(provider)[:subnet], host.subnet.remote_execution_proxies.first }
    end

    context 'fallback strategy' do
      let(:host) { FactoryBot.build(:host, :with_tftp_subnet) }

      context 'enabled' do
        before do
          Setting[:remote_execution_fallback_proxy] = true
          host.subnet.tftp.features << FactoryBot.create(:feature, :ssh)
        end

        it 'returns a fallback proxy' do
          assert_includes host.remote_execution_proxies(provider)[:fallback], host.subnet.tftp
        end
      end

      context 'disabled' do
        before do
          Setting[:remote_execution_fallback_proxy] = false
          host.subnet.tftp.features << FactoryBot.create(:feature, :ssh)
        end

        it 'returns no proxy' do
          assert_empty host.remote_execution_proxies(provider)[:fallback]
        end
      end
    end

    context 'global strategy' do
      let(:tax_organization) { FactoryBot.build(:organization) }
      let(:tax_location) { FactoryBot.build(:location) }
      let(:host) { FactoryBot.build(:host, :organization => tax_organization, :location => tax_location) }
      let(:proxy_in_taxonomies) { FactoryBot.create(:smart_proxy, :ssh, :organizations => [tax_organization], :locations => [tax_location]) }
      let(:proxy_no_taxonomies) { FactoryBot.create(:smart_proxy, :ssh) }

      context 'enabled' do
        before { Setting[:remote_execution_global_proxy] = true }

        it 'returns correct proxies confined by taxonomies' do
          proxy_in_taxonomies
          proxy_no_taxonomies
          assert_includes host.remote_execution_proxies(provider)[:global], proxy_in_taxonomies
          refute_includes host.remote_execution_proxies(provider)[:global], proxy_no_taxonomies
        end
      end

      context 'disabled' do
        before { Setting[:remote_execution_global_proxy] = false }
        it 'returns no proxy' do
          assert_empty host.remote_execution_proxies(provider)[:global]
        end
      end
    end
  end

  describe '#execution_scope' do
    let(:host) { FactoryBot.create(:host) }
    let(:infra_host) { FactoryBot.create(:host, :with_infrastructure_facet) }

    before do
      host
      infra_host
    end

    context 'without infrastructure host permission' do
      it 'omits the infrastructure host' do
        setup_user('view', 'hosts')

        hosts = ::Host::Managed.execution_scope
        assert_includes hosts, host
        refute_includes hosts, infra_host
      end
    end

    context 'with infrastructure host permission' do
      it 'finds the host as admin' do
        assert User.current.admin?
        hosts = ::Host::Managed.execution_scope
        assert_includes hosts, host
        assert_includes hosts, infra_host
      end

      it 'finds the host as user with needed permissions' do
        setup_user('execute_jobs_on', 'infrastructure_hosts')
        hosts = ::Host::Managed.execution_scope
        assert_includes hosts, host
        assert_includes hosts, infra_host
      end
    end
  end
end
