# frozen_string_literal: true

require 'spec_helper'

describe AuthorizedProjectUpdate::ProjectCreateService do
  let_it_be(:group_parent) { create(:group, :private) }
  let_it_be(:group) { create(:group, :private, parent: group_parent) }
  let_it_be(:group_child) { create(:group, :private, parent: group) }

  let_it_be(:group_project) { create(:project, group: group) }

  let_it_be(:parent_group_user) { create(:user) }
  let_it_be(:group_user) { create(:user) }
  let_it_be(:child_group_user) { create(:user) }

  let(:access_level) { Gitlab::Access::MAINTAINER }

  subject(:service) { described_class.new(group_project) }

  describe '#perform' do
    context 'direct group members' do
      before do
        create(:group_member, access_level: access_level, group: group, user: group_user)
        ProjectAuthorization.delete_all
      end

      it 'creates project authorization' do
        expect { service.execute }.to(
          change { ProjectAuthorization.count }.from(0).to(1))

        project_authorization = ProjectAuthorization.where(
          project_id: group_project.id,
          user_id: group_user.id,
          access_level: access_level)

        expect(project_authorization).to exist
      end
    end

    context 'inherited group members' do
      before do
        create(:group_member, access_level: access_level, group: group_parent, user: parent_group_user)
        ProjectAuthorization.delete_all
      end

      it 'creates project authorization' do
        expect { service.execute }.to(
          change { ProjectAuthorization.count }.from(0).to(1))

        project_authorization = ProjectAuthorization.where(
          project_id: group_project.id,
          user_id: parent_group_user.id,
          access_level: access_level)
        expect(project_authorization).to exist
      end
    end

    context 'membership overrides' do
      before do
        create(:group_member, access_level: Gitlab::Access::REPORTER, group: group_parent, user: group_user)
        create(:group_member, access_level: Gitlab::Access::DEVELOPER, group: group, user: group_user)
        ProjectAuthorization.delete_all
      end

      it 'creates project authorization' do
        expect { service.execute }.to(
          change { ProjectAuthorization.count }.from(0).to(1))

        project_authorization = ProjectAuthorization.where(
          project_id: group_project.id,
          user_id: group_user.id,
          access_level: Gitlab::Access::DEVELOPER)
        expect(project_authorization).to exist
      end
    end

    context 'no group member' do
      it 'does not create project authorization' do
        expect { service.execute }.not_to(
          change { ProjectAuthorization.count }.from(0))
      end
    end

    context 'unapproved access requests' do
      before do
        create(:group_member, :guest, :access_request, user: group_user, group: group)
      end

      it 'does not create project authorization' do
        expect { service.execute }.not_to(
          change { ProjectAuthorization.count }.from(0))
      end
    end

    context 'project has more user than BATCH_SIZE' do
      let(:batch_size) { 2 }
      let(:users) { create_list(:user, batch_size + 1 ) }

      before do
        stub_const("#{described_class.name}::BATCH_SIZE", batch_size)

        users.each do |user|
          create(:group_member, access_level: access_level, group: group_parent, user: user)
        end

        ProjectAuthorization.delete_all
      end

      it 'bulk creates project authorizations in batches' do
        users.each_slice(batch_size) do |batch|
          attributes = batch.map do |user|
            { user_id: user.id, project_id: group_project.id, access_level: access_level }
          end

          expect(ProjectAuthorization).to(
            receive(:insert_all).with(array_including(attributes)).and_call_original)
        end

        expect { service.execute }.to(
          change { ProjectAuthorization.count }.from(0).to(batch_size + 1))
      end
    end

    context 'ignores existing project authorizations' do
      before do
        # ProjectAuthorizations is also created because of an after_commit
        # callback on Member model
        create(:group_member, access_level: access_level, group: group, user: group_user)
      end

      it 'does not create project authorization' do
        project_authorization = ProjectAuthorization.where(
          project_id: group_project.id,
          user_id: group_user.id,
          access_level: access_level)

        expect { service.execute }.not_to(
          change { project_authorization.reload.exists? }.from(true))
      end
    end
  end
end
