# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Ci::DropPipelinesAndDisableSchedulesForUserService, feature_category: :continuous_integration do
  describe '#execute' do
    let_it_be(:user) { create(:user) }

    let_it_be(:user_personal_projects) { create_list(:project, 2, :repository, namespace: user.namespace) }

    let_it_be(:group) do
      create(:group) do |group|
        group.add_owner(user)
      end
    end

    let_it_be(:subgroup) { create(:group, parent: group) }

    let_it_be(:other_user) do
      create(:user) do |new_user|
        create(:group_member, :maintainer, user: new_user, group: group)
        create(:group_member, :maintainer, user: new_user, group: subgroup)

        user_personal_projects.each do |project|
          create(:project_member, :maintainer, user: new_user, project: project)
        end
      end
    end

    let_it_be(:group_projects) { create_list(:project, 2, :repository, namespace: group) }
    let_it_be(:subgroup_projects) do
      create_list(:project, 2, :repository, namespace: subgroup)
    end

    let_it_be(:other_user_personal_projects) { create_list(:project, 2, :repository, namespace: other_user.namespace) }

    subject(:service) { described_class.new.execute(user) }

    context 'when user owns pipelines/schedules and groups with other users also owning pipelines/schedules' do
      # Pipelines/pipeline schedules owned by the user in their personal, group and descendent group projects
      let_it_be_with_reload(:user_owned_pipelines) do
        [user_personal_projects, group_projects, subgroup_projects].flat_map do |projects|
          projects.flat_map do |project|
            create_list(:ci_pipeline, 2, :running, project: project, user: user) do |pipeline|
              create(:ci_build, :running, pipeline: pipeline)
              create(:commit_status, :running, pipeline: pipeline)
            end
          end
        end
      end

      let_it_be_with_reload(:user_owned_schedules) do
        [user_personal_projects, group_projects, subgroup_projects].flat_map do |projects|
          projects.flat_map do |project|
            create_list(:ci_pipeline_schedule, 2, active: true, project: project, owner: user)
          end
        end
      end

      # Pipelines/pipeline schedules owned by another user in user personal projects and group and descendant group
      # projects owned by the user
      let_it_be_with_reload(:other_user_owned_group_project_pipelines) do
        [user_personal_projects, group_projects, subgroup_projects].flat_map do |projects|
          projects.flat_map do |project|
            create_list(:ci_pipeline, 2, :running, project: project, user: other_user) do |pipeline|
              create(:ci_build, :running, pipeline: pipeline)
              create(:commit_status, :running, pipeline: pipeline)
            end
          end
        end
      end

      let_it_be_with_reload(:other_user_owned_group_project_schedules) do
        [user_personal_projects, group_projects, subgroup_projects].flat_map do |projects|
          projects.flat_map do |project|
            create_list(:ci_pipeline_schedule, 2, active: true, project: project, owner: other_user)
          end
        end
      end

      # Pipelines/pipeline schedules owned by another user in their personal projects (should never be changed)
      let_it_be_with_reload(:other_user_owned_personal_pipelines) do
        other_user_personal_projects.flat_map do |project|
          create_list(:ci_pipeline, 2, :running, project: project, user: other_user) do |pipeline|
            create(:ci_build, :running, pipeline: pipeline)
            create(:commit_status, :running, pipeline: pipeline)
          end
        end
      end

      let_it_be_with_reload(:other_user_owned_personal_schedules) do
        other_user_personal_projects.flat_map do |project|
          create_list(:ci_pipeline_schedule, 2, active: true, project: project, owner: other_user)
        end
      end

      it 'drops running pipelines/disabled active schedules owned by user', :sidekiq_inline do
        expect { service }.to change {
                                user_owned_pipelines.map(&:reload).map(&:status).uniq
                              }
                              .from(["running"])
                              .to(["failed"])
                          .and change {
                                 user_owned_schedules.map(&:reload).map(&:active?).uniq
                               }
                              .from([true])
                              .to([false])
                          .and not_change {
                                 [
                                   other_user_owned_group_project_pipelines,
                                   other_user_owned_personal_pipelines
                                 ].flatten.map(&:reload).map(&:status).uniq
                               }
                          .and not_change {
                                 [
                                   other_user_owned_group_project_schedules,
                                   other_user_owned_personal_schedules
                                 ].flatten.map(&:reload).map(&:active?).uniq
                               }
      end

      it 'avoids N+1 queries when reading data' do
        notification_service = instance_double(NotificationService)
        allow(NotificationService).to receive(:new).and_return(notification_service)
        allow(notification_service).to receive(:pipeline_schedule_owner_unavailable)

        control_count = ActiveRecord::QueryRecorder.new do
          described_class.new.execute(user)
        end.count

        extra_projects = create_list(:project, 2, :repository, namespace: group)

        [extra_projects, user_personal_projects, group_projects, subgroup_projects].flat_map do |projects|
          projects.flat_map do |project|
            create_list(:ci_pipeline, 2, :running, project: project, user: user) do |pipeline|
              create(:ci_build, :running, pipeline: pipeline)
              create(:commit_status, :running, pipeline: pipeline)
            end
            create_list(:ci_pipeline_schedule, 2, active: true, project: project, owner: user)
          end
        end

        expect do
          described_class.new.execute(user)
        end.not_to exceed_query_limit(control_count)
      end

      context "with notifications" do
        let(:notification_service) { instance_double(NotificationService) }

        before do
          allow(NotificationService).to receive(:new).and_return(notification_service)
          allow(notification_service).to receive(:pipeline_schedule_owner_unavailable)
        end

        it 'sends pipeline_schedule_owner_unavailable notification for each user owned schedule' do
          expect(notification_service).to receive(:pipeline_schedule_owner_unavailable)
            .exactly(user_owned_schedules.count).times
          service
        end
      end

      context 'when include_owned_projects_and_groups is true' do
        subject(:service) { described_class.new.execute(user, include_owned_projects_and_groups: true) }

        it 'drops running pipelines/disabled active schedules owned by user and/or in their owned groups/descendants',
          :sidekiq_inline do
          expect { service }.to change {
                                  [
                                    user_owned_pipelines,
                                    other_user_owned_group_project_pipelines
                                  ].flatten.map(&:reload).map(&:status).uniq
                                }
                                .from(["running"])
                                .to(["failed"])
                            .and change {
                                   [
                                     user_owned_schedules,
                                     other_user_owned_group_project_schedules
                                   ].flatten.map(&:reload).map(&:active?).uniq
                                 }
                                .from([true])
                                .to([false])
                            .and not_change {
                                   other_user_owned_personal_pipelines.map(&:reload).map(&:active?).uniq
                                 }
                            .and not_change {
                                   other_user_owned_personal_schedules.map(&:reload).map(&:active?).uniq
                                 }
        end

        it 'avoids N+1 queries when reading data' do
          control_count = ActiveRecord::QueryRecorder.new do
            described_class.new.execute(user, include_owned_projects_and_groups: true)
          end.count

          extra_projects = create_list(:project, 2, :repository, namespace: group)

          [user_personal_projects, extra_projects, group_projects, subgroup_projects].flat_map do |projects|
            projects.flat_map do |project|
              create_list(:ci_pipeline, 2, :running, project: project, user: user) do |pipeline|
                create(:ci_build, :running, pipeline: pipeline)
                create(:commit_status, :running, pipeline: pipeline)
              end
              create_list(:ci_pipeline_schedule, 2, active: true, project: project, owner: user)
            end
          end

          [extra_projects, group_projects, subgroup_projects].flat_map do |projects|
            projects.flat_map do |project|
              create_list(:ci_pipeline, 2, :running, project: project, user: other_user) do |pipeline|
                create(:ci_build, :running, pipeline: pipeline)
                create(:commit_status, :running, pipeline: pipeline)
              end
              create_list(:ci_pipeline_schedule, 2, active: true, project: project, owner: other_user)
            end
          end

          expect do
            described_class.new.execute(user, include_owned_projects_and_groups: true)
          end.not_to exceed_query_limit(control_count)
        end
      end
    end
  end
end
