# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Ci::JobsFinder, '#execute', feature_category: :continuous_integration do
  let_it_be(:user) { create(:user) }
  let_it_be(:admin) { create(:user, :admin) }
  let_it_be(:project) { create(:project, :private, public_builds: false) }
  let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
  let_it_be(:pending_job) { create(:ci_build, :pending) }
  let_it_be(:running_job) { create(:ci_build, :running) }
  let_it_be(:successful_job) { create(:ci_build, :success, pipeline: pipeline, name: 'build') }

  let(:params) { {} }

  context 'when project, pipeline, and runner are blank' do
    subject(:finder_execute) { described_class.new(current_user: current_user, params: params).execute }

    context 'with admin' do
      let(:current_user) { admin }

      context 'when admin mode setting is disabled', :do_not_mock_admin_mode_setting do
        it { is_expected.to match_array([pending_job, running_job, successful_job]) }
      end

      context 'when admin mode setting is enabled' do
        context 'when in admin mode', :enable_admin_mode do
          it { is_expected.to match_array([pending_job, running_job, successful_job]) }
        end

        context 'when not in admin mode' do
          it { is_expected.to be_empty }
        end
      end
    end

    context 'with admin and admin mode enabled', :enable_admin_mode do
      let(:current_user) { admin }

      context 'with param `scope`' do
        using RSpec::Parameterized::TableSyntax

        where(:scope, :expected_jobs) do
          'pending'           | lazy { [pending_job] }
          'running'           | lazy { [running_job] }
          'finished'          | lazy { [successful_job] }
          %w[running success] | lazy { [running_job, successful_job] }
        end

        with_them do
          let(:params) { { scope: scope } }

          it { is_expected.to match_array(expected_jobs) }
        end
      end

      context 'with param `runner_type`' do
        let_it_be(:job_with_instance_runner) { create(:ci_build, :success, runner: create(:ci_runner, :instance)) }
        let_it_be(:job_with_group_runner) do
          create(:ci_build, :success, runner: create(:ci_runner, :group, groups: [project.parent]))
        end

        let_it_be(:job_with_project_runner) do
          create(:ci_build, :success, runner: create(:ci_runner, :project, projects: [project]))
        end

        context 'with feature flag :admin_jobs_filter_runner_type enabled' do
          using RSpec::Parameterized::TableSyntax

          where(:runner_type, :expected_jobs) do
            'group_type'                   | lazy { [job_with_group_runner] }
            'instance_type'                | lazy { [job_with_instance_runner] }
            'project_type'                 | lazy { [job_with_project_runner] }
            %w[instance_type project_type] | lazy { [job_with_instance_runner, job_with_project_runner] }
          end

          with_them do
            let(:params) { { runner_type: runner_type } }
            it { is_expected.to match_array(expected_jobs) }
          end
        end

        context 'with feature flag :admin_jobs_filter_runner_type disabled' do
          let(:params) { { runner_type: 'instance_type' } }
          let(:expected_jobs) do
            [
              job_with_group_runner,
              job_with_instance_runner,
              job_with_project_runner,
              pending_job,
              running_job,
              successful_job
            ]
          end

          before do
            stub_feature_flags(admin_jobs_filter_runner_type: false)
          end

          it { is_expected.to match_array(expected_jobs) }
        end
      end

      context "with params" do
        let_it_be(:job_with_running_status_and_group_runner) do
          create(:ci_build, :running, runner: create(:ci_runner, :group, groups: [project.parent]))
        end

        let_it_be(:job_with_instance_runner) { create(:ci_build, :success, runner: create(:ci_runner, :instance)) }
        let_it_be(:job_with_project_runner) do
          create(:ci_build, :success, runner: create(:ci_runner, :project, projects: [project]))
        end

        context 'with feature flag :admin_jobs_filter_runner_type enabled' do
          using RSpec::Parameterized::TableSyntax

          where(:param_runner_type, :param_scope, :expected_jobs) do
            'group_type'                   | 'running'  | lazy { [job_with_running_status_and_group_runner] }
            %w[instance_type project_type] | 'finished' | lazy { [job_with_instance_runner, job_with_project_runner] }
            %w[instance_type project_type] | 'pending'  | lazy { [] }
          end

          with_them do
            let(:params) { { runner_type: param_runner_type, scope: param_scope } }

            it { is_expected.to match_array(expected_jobs) }
          end
        end

        context 'with feature flag :admin_jobs_filter_runner_type disabled' do
          before do
            stub_feature_flags(admin_jobs_filter_runner_type: false)
          end

          using RSpec::Parameterized::TableSyntax

          where(:param_runner_type, :param_scope, :expected_jobs) do
            'group_type' | 'running' | lazy do
              [job_with_running_status_and_group_runner, running_job]
            end
            %w[instance_type project_type] | 'finished' | lazy do
              [
                job_with_instance_runner,
                job_with_project_runner,
                successful_job
              ]
            end
            %w[instance_type project_type] | 'pending' | lazy { [pending_job] }
          end

          with_them do
            let(:params) { { runner_type: param_runner_type, scope: param_scope } }

            it { is_expected.to match_array(expected_jobs) }
          end
        end
      end
    end

    context 'with user not being project member' do
      let(:current_user) { user }

      it { is_expected.to be_empty }
    end

    context 'without user' do
      let(:current_user) { nil }

      it { is_expected.to be_empty }
    end
  end

  context 'when project is present' do
    subject { described_class.new(current_user: user, project: project, params: params).execute }

    context 'with user being project maintainer' do
      before do
        project.add_maintainer(user)
      end

      it 'returns jobs for the specified project' do
        expect(subject).to match_array([successful_job])
      end

      context 'when artifacts are present for some jobs' do
        let_it_be(:job_with_artifacts) { create(:ci_build, :success, pipeline: pipeline, name: 'test') }
        let_it_be(:artifact) { create(:ci_job_artifact, job: job_with_artifacts) }

        context 'when with_artifacts is true' do
          let(:params) { { with_artifacts: true } }

          it 'returns only jobs with artifacts' do
            expect(subject).to match_array([job_with_artifacts])
          end
        end

        context 'when with_artifacts is false' do
          let(:params) { { with_artifacts: false } }

          it 'returns all jobs' do
            expect(subject).to match_array([successful_job, job_with_artifacts])
          end
        end

        context "with param `scope" do
          using RSpec::Parameterized::TableSyntax

          where(:param_scope, :expected_jobs) do
            'success'           | lazy { [successful_job, job_with_artifacts] }
            '[success pending]' | lazy { [successful_job, job_with_artifacts] }
            'pending'           | lazy { [] }
            nil                 | lazy { [successful_job, job_with_artifacts] }
          end

          with_them do
            let(:params) { { with_artifacts: false, scope: param_scope } }

            it { is_expected.to match_array(expected_jobs) }
          end
        end
      end
    end

    context 'with user being project guest' do
      before do
        project.add_guest(user)
      end

      it 'returns no jobs' do
        expect(subject).to be_empty
      end
    end

    context 'without user' do
      let(:user) { nil }

      it 'returns no jobs' do
        expect(subject).to be_empty
      end
    end
  end

  context 'when pipeline is present' do
    subject { described_class.new(current_user: user, pipeline: pipeline, params: params).execute }

    context 'with user being project maintainer' do
      before_all do
        project.add_maintainer(user)
        successful_job.update!(retried: true)
      end

      let_it_be(:job_4) { create(:ci_build, :success, pipeline: pipeline, name: 'build') }

      it 'does not return retried jobs by default' do
        expect(subject).to match_array([job_4])
      end

      context 'when include_retried is false' do
        let(:params) { { include_retried: false } }

        it 'does not return retried jobs' do
          expect(subject).to match_array([job_4])
        end
      end

      context 'when include_retried is true' do
        let(:params) { { include_retried: true } }

        it 'returns retried jobs' do
          expect(subject).to match_array([successful_job, job_4])
        end
      end
    end

    context 'without user' do
      let(:user) { nil }

      it 'returns no jobs' do
        expect(subject).to be_empty
      end
    end
  end

  context 'when runner is present' do
    let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) }
    let_it_be(:job_4) { create(:ci_build, :success, runner: runner) }

    subject(:execute) { described_class.new(current_user: user, runner: runner, params: params).execute }

    context 'when current user is an admin' do
      let(:user) { admin }

      context 'when admin mode is enabled', :enable_admin_mode do
        it 'returns jobs for the specified project' do
          expect(subject).to contain_exactly job_4
        end

        context 'with params' do
          using RSpec::Parameterized::TableSyntax

          where(:param_runner_type, :param_scope, :expected_jobs) do
            'project_type'  | 'success' | lazy { [job_4] }
            'instance_type' | nil       | lazy { [] }
            nil             | 'pending' | lazy { [] }
          end

          with_them do
            let(:params) { { runner_type: param_runner_type, scope: param_scope } }

            it { is_expected.to match_array(expected_jobs) }
          end
        end
      end
    end

    context 'with user being project guest' do
      let_it_be(:guest) { create(:user) }

      let(:user) { guest }

      before do
        project.add_guest(guest)
      end

      it 'returns no jobs' do
        expect(subject).to be_empty
      end
    end

    context 'without user' do
      let(:user) { nil }

      it 'returns no jobs' do
        expect(subject).to be_empty
      end
    end

    describe 'when match_compatible_runner_only is true' do
      let_it_be(:owner) { create(:user) }
      let_it_be(:maintainer) { create(:user) }
      let_it_be(:developer) { create(:user) }
      let_it_be(:groups) { create_list(:group, 2, owners: owner, maintainers: maintainer, developers: developer) }
      let_it_be(:projects) { groups.map { |group| create(:project, group: group) } }

      let_it_be(:runner_matching_tags) { %w[tag1 tag2 tag3] }

      let_it_be(:job) do
        create(:ci_build, :pending, name: 'pending_job', project: projects.first,
          tag_list: runner_matching_tags.sample(2))
      end

      let_it_be(:pending_job) { create(:ci_pending_build, project: projects.first, build: job) }

      let_it_be(:different_project_job) do
        create(:ci_build, :pending, project: projects.second, tag_list: runner_matching_tags)
      end

      let_it_be(:different_project_pending_job) do
        create(:ci_pending_build, project: projects.second, build: different_project_job)
      end

      let_it_be(:instance_runner) { create(:ci_runner, tag_list: runner_matching_tags, run_untagged: false) }
      let_it_be(:group_runner) { create(:ci_runner, :group, groups: [groups.first], tag_list: runner_matching_tags) }
      let_it_be(:project_runner) do
        create(:ci_runner, :project, projects: [projects.first], tag_list: runner_matching_tags)
      end

      let(:params) do
        { match_compatible_runner_only: true }
      end

      before_all do
        # Add job with non-matching tags to check for this does not return unrelated jobs
        create(:ci_pending_build, project: projects.first,
          build: create(:ci_build, :pending, project: projects.first, tag_list: %w[tag1 tag4]))
      end

      context 'with instance runner' do
        let(:runner) { instance_runner }

        context 'when user has access to runner' do
          context 'when current user is an admin', :enable_admin_mode do
            let(:user) { admin }

            it 'returns the pending jobs' do
              expect(execute).to contain_exactly(job, different_project_job)
            end
          end
        end

        context 'without user' do
          let(:user) { nil }

          it { is_expected.to be_empty }
        end
      end

      context 'with group runner' do
        let(:runner) { group_runner }

        context 'when user has access to runner' do
          context 'when current user is an admin', :enable_admin_mode do
            let(:user) { admin }

            it 'returns the pending job' do
              expect(execute).to contain_exactly(job)
            end
          end

          context 'when current user is an owner of group' do
            let(:user) { owner }

            it 'returns the pending job' do
              expect(execute).to contain_exactly(job)
            end
          end

          context 'when current user is a maintainer of group' do
            let(:user) { maintainer }

            it { is_expected.to be_empty }
          end
        end

        context 'without user' do
          let(:user) { nil }

          it { is_expected.to be_empty }
        end
      end

      context 'with project runner' do
        let(:runner) { project_runner }

        context 'when user has access to runner' do
          context 'when current user is an admin', :enable_admin_mode do
            let(:user) { admin }

            it 'returns the pending job' do
              expect(execute).to contain_exactly(job)
            end
          end

          context 'when current user is an maintainer of group' do
            let(:user) { maintainer }

            it 'returns the pending job' do
              expect(execute).to contain_exactly(job)
            end
          end

          context 'when current user is a developer of group' do
            let(:user) { developer }

            it { is_expected.to be_empty }
          end
        end

        context 'without user' do
          let(:user) { nil }

          it { is_expected.to be_empty }
        end
      end
    end
  end
end
