# frozen_string_literal: true

require 'spec_helper'

RSpec.describe GroupPolicy, feature_category: :groups_and_projects do
  include AdminModeHelper
  include LoginHelpers

  using RSpec::Parameterized::TableSyntax

  def stub_group_saml_config(enabled)
    allow(::Gitlab::Auth::GroupSaml::Config).to receive_messages(enabled?: enabled)
  end

  include_context 'GroupPolicy context'
  # Can't move to GroupPolicy context because auditor trait is not present
  # outside of EE context and FOSS will fail on this
  let_it_be(:auditor) { create(:user, :auditor) }

  let(:epic_rules) do
    %i[read_epic create_epic admin_epic destroy_epic read_confidential_epic
       read_epic_board read_epic_board_list admin_epic_board
       admin_epic_board_list]
  end

  let(:auditor_permissions) do
    %i[
      read_group
      read_group_security_dashboard
      read_cluster
      read_group_runners
      read_billing
      read_container_image
      read_confidential_issues
      read_cycle_analytics
    ]
  end

  context 'when epics feature is disabled' do
    let(:current_user) { owner }

    it { is_expected.to be_disallowed(*epic_rules) }
  end

  context 'when epics feature is enabled' do
    before do
      stub_licensed_features(epics: true)
    end

    context 'when user is owner' do
      let(:current_user) { owner }

      it { is_expected.to be_allowed(*epic_rules) }
    end

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

      context 'when admin mode is enabled', :enable_admin_mode do
        it { is_expected.to be_allowed(*epic_rules) }
      end

      context 'when admin mode is disabled' do
        it { is_expected.to be_disallowed(*epic_rules) }
      end
    end

    context 'when user is maintainer' do
      let(:current_user) { maintainer }

      it { is_expected.to be_allowed(*(epic_rules - [:destroy_epic])) }
      it { is_expected.to be_disallowed(:destroy_epic) }
    end

    context 'when user is developer' do
      let(:current_user) { developer }

      it { is_expected.to be_allowed(*(epic_rules - [:destroy_epic])) }
      it { is_expected.to be_disallowed(:destroy_epic) }
    end

    context 'when user is reporter' do
      let(:current_user) { reporter }

      it { is_expected.to be_allowed(*(epic_rules - [:destroy_epic])) }
      it { is_expected.to be_disallowed(:destroy_epic) }
    end

    context 'when user is planner' do
      let(:current_user) { planner }

      it { is_expected.to be_allowed(*epic_rules) }
    end

    context 'when user is guest' do
      let(:current_user) { guest }

      it { is_expected.to be_allowed(:read_epic, :read_epic_board, :list_subgroup_epics) }
      it { is_expected.to be_disallowed(*(epic_rules - [:read_epic, :read_epic_board, :read_epic_board_list])) }
    end

    context 'when user is support bot' do
      let_it_be(:current_user) { Users::Internal.support_bot }

      before do
        allow(::ServiceDesk).to receive(:supported?).and_return(true)
      end

      context 'when group has at least one project with service desk enabled' do
        let_it_be(:project_with_service_desk) do
          create(:project, group: group, service_desk_enabled: true)
        end

        it { is_expected.to be_allowed(:read_epic, :read_epic_iid) }
        it { is_expected.to be_disallowed(*(epic_rules - [:read_epic, :read_epic_iid])) }
      end

      context 'when group does not have projects with service desk enabled' do
        let_it_be(:project_without_service_desk) do
          create(:project, group: group, service_desk_enabled: false)
        end

        it { is_expected.to be_disallowed(*epic_rules) }
      end
    end

    context 'when user is not member' do
      let(:current_user) { create(:user) }

      it { is_expected.to be_disallowed(*epic_rules) }
    end

    context 'when user is anonymous' do
      let(:current_user) { nil }

      it { is_expected.to be_disallowed(*epic_rules) }
    end
  end

  context 'when iterations feature is disabled' do
    let(:current_user) { owner }

    before do
      stub_licensed_features(iterations: false)
    end

    it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration, :create_iteration_cadence, :admin_iteration_cadence) }
  end

  context 'when iterations feature is enabled' do
    let(:read_actions) { [:read_iteration, :read_iteration_cadence] }
    let(:edit_actions) { [:create_iteration, :admin_iteration, :create_iteration_cadence, :admin_iteration_cadence] }

    before do
      stub_licensed_features(iterations: true)
    end

    where(:role, :actions, :allowed) do
      :none     | ref(:read_actions) | false
      :none     | ref(:edit_actions) | false
      :guest    | ref(:read_actions) | true
      :guest    | ref(:edit_actions) | false
      :planner  | ref(:read_actions) | true
      :planner  | ref(:edit_actions) | true
      :reporter | ref(:read_actions) | true
      :reporter | ref(:edit_actions) | true
    end

    with_them do
      let(:current_user) { try(role) }

      it { is_expected.to(allowed ? be_allowed(*actions) : be_disallowed(*actions)) }
    end

    context 'when project is public' do
      let(:group) { create(:group, :public, :owner_subgroup_creation_only) }

      context 'when user is logged out' do
        let(:current_user) { nil }

        it { is_expected.to be_allowed(:read_iteration, :read_iteration_cadence) }
        it { is_expected.to be_disallowed(:create_iteration, :admin_iteration, :create_iteration_cadence, :admin_iteration_cadence) }
      end
    end
  end

  context 'when custom fields are available' do
    before do
      stub_licensed_features(custom_fields: true)
    end

    context 'when user is a guest' do
      let(:current_user) { guest }

      it { is_expected.to be_allowed(:read_custom_field) }
      it { is_expected.to be_disallowed(:admin_custom_field) }
    end

    context 'when user is a maintainer' do
      let(:current_user) { maintainer }

      it { is_expected.to be_allowed(:read_custom_field, :admin_custom_field) }
    end

    context 'when user is logged out' do
      let(:current_user) { nil }

      it { is_expected.to be_disallowed(:read_custom_field) }
    end
  end

  context 'when custom fields are not available' do
    let(:current_user) { guest }

    before do
      stub_licensed_features(custom_fields: false)
    end

    it { is_expected.to be_disallowed(:read_custom_field, :admin_custom_field) }
  end

  context 'when cluster deployments is available' do
    let(:current_user) { maintainer }

    before do
      stub_licensed_features(cluster_deployments: true)
    end

    it { is_expected.to be_allowed(:read_cluster_environments) }
  end

  context 'when cluster deployments is not available' do
    let(:current_user) { maintainer }

    before do
      stub_licensed_features(cluster_deployments: false)
    end

    it { is_expected.not_to be_allowed(:read_cluster_environments) }
  end

  describe 'invite_group_members policy' do
    context 'when on saas', :saas do
      let(:policy) { :invite_group_members }
      let(:app_setting) { :disable_invite_members }

      before do
        stub_saas_features(group_disable_invite_members: true)
      end

      context 'with disable_invite_members is available in license' do
        where(:role, :group_setting, :application_setting, :allowed) do
          :guest      | true | true | false
          :planner    | true | true | false
          :reporter   | true | true | false
          :developer  | true | true | false
          :maintainer | false | true | false
          :maintainer | true | true | false
          :owner      | false | true | true
          :owner      | false | false | true
          :owner      | true  | true | false
          :owner      | true  | false | false
          :admin      | false | true |  true
          :admin      | false | false | true
          :admin      | false | true | true
          :admin      | false | false | true
        end

        with_them do
          let(:current_user) { public_send(role) }

          before do
            stub_licensed_features(disable_invite_members: true)
            stub_application_setting(app_setting => application_setting)
            allow(group).to receive(:disable_invite_members?).and_return(group_setting)
            enable_admin_mode!(current_user) if role == :admin
          end

          it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
        end
      end

      context 'with disable_invite_members not available in license' do
        where(:role, :group_setting, :application_setting, :allowed) do
          :guest      | true | true | false
          :planner    | true | true | false
          :reporter   | true | true | false
          :developer  | true | true | false
          :maintainer | false | true |  false
          :maintainer | true | true   | false
          :owner      | false  | true | true
          :owner      | false  | false | true
          :owner      | true   | false | true
          :owner      | true   | true | true
          :admin      | false  | true | true
          :admin      | true | false | true
        end

        with_them do
          let(:current_user) { public_send(role) }

          before do
            stub_licensed_features(disable_invite_members: false)
            stub_application_setting(app_setting => application_setting)
            allow(group).to receive(:disable_invite_members?).and_return(group_setting)
            enable_admin_mode!(current_user) if role == :admin
          end

          it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
        end
      end
    end

    context 'when self-managed' do
      before do
        allow(Gitlab).to receive(:com?).and_return(false)
      end

      let(:app_setting) { :disable_invite_members }
      let(:policy) { :invite_group_members }

      context 'with disable_invite_members available in license' do
        where(:role, :setting, :admin_mode, :allowed) do
          :guest      | true  | nil    | false
          :planner    | true  | nil    | false
          :reporter   | true  | nil    | false
          :developer  | true  | nil    | false
          :maintainer | false | nil    | false
          :maintainer | true  | nil    | false
          :owner      | false | nil    | true
          :owner      | true  | nil    | false
          :admin      | false | false  | false
          :admin      | false | true   | true
          :admin      | true  | false  | false
        end

        with_them do
          let(:current_user) { public_send(role) }

          before do
            stub_licensed_features(disable_invite_members: true)
            stub_application_setting(app_setting => setting)
            enable_admin_mode!(current_user) if admin_mode
          end

          it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
        end
      end

      context 'with disable_invite_members not available in license' do
        where(:role, :setting, :admin_mode, :allowed) do
          :guest      | true  | nil    | false
          :planner    | true  | nil    | false
          :reporter   | true  | nil    | false
          :developer  | true  | nil    | false
          :maintainer | false | nil    | false
          :maintainer | true  | nil    | false
          :owner      | false | nil    | true
          :owner      | true  | nil    | true
          :admin      | false | false  | false
          :admin      | false | true   | true
          :admin      | true  | false  | false
          :admin      | true  | true   | true
        end

        with_them do
          let(:current_user) { public_send(role) }

          before do
            stub_licensed_features(disable_invite_members: false)
            stub_application_setting(app_setting => setting)
            enable_admin_mode!(current_user) if admin_mode
          end

          it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
        end
      end
    end
  end

  describe 'modify_value_stream_dashboard_settings policy' do
    context 'when analytics dashboard is available' do
      let(:current_user) { maintainer }

      before do
        stub_licensed_features(group_level_analytics_dashboard: true)
      end

      it { is_expected.to be_allowed(:modify_value_stream_dashboard_settings) }

      context 'when the current user is the owner' do
        let(:current_user) { owner }

        it { is_expected.to be_allowed(:modify_value_stream_dashboard_settings) }
      end

      context 'when the current user is admin', :enable_admin_mode do
        let(:current_user) { admin }

        it { is_expected.to be_allowed(:modify_value_stream_dashboard_settings) }
      end

      context 'when a sub-group is given' do
        let(:sub_group) { create(:group, :private, parent: group) }

        subject { described_class.new(maintainer, sub_group) }

        it { is_expected.not_to be_allowed(:modify_value_stream_dashboard_settings) }
      end

      context 'when the user is not a maintainer' do
        let(:current_user) { developer }

        it { is_expected.not_to be_allowed(:modify_value_stream_dashboard_settings) }
      end
    end

    context 'when analytics dashboard is not available' do
      let(:current_user) { maintainer }

      before do
        stub_licensed_features(group_level_analytics_dashboard: false)
      end

      it { is_expected.not_to be_allowed(:modify_value_stream_dashboard_settings) }
    end
  end

  context 'when contribution analytics is available' do
    let(:current_user) { developer }

    before do
      stub_licensed_features(contribution_analytics: true)
    end

    context 'when signed in user is a member of the group' do
      it { is_expected.to be_allowed(:read_group_contribution_analytics) }
    end

    describe 'when user is not a member of the group' do
      let(:current_user) { non_group_member }
      let(:private_group) { create(:group, :private) }

      subject { described_class.new(non_group_member, private_group) }

      context 'when user is not invited to any of the group projects' do
        it { is_expected.not_to be_allowed(:read_group_contribution_analytics) }
      end

      context 'when user is invited to a group project, but not to the group' do
        let(:private_project) { create(:project, :private, group: private_group) }

        before do
          private_project.add_guest(non_group_member)
        end

        it { is_expected.not_to be_allowed(:read_group_contribution_analytics) }
      end

      context 'when user has an auditor role' do
        before do
          allow(current_user).to receive(:auditor?).and_return(true)
        end

        it { is_expected.to be_allowed(:read_group_contribution_analytics) }
      end
    end
  end

  context 'when contribution analytics is not available' do
    let(:current_user) { developer }

    before do
      stub_licensed_features(contribution_analytics: false)
    end

    it { is_expected.not_to be_allowed(:read_group_contribution_analytics) }
  end

  context 'when dora4 analytics is available' do
    before do
      stub_licensed_features(dora4_analytics: true)
    end

    context 'when the user is a developer' do
      let(:current_user) { developer }

      it { is_expected.to be_allowed(:read_dora4_analytics) }
    end

    context 'when the user is an auditor' do
      let(:current_user) { auditor }

      it { is_expected.to be_allowed(:read_dora4_analytics) }
    end

    context 'when the user is an admin', :enable_admin_mode do
      let(:current_user) { admin }

      it { is_expected.to be_allowed(:read_dora4_analytics) }
    end
  end

  context 'when dora4 analytics is not available' do
    let(:current_user) { developer }

    before do
      stub_licensed_features(dora4_analytics: false)
    end

    it { is_expected.not_to be_allowed(:read_dora4_analytics) }
  end

  describe ':read_product_analytics', :enable_admin_mode do
    where(:role, :allowed) do
      :guest     | false
      :planner   | false
      :reporter  | true
      :developer | true
      :admin     | true
    end

    with_them do
      let(:current_user) { public_send(role) }

      it { is_expected.to(allowed ? be_allowed(:read_product_analytics) : be_disallowed(:read_product_analytics)) }
    end
  end

  describe ':read_enterprise_ai_analytics' do
    context 'when on SAAS', :saas do
      let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, namespace: group) }

      it_behaves_like 'ai permission to', :read_enterprise_ai_analytics
    end

    context 'when on self-managed' do
      let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, :self_managed) }

      it_behaves_like 'ai permission to', :read_enterprise_ai_analytics
    end
  end

  describe ':read_pro_ai_analytics' do
    context 'when on SAAS', :saas do
      context 'with pro subscription' do
        let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_pro, namespace: group) }

        it_behaves_like 'ai permission to', :read_pro_ai_analytics
      end

      context 'with enterprise subscription' do
        let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, namespace: group) }

        it_behaves_like 'ai permission to', :read_pro_ai_analytics
      end
    end

    context 'when on self-managed' do
      context 'with pro subscription' do
        let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_pro, :self_managed) }

        it_behaves_like 'ai permission to', :read_pro_ai_analytics
      end

      context 'with enterprise subscription' do
        let(:subscription_purchase) { create(:gitlab_subscription_add_on_purchase, :duo_enterprise, :self_managed) }

        it_behaves_like 'ai permission to', :read_pro_ai_analytics
      end
    end
  end

  describe 'analytics value streams' do
    context 'when feature is not available' do
      context 'and user is admin', :enable_admin_mode do
        let(:current_user) { admin }

        it { is_expected.not_to be_allowed(:admin_value_stream) }
      end

      context 'and user is reporter' do
        let(:current_user) { reporter }

        it { is_expected.not_to be_allowed(:admin_value_stream) }
      end
    end

    context 'when user feature is available' do
      before do
        stub_licensed_features(cycle_analytics_for_groups: true)
      end

      context 'and user is guest' do
        let(:current_user) { guest }

        it { is_expected.not_to be_allowed(:admin_value_stream) }
      end

      context 'and user is reporter' do
        let(:current_user) { reporter }

        it { is_expected.to be_allowed(:admin_value_stream) }
      end

      context 'and user is admin', :enable_admin_mode do
        let(:current_user) { admin }

        it { is_expected.to be_allowed(:admin_value_stream) }
      end
    end
  end

  context 'export group memberships' do
    let(:current_user) { owner }

    context 'when exporting user permissions is not available' do
      before do
        stub_licensed_features(export_user_permissions: false)
      end

      it { is_expected.not_to be_allowed(:export_group_memberships) }
    end

    context 'when exporting user permissions is available' do
      before do
        stub_licensed_features(export_user_permissions: true)
      end

      it { is_expected.to be_allowed(:export_group_memberships) }
    end
  end

  context 'when group activity analytics is available' do
    let(:current_user) { developer }

    before do
      stub_licensed_features(group_activity_analytics: true)
    end

    it { is_expected.to be_allowed(:read_group_activity_analytics) }
  end

  context 'when group activity analytics is not available' do
    let(:current_user) { developer }

    before do
      stub_licensed_features(group_activity_analytics: false)
    end

    it { is_expected.not_to be_allowed(:read_group_activity_analytics) }
  end

  context 'group CI/CD analytics' do
    context 'when group CI/CD analytics is available' do
      before do
        stub_licensed_features(group_ci_cd_analytics: true)
      end

      context 'when the user has at least reporter permissions' do
        let(:current_user) { reporter }

        it { is_expected.to be_allowed(:view_group_ci_cd_analytics) }
      end

      context 'when the user has less than reporter permissions' do
        let(:current_user) { guest }

        it { is_expected.not_to be_allowed(:view_group_ci_cd_analytics) }
      end

      context 'when the user has auditor permissions' do
        let(:current_user) { auditor }

        it { is_expected.to be_allowed(:view_group_ci_cd_analytics) }
      end
    end

    context 'when group CI/CD analytics is not available' do
      let(:current_user) { reporter }

      before do
        stub_licensed_features(group_ci_cd_analytics: false)
      end

      it { is_expected.not_to be_allowed(:view_group_ci_cd_analytics) }
    end
  end

  context 'when group repository analytics is available' do
    before do
      stub_licensed_features(group_repository_analytics: true)
    end

    context 'for guests' do
      let(:current_user) { guest }

      it { is_expected.not_to be_allowed(:read_group_repository_analytics) }
    end

    context 'for reporter+' do
      let(:current_user) { reporter }

      it { is_expected.to be_allowed(:read_group_repository_analytics) }
    end

    context 'for auditor' do
      let(:current_user) { auditor }

      it { is_expected.to be_allowed(:read_group_repository_analytics) }
    end
  end

  context 'when group repository analytics is not available' do
    let(:current_user) { maintainer }

    before do
      stub_licensed_features(group_repository_analytics: false)
    end

    it { is_expected.not_to be_allowed(:read_group_repository_analytics) }
  end

  context 'when group cycle analytics is available' do
    before do
      stub_licensed_features(cycle_analytics_for_groups: true)
    end

    context 'for guests' do
      let(:current_user) { guest }

      it { is_expected.not_to be_allowed(:read_cycle_analytics) }
      it { is_expected.not_to be_allowed(:read_group_stage) }
      it { is_expected.not_to be_allowed(:view_type_of_work_charts) }
    end

    context 'for reporter+' do
      let(:current_user) { reporter }

      it { is_expected.to be_allowed(:read_cycle_analytics) }
      it { is_expected.to be_allowed(:read_group_stage) }
      it { is_expected.to be_allowed(:view_type_of_work_charts) }
    end

    context 'for auditor' do
      let(:current_user) { auditor }

      it { is_expected.to be_allowed(:read_cycle_analytics) }
      it { is_expected.to be_allowed(:read_group_stage) }
      it { is_expected.to be_allowed(:view_type_of_work_charts) }
    end
  end

  context 'when group cycle analytics is not available' do
    let(:current_user) { maintainer }

    before do
      stub_licensed_features(cycle_analytics_for_groups: false)
    end

    it { is_expected.not_to be_allowed(:read_cycle_analytics) }
  end

  context 'when group coverage reports is available' do
    before do
      stub_licensed_features(group_coverage_reports: true)
    end

    context 'for guests' do
      let(:current_user) { guest }

      it { is_expected.not_to be_allowed(:read_group_coverage_reports) }
    end

    context 'for reporter+' do
      let(:current_user) { reporter }

      it { is_expected.to be_allowed(:read_group_coverage_reports) }
    end
  end

  context 'when group coverage reports is not available' do
    let(:current_user) { maintainer }

    before do
      stub_licensed_features(group_coverage_reports: false)
    end

    it { is_expected.not_to be_allowed(:read_group_coverage_reports) }
  end

  describe 'per group SAML' do
    context 'when group_saml is unavailable' do
      let(:current_user) { owner }

      context 'when group saml config is disabled' do
        before do
          stub_group_saml_config(false)
        end

        it { is_expected.to be_disallowed(:admin_group_saml) }
      end

      context 'when the group is a subgroup' do
        let_it_be(:subgroup) { create(:group, :private, parent: group) }

        before do
          stub_group_saml_config(true)
        end

        subject { described_class.new(current_user, subgroup) }

        it { is_expected.to be_disallowed(:admin_group_saml) }
      end

      context 'when the feature is not licensed' do
        before do
          stub_group_saml_config(true)
          stub_licensed_features(group_saml: false)
        end

        it { is_expected.to be_disallowed(:admin_group_saml) }
      end
    end

    context 'when group_saml is available' do
      before do
        stub_licensed_features(group_saml: true)
      end

      context 'when group_saml_group_sync is not licensed' do
        context 'with an enabled SAML provider' do
          let_it_be(:saml_provider) { create(:saml_provider, group: group, enabled: true) }

          context 'owner' do
            let(:current_user) { owner }

            it { is_expected.to be_disallowed(:admin_saml_group_links) }
          end

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

            it 'is disallowed even with admin mode', :enable_admin_mode do
              is_expected.to be_disallowed(:admin_saml_group_links)
            end
          end
        end
      end

      context 'when group_saml_group_sync is licensed', :saas do
        before do
          stub_group_saml_config(true)
          stub_application_setting(check_namespace_plan: true)
        end

        before_all do
          create(:license, plan: License::ULTIMATE_PLAN)
          create(:gitlab_subscription, :premium, namespace: group)
        end

        context 'without an enabled SAML provider' do
          context 'maintainer' do
            let(:current_user) { maintainer }

            it { is_expected.to be_disallowed(:admin_group_saml) }
            it { is_expected.to be_disallowed(:admin_saml_group_links) }
          end

          context 'owner' do
            let(:current_user) { owner }

            it { is_expected.to be_allowed(:admin_group_saml) }
            it { is_expected.to be_disallowed(:admin_saml_group_links) }
          end

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

            context 'when admin mode is enabled', :enable_admin_mode do
              it { is_expected.to be_allowed(:admin_group_saml) }
              it { is_expected.to be_disallowed(:admin_saml_group_links) }
            end

            context 'when admin mode is disabled' do
              it { is_expected.to be_disallowed(:admin_group_saml) }
              it { is_expected.to be_disallowed(:admin_saml_group_links) }
            end
          end
        end

        context 'with an enabled SAML provider' do
          let_it_be(:saml_provider) { create(:saml_provider, group: group, enabled: true) }

          context 'maintainer' do
            let(:current_user) { maintainer }

            it { is_expected.to be_disallowed(:admin_saml_group_links) }
          end

          context 'owner' do
            let(:current_user) { owner }

            it { is_expected.to be_allowed(:admin_saml_group_links) }
          end

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

            context 'when admin mode is enabled', :enable_admin_mode do
              it { is_expected.to be_allowed(:admin_saml_group_links) }
            end

            context 'when admin mode is disabled' do
              it { is_expected.to be_disallowed(:admin_saml_group_links) }
            end
          end

          context 'when the group is a subgroup' do
            let_it_be(:subgroup) { create(:group, :private, parent: group) }

            let(:current_user) { owner }

            subject { described_class.new(current_user, subgroup) }

            it { is_expected.to be_allowed(:admin_saml_group_links) }
          end
        end
      end

      context 'with SSO enforcement enabled' do
        let(:current_user) { guest }

        let_it_be(:saml_provider) { create(:saml_provider, group: group, enforced_sso: true) }

        context 'when in context of the user web activity' do
          around do |example|
            session = {}

            session['warden.user.user.key'] = [[current_user.id], current_user.authenticatable_salt]

            Gitlab::Session.with_session(session) do
              example.run
            end
          end

          it 'prevents access without a SAML session' do
            is_expected.not_to be_allowed(:read_group)
          end

          it 'allows access with a SAML session' do
            Gitlab::Auth::GroupSaml::SsoEnforcer.new(saml_provider).update_session

            is_expected.to be_allowed(:read_group)
          end
        end

        context 'when there is no global session or sso state' do
          it "allows access because we haven't yet restricted all use cases" do
            is_expected.to be_allowed(:read_group)
          end

          context 'when the current user is a deploy token' do
            let(:current_user) { create(:deploy_token, :group, groups: [group], read_package_registry: true) }

            it 'allows access without a SAML session' do
              is_expected.to allow_action(:read_group)
            end
          end
        end
      end

      context 'without SSO enforcement enabled' do
        let(:current_user) { guest }

        let_it_be(:saml_provider) { create(:saml_provider, group: group, enforced_sso: false) }

        context 'when in context of the user web activity' do
          around do |example|
            session = {}

            session['warden.user.user.key'] = [[current_user.id], current_user.authenticatable_salt]

            Gitlab::Session.with_session(session) do
              example.run
            end
          end

          it 'allows access when the user has no Group SAML identity' do
            is_expected.to be_allowed(:read_group)
          end
        end

        context 'when there is no global session or sso state' do
          context 'when the current user is a deploy token' do
            let(:current_user) { create(:deploy_token, :group, groups: [group], read_package_registry: true) }

            it 'allows access without a SAML session' do
              is_expected.to allow_action(:read_group)
            end
          end
        end
      end
    end

    context 'reading a group' do
      context 'when SAML SSO is enabled for resource' do
        let(:saml_provider) { create(:saml_provider, enabled: true, enforced_sso: false) }
        let(:identity) { create(:group_saml_identity, saml_provider: saml_provider) }
        let(:root_group) { saml_provider.group }
        let(:subgroup) { create(:group, parent: root_group) }
        let(:member_with_identity) { identity.user }
        let(:member_without_identity) { create(:user) }
        let(:non_member) { create(:user) }
        let(:not_signed_in_user) { nil }

        before do
          stub_licensed_features(group_saml: true)
          root_group.add_developer(member_with_identity)
          root_group.add_developer(member_without_identity)
        end

        subject { described_class.new(current_user, resource) }

        shared_examples 'does not allow read group' do
          it 'does not allow read group' do
            is_expected.not_to allow_action(:read_group)
          end
        end

        shared_examples 'allows to read group' do
          it 'allows read group' do
            is_expected.to allow_action(:read_group)
          end
        end

        shared_examples 'does not allow to read group due to its visibility level' do
          it 'does not allow to read group due to its visibility level', :aggregate_failures do
            expect(resource.root_ancestor.saml_provider.enforced_sso?).to eq(false)

            is_expected.not_to allow_action(:read_group)
          end
        end

        # See https://docs.gitlab.com/ee/user/group/saml_sso/#sso-enforcement
        where(:resource, :resource_visibility_level, :enforced_sso?, :user, :user_is_resource_owner?, :user_with_saml_session?, :user_is_admin?, :enable_admin_mode?, :user_is_auditor?, :shared_examples) do
          # Project/Group visibility: Private; Enforce SSO setting: Off

          ref(:root_group) | 'private' | false | ref(:member_with_identity)    | false | false | nil  | nil   | nil  | 'does not allow read group'
          ref(:root_group) | 'private' | false | ref(:member_with_identity)    | true  | false | nil  | nil   | nil  | 'allows to read group'
          ref(:root_group) | 'private' | false | ref(:member_with_identity)    | false | true  | nil  | nil   | nil  | 'allows to read group'
          ref(:root_group) | 'private' | false | ref(:member_with_identity)    | false | false | true | false | nil  | 'does not allow read group'
          ref(:root_group) | 'private' | false | ref(:member_with_identity)    | false | false | true | true  | nil  | 'allows to read group'
          ref(:root_group) | 'private' | false | ref(:member_with_identity)    | false | false | nil  | nil   | true | 'allows to read group'
          ref(:subgroup)   | 'private' | false | ref(:member_with_identity)    | false | false | nil  | nil   | nil  | 'does not allow read group'
          ref(:subgroup)   | 'private' | false | ref(:member_with_identity)    | true  | false | nil  | nil   | nil  | 'does not allow read group'
          ref(:subgroup)   | 'private' | false | ref(:member_with_identity)    | false | true  | nil  | nil   | nil  | 'allows to read group'
          ref(:subgroup)   | 'private' | false | ref(:member_with_identity)    | false | false | true | false | nil  | 'does not allow read group'
          ref(:subgroup)   | 'private' | false | ref(:member_with_identity)    | false | false | true | true  | nil  | 'allows to read group'
          ref(:subgroup)   | 'private' | false | ref(:member_with_identity)    | false | false | nil  | nil   | true | 'allows to read group'

          ref(:root_group) | 'private' | false | ref(:member_without_identity) | false | nil   | nil  | nil   | nil  | 'allows to read group'
          ref(:subgroup)   | 'private' | false | ref(:member_without_identity) | false | nil   | nil  | nil   | nil  | 'allows to read group'

          ref(:root_group) | 'private' | false | ref(:non_member)              | nil   | nil   | nil  | nil   | nil  | 'does not allow to read group due to its visibility level'
          ref(:root_group) | 'private' | false | ref(:non_member)              | nil   | nil   | true | false | nil  | 'does not allow to read group due to its visibility level'
          ref(:root_group) | 'private' | false | ref(:non_member)              | nil   | nil   | true | true  | nil  | 'allows to read group'
          ref(:root_group) | 'private' | false | ref(:non_member)              | nil   | nil   | nil  | nil   | true | 'allows to read group'
          ref(:root_group) | 'private' | false | ref(:not_signed_in_user)      | nil   | nil   | nil  | nil   | nil  | 'does not allow to read group due to its visibility level'
          ref(:subgroup)   | 'private' | false | ref(:non_member)              | nil   | nil   | nil  | nil   | nil  | 'does not allow to read group due to its visibility level'
          ref(:subgroup)   | 'private' | false | ref(:non_member)              | nil   | nil   | true | false | nil  | 'does not allow to read group due to its visibility level'
          ref(:subgroup)   | 'private' | false | ref(:non_member)              | nil   | nil   | true | true  | nil  | 'allows to read group'
          ref(:subgroup)   | 'private' | false | ref(:non_member)              | nil   | nil   | nil  | nil   | true | 'allows to read group'
          ref(:subgroup)   | 'private' | false | ref(:not_signed_in_user)      | nil   | nil   | nil  | nil   | nil  | 'does not allow to read group due to its visibility level'

          # Project/Group visibility: Private; Enforce SSO setting: On

          ref(:root_group) | 'private' | true  | ref(:member_with_identity)    | false | false | nil  | nil   | nil  | 'does not allow read group'
          ref(:root_group) | 'private' | true  | ref(:member_with_identity)    | true  | false | nil  | nil   | nil  | 'allows to read group'
          ref(:root_group) | 'private' | true  | ref(:member_with_identity)    | false | true  | nil  | nil   | nil  | 'allows to read group'
          ref(:root_group) | 'private' | true  | ref(:member_with_identity)    | false | false | true | false | nil  | 'does not allow read group'
          ref(:root_group) | 'private' | true  | ref(:member_with_identity)    | false | false | true | true  | nil  | 'allows to read group'
          ref(:root_group) | 'private' | true  | ref(:member_with_identity)    | false | false | nil  | nil   | true | 'allows to read group'
          ref(:subgroup)   | 'private' | true  | ref(:member_with_identity)    | false | false | nil  | nil   | nil  | 'does not allow read group'
          ref(:subgroup)   | 'private' | true  | ref(:member_with_identity)    | true  | false | nil  | nil   | nil  | 'does not allow read group'
          ref(:subgroup)   | 'private' | true  | ref(:member_with_identity)    | false | true  | nil  | nil   | nil  | 'allows to read group'
          ref(:subgroup)   | 'private' | true  | ref(:member_with_identity)    | false | false | true | false | nil  | 'does not allow read group'
          ref(:subgroup)   | 'private' | true  | ref(:member_with_identity)    | false | false | true | true  | nil  | 'allows to read group'
          ref(:subgroup)   | 'private' | true  | ref(:member_with_identity)    | false | false | nil  | nil   | true | 'allows to read group'

          ref(:root_group) | 'private' | true  | ref(:member_without_identity) | false | nil   | nil  | nil   | nil  | 'does not allow read group'
          ref(:root_group) | 'private' | true  | ref(:member_without_identity) | true  | nil   | nil  | nil   | nil  | 'allows to read group'
          ref(:root_group) | 'private' | true  | ref(:member_without_identity) | false | nil   | true | false | nil  | 'does not allow read group'
          ref(:root_group) | 'private' | true  | ref(:member_without_identity) | false | nil   | true | true  | nil  | 'allows to read group'
          ref(:root_group) | 'private' | true  | ref(:member_without_identity) | false | nil   | nil  | nil   | true | 'allows to read group'
          ref(:subgroup)   | 'private' | true  | ref(:member_without_identity) | false | nil   | nil  | nil   | nil  | 'does not allow read group'
          ref(:subgroup)   | 'private' | true  | ref(:member_without_identity) | true  | nil   | nil  | nil   | nil  | 'does not allow read group'
          ref(:subgroup)   | 'private' | true  | ref(:member_without_identity) | false | nil   | true | false | nil  | 'does not allow read group'
          ref(:subgroup)   | 'private' | true  | ref(:member_without_identity) | false | nil   | true | true  | nil  | 'allows to read group'
          ref(:subgroup)   | 'private' | true  | ref(:member_without_identity) | false | nil   | nil  | nil   | true | 'allows to read group'

          ref(:root_group) | 'private' | true  | ref(:non_member)              | nil   | nil   | nil  | nil   | nil  | 'does not allow read group'
          ref(:root_group) | 'private' | true  | ref(:non_member)              | nil   | nil   | true | false | nil  | 'does not allow read group'
          ref(:root_group) | 'private' | true  | ref(:non_member)              | nil   | nil   | true | true  | nil  | 'allows to read group'
          ref(:root_group) | 'private' | true  | ref(:non_member)              | nil   | nil   | nil  | nil   | true | 'allows to read group'
          ref(:root_group) | 'private' | true  | ref(:not_signed_in_user)      | nil   | nil   | nil  | nil   | nil  | 'does not allow read group'
          ref(:subgroup)   | 'private' | true  | ref(:non_member)              | nil   | nil   | nil  | nil   | nil  | 'does not allow read group'
          ref(:subgroup)   | 'private' | true  | ref(:non_member)              | nil   | nil   | true | false | nil  | 'does not allow read group'
          ref(:subgroup)   | 'private' | true  | ref(:non_member)              | nil   | nil   | true | true  | nil  | 'allows to read group'
          ref(:subgroup)   | 'private' | true  | ref(:non_member)              | nil   | nil   | nil  | nil   | true | 'allows to read group'
          ref(:subgroup)   | 'private' | true  | ref(:not_signed_in_user)      | nil   | nil   | nil  | nil   | nil  | 'does not allow read group'

          # Project/Group visibility: Public; Enforce SSO setting: Off

          ref(:root_group) | 'public'  | false | ref(:member_with_identity)    | false | false | nil  | nil   | nil  | 'does not allow read group'
          ref(:root_group) | 'public'  | false | ref(:member_with_identity)    | true  | false | nil  | nil   | nil  | 'allows to read group'
          ref(:root_group) | 'public'  | false | ref(:member_with_identity)    | false | true  | nil  | nil   | nil  | 'allows to read group'
          ref(:root_group) | 'public'  | false | ref(:member_with_identity)    | false | false | true | false | nil  | 'does not allow read group'
          ref(:root_group) | 'public'  | false | ref(:member_with_identity)    | false | false | true | true  | nil  | 'allows to read group'
          ref(:root_group) | 'public'  | false | ref(:member_with_identity)    | false | false | nil  | nil   | true | 'allows to read group'
          ref(:subgroup)   | 'public'  | false | ref(:member_with_identity)    | false | false | nil  | nil   | nil  | 'does not allow read group'
          ref(:subgroup)   | 'public'  | false | ref(:member_with_identity)    | true  | false | nil  | nil   | nil  | 'does not allow read group'
          ref(:subgroup)   | 'public'  | false | ref(:member_with_identity)    | false | true  | nil  | nil   | nil  | 'allows to read group'
          ref(:subgroup)   | 'public'  | false | ref(:member_with_identity)    | false | false | true | false | nil  | 'does not allow read group'
          ref(:subgroup)   | 'public'  | false | ref(:member_with_identity)    | false | false | true | true  | nil  | 'allows to read group'
          ref(:subgroup)   | 'public'  | false | ref(:member_with_identity)    | false | false | nil  | nil   | true | 'allows to read group'

          ref(:root_group) | 'public'  | false | ref(:member_without_identity) | false | nil   | nil  | nil   | nil  | 'allows to read group'
          ref(:subgroup)   | 'public'  | false | ref(:member_without_identity) | false | nil   | nil  | nil   | nil  | 'allows to read group'

          ref(:root_group) | 'public'  | false | ref(:non_member)              | nil   | nil   | nil  | nil   | nil  | 'allows to read group'
          ref(:root_group) | 'public'  | false | ref(:not_signed_in_user)      | nil   | nil   | nil  | nil   | nil  | 'allows to read group'
          ref(:subgroup)   | 'public'  | false | ref(:non_member)              | nil   | nil   | nil  | nil   | nil  | 'allows to read group'
          ref(:subgroup)   | 'public'  | false | ref(:not_signed_in_user)      | nil   | nil   | nil  | nil   | nil  | 'allows to read group'

          # Project/Group visibility: Public; Enforce SSO setting: On

          ref(:root_group) | 'public'  | true  | ref(:member_with_identity)    | false | false | nil  | nil   | nil  | 'does not allow read group'
          ref(:root_group) | 'public'  | true  | ref(:member_with_identity)    | true  | false | nil  | nil   | nil  | 'allows to read group'
          ref(:root_group) | 'public'  | true  | ref(:member_with_identity)    | false | true  | nil  | nil   | nil  | 'allows to read group'
          ref(:root_group) | 'public'  | true  | ref(:member_with_identity)    | false | false | true | false | nil  | 'does not allow read group'
          ref(:root_group) | 'public'  | true  | ref(:member_with_identity)    | false | false | true | true  | nil  | 'allows to read group'
          ref(:root_group) | 'public'  | true  | ref(:member_with_identity)    | false | false | nil  | nil   | true | 'allows to read group'
          ref(:subgroup)   | 'public'  | true  | ref(:member_with_identity)    | false | false | nil  | nil   | nil  | 'does not allow read group'
          ref(:subgroup)   | 'public'  | true  | ref(:member_with_identity)    | true  | false | nil  | nil   | nil  | 'does not allow read group'
          ref(:subgroup)   | 'public'  | true  | ref(:member_with_identity)    | false | true  | nil  | nil   | nil  | 'allows to read group'
          ref(:subgroup)   | 'public'  | true  | ref(:member_with_identity)    | false | false | true | false | nil  | 'does not allow read group'
          ref(:subgroup)   | 'public'  | true  | ref(:member_with_identity)    | false | false | true | true  | nil  | 'allows to read group'
          ref(:subgroup)   | 'public'  | true  | ref(:member_with_identity)    | false | false | nil  | nil   | true | 'allows to read group'

          ref(:root_group) | 'public'  | true  | ref(:member_without_identity) | false | nil   | nil  | nil   | nil  | 'does not allow read group'
          ref(:root_group) | 'public'  | true  | ref(:member_without_identity) | true  | nil   | nil  | nil   | nil  | 'allows to read group'
          ref(:root_group) | 'public'  | true  | ref(:member_without_identity) | false | nil   | true | false | nil  | 'does not allow read group'
          ref(:root_group) | 'public'  | true  | ref(:member_without_identity) | false | nil   | true | true  | nil  | 'allows to read group'
          ref(:root_group) | 'public'  | true  | ref(:member_without_identity) | false | nil   | nil  | nil   | true | 'allows to read group'
          ref(:subgroup)   | 'public'  | true  | ref(:member_without_identity) | false | nil   | nil  | nil   | nil  | 'does not allow read group'
          ref(:subgroup)   | 'public'  | true  | ref(:member_without_identity) | true  | nil   | nil  | nil   | nil  | 'does not allow read group'
          ref(:subgroup)   | 'public'  | true  | ref(:member_without_identity) | false | nil   | true | false | nil  | 'does not allow read group'
          ref(:subgroup)   | 'public'  | true  | ref(:member_without_identity) | false | nil   | true | true  | nil  | 'allows to read group'
          ref(:subgroup)   | 'public'  | true  | ref(:member_without_identity) | false | nil   | nil  | nil   | true | 'allows to read group'

          ref(:root_group) | 'public'  | true  | ref(:non_member)              | nil   | nil   | nil  | nil   | nil  | 'allows to read group'
          ref(:root_group) | 'public'  | true  | ref(:not_signed_in_user)      | nil   | nil   | nil  | nil   | nil  | 'allows to read group'
          ref(:subgroup)   | 'public'  | true  | ref(:non_member)              | nil   | nil   | nil  | nil   | nil  | 'allows to read group'
          ref(:subgroup)   | 'public'  | true  | ref(:not_signed_in_user)      | nil   | nil   | nil  | nil   | nil  | 'allows to read group'
        end

        with_them do
          context "when 'Enforce SSO-only authentication for web activity for this group' option is #{params[:enforced_sso?] ? 'enabled' : 'not enabled'}" do
            around do |example|
              session = {}

              session['warden.user.user.key'] = [[user.id], user.authenticatable_salt] if user.is_a?(User)

              Gitlab::Session.with_session(session) do
                example.run
              end
            end

            before do
              saml_provider.update!(enforced_sso: enforced_sso?)
            end

            context "when resource is #{params[:resource_visibility_level]}" do
              before do
                resource.update!(visibility_level: Gitlab::VisibilityLevel.string_options[resource_visibility_level])
              end

              context 'for user', enable_admin_mode: params[:enable_admin_mode?] do
                before do
                  if user_is_resource_owner?
                    resource.root_ancestor.member(user).update_column(:access_level, Gitlab::Access::OWNER)
                  end

                  Gitlab::Auth::GroupSaml::SsoEnforcer.new(saml_provider).update_session if user_with_saml_session?

                  user.update!(admin: true) if user_is_admin?
                  user.update!(auditor: true) if user_is_auditor?
                end

                let(:current_user) { user }

                include_examples params[:shared_examples]
              end
            end
          end
        end
      end
    end
  end

  describe 'admin_saml_group_links for global SAML' do
    let(:current_user) { owner }

    it { is_expected.to be_disallowed(:admin_saml_group_links) }

    context 'when global SAML is enabled' do
      context 'when the groups attribute is not configured' do
        before do
          stub_basic_saml_config
        end

        it { is_expected.to be_disallowed(:admin_saml_group_links) }
      end

      context 'when the groups attribute is configured' do
        before do
          stub_omniauth_config(providers: [{ name: 'saml', groups_attribute: 'Groups', args: {} }])
        end

        it { is_expected.to be_disallowed(:admin_saml_group_links) }

        context 'when saml_group_sync feature is licensed' do
          before do
            stub_licensed_features(saml_group_sync: true)
          end

          it { is_expected.to be_allowed(:admin_saml_group_links) }

          context 'when the current user is not an admin or owner' do
            let(:current_user) { maintainer }

            it { is_expected.to be_disallowed(:admin_saml_group_links) }
          end
        end
      end
    end
  end

  context 'with ip restriction' do
    let(:current_user) { maintainer }

    before do
      allow(Gitlab::IpAddressState).to receive(:current).and_return('192.168.0.2')
      stub_licensed_features(group_ip_restriction: true, epics: true)
      stub_config(dependency_proxy: { enabled: true })
    end

    context 'without restriction' do
      it { is_expected.to be_allowed(:read_group) }
      it { is_expected.to be_allowed(:read_milestone) }
      it { is_expected.to be_allowed(:read_package) }
      it { is_expected.to be_allowed(:create_package) }
      it { is_expected.to be_allowed(:destroy_package) }
      it { is_expected.to be_allowed(:create_epic) }
      it { is_expected.to be_allowed(:read_dependency_proxy) }
      it { is_expected.to be_disallowed(:admin_package) }
      it { is_expected.to be_disallowed(:admin_dependency_proxy) }
    end

    context 'with restriction' do
      before do
        create(:ip_restriction, group: group, range: range)
      end

      context 'address is within the range' do
        let(:range) { '192.168.0.0/24' }

        it { is_expected.to be_allowed(:read_group) }
        it { is_expected.to be_allowed(:read_milestone) }
        it { is_expected.to be_allowed(:read_package) }
        it { is_expected.to be_allowed(:create_package) }
        it { is_expected.to be_allowed(:destroy_package) }
        it { is_expected.to be_allowed(:create_epic) }
        it { is_expected.to be_allowed(:read_dependency_proxy) }
        it { is_expected.to be_disallowed(:admin_package) }
        it { is_expected.to be_disallowed(:admin_dependency_proxy) }
      end

      context 'address is outside the range' do
        let(:range) { '10.0.0.0/8' }

        context 'as maintainer' do
          it { is_expected.to be_disallowed(:read_group) }
          it { is_expected.to be_disallowed(:read_milestone) }
          it { is_expected.to be_disallowed(:read_package) }
          it { is_expected.to be_disallowed(:create_package) }
          it { is_expected.to be_disallowed(:destroy_package) }
          it { is_expected.to be_disallowed(:create_epic) }
          it { is_expected.to be_disallowed(:admin_package) }
          it { is_expected.to be_disallowed(:read_dependency_proxy) }
          it { is_expected.to be_disallowed(:admin_dependency_proxy) }
        end

        context 'as owner' do
          let(:current_user) { owner }

          it { is_expected.to be_allowed(:read_group) }
          it { is_expected.to be_allowed(:read_milestone) }
          it { is_expected.to be_allowed(:read_package) }
          it { is_expected.to be_allowed(:create_package) }
          it { is_expected.to be_allowed(:destroy_package) }
          it { is_expected.to be_allowed(:admin_package) }
          it { is_expected.to be_allowed(:read_dependency_proxy) }
          it { is_expected.to be_allowed(:admin_dependency_proxy) }
        end

        context 'as auditor' do
          let(:current_user) { create(:user, :auditor) }

          it { is_expected.to be_allowed(:read_group) }
          it { is_expected.to be_allowed(:read_milestone) }
          it { is_expected.to be_allowed(:read_group_audit_events) }
          it { is_expected.to be_allowed(:read_dependency_proxy) }
          it { is_expected.to be_disallowed(:admin_dependency_proxy) }
        end
      end
    end
  end

  context 'when LDAP sync is not enabled' do
    context 'owner' do
      let(:current_user) { owner }

      it { is_expected.to be_disallowed(:override_group_member) }
      it { is_expected.to be_allowed(:admin_ldap_group_links) }

      context 'does not allow group owners to manage ldap' do
        before do
          stub_application_setting(allow_group_owners_to_manage_ldap: false)
        end

        it { is_expected.to be_disallowed(:admin_ldap_group_links) }
      end
    end

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

      context 'when admin mode is enabled', :enable_admin_mode do
        it { is_expected.to be_disallowed(:override_group_member) }
        it { is_expected.to be_allowed(:admin_ldap_group_links) }
      end

      context 'when admin mode is disabled' do
        it { is_expected.to be_disallowed(:override_group_member) }
        it { is_expected.to be_disallowed(:admin_ldap_group_links) }
      end
    end
  end

  context 'when memberships locked to SAML' do
    let(:saml_with_group_sync) do
      {
        name: 'saml',
        groups_attribute: 'groups',
        external_groups: [],
        args: {}
      }
    end

    before do
      stub_application_setting(lock_memberships_to_saml: true)
      stub_licensed_features(saml_group_sync: true)
    end

    context 'when group is a root group' do
      before do
        stub_omniauth_config(providers: [saml_with_group_sync])
        allow(Devise).to receive(:omniauth_providers).and_return([:saml])
      end

      context 'when SAML group link sync is enabled' do
        before do
          create(:saml_group_link, group: group)
        end

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

          context 'when admin mode is enabled', :enable_admin_mode do
            it { is_expected.to be_allowed(:admin_group_member) }
          end

          context 'when admin mode is disabled' do
            it { is_expected.not_to be_allowed(:admin_group_member) }
          end
        end

        context 'owner' do
          let(:current_user) { owner }

          it { is_expected.not_to be_allowed(:admin_group_member) }
        end

        context 'maintainer' do
          let(:current_user) { maintainer }

          it { is_expected.not_to be_allowed(:admin_group_member) }
        end
      end

      context 'when no SAML sync is enabled' do
        before do
          allow(group).to receive(:saml_group_links_exists?).and_return(false)
        end

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

          it { is_expected.not_to be_allowed(:admin_group_member) }
        end

        context 'owner' do
          let(:current_user) { owner }

          it { is_expected.to be_allowed(:admin_group_member) }
        end
      end
    end

    context 'when group is not a root group' do
      let(:parent_group) { create(:group) }
      let(:group) { create(:group, :private, parent: parent_group) }

      before do
        stub_omniauth_config(providers: [saml_with_group_sync])
        allow(Devise).to receive(:omniauth_providers).and_return([:saml])

        group.add_owner(owner)
        parent_group.add_owner(owner)
      end

      context 'when SAML group link sync is enabled' do
        before do
          create(:saml_group_link, group: parent_group)
        end

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

          context 'when admin mode is enabled', :enable_admin_mode do
            it { is_expected.to be_allowed(:admin_group_member) }
          end

          context 'when admin mode is disabled' do
            it { is_expected.not_to be_allowed(:admin_group_member) }
          end
        end

        context 'owner' do
          let(:current_user) { owner }

          it { is_expected.not_to be_allowed(:admin_group_member) }
        end

        context 'maintainer' do
          let(:current_user) { maintainer }

          it { is_expected.not_to be_allowed(:admin_group_member) }
        end

        context 'when child group has different owner than parent group' do
          let(:sub_group_owner) { create(:user) }
          let(:current_user) { sub_group_owner }

          before do
            group.add_owner(sub_group_owner)
          end

          it { is_expected.not_to be_allowed(:admin_group_member) }
        end
      end

      context 'when no SAML group link sync is enabled' do
        before do
          allow(group).to receive(:saml_group_links_exists?).and_return(false)
        end

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

          it { is_expected.to be_disallowed(:admin_group_member) }
        end

        context 'owner' do
          let(:current_user) { owner }

          it { is_expected.to be_allowed(:admin_group_member) }
        end

        context 'maintainer' do
          let(:current_user) { maintainer }

          it { is_expected.to be_disallowed(:admin_group_member) }
        end
      end
    end

    context 'when group is synced via GroupSaml', :saas do
      before do
        stub_licensed_features(group_saml: true, saml_group_sync: true)
        stub_group_saml_config(true)
        create(:saml_provider, group: group.root_ancestor, enabled: true)
      end

      context 'when SAML group link is configured' do
        before do
          create(:saml_group_link, group: group)
        end

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

          context 'when admin mode is enabled', :enable_admin_mode do
            it { is_expected.to be_allowed(:admin_group_member) }
          end

          context 'when admin mode is disabled' do
            it { is_expected.not_to be_allowed(:admin_group_member) }
          end
        end

        context 'owner' do
          let(:current_user) { owner }

          it { is_expected.not_to be_allowed(:admin_group_member) }
        end

        context 'maintainer' do
          let(:current_user) { maintainer }

          it { is_expected.not_to be_allowed(:admin_group_member) }
        end
      end

      context 'when SAML group link is not configured' do
        before do
          allow(group).to receive(:saml_group_links_exists?).and_return(false)
        end

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

          it { is_expected.not_to be_allowed(:admin_group_member) }
        end

        context 'owner' do
          let(:current_user) { owner }

          it { is_expected.to be_allowed(:admin_group_member) }
        end
      end
    end
  end

  context 'when LDAP sync is enabled' do
    before do
      allow(group).to receive(:ldap_synced?).and_return(true)
    end

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

      it { is_expected.to be_disallowed(:override_group_member) }
      it { is_expected.to be_disallowed(:admin_ldap_group_links) }
    end

    context 'guests' do
      let(:current_user) { guest }

      it { is_expected.to be_disallowed(:override_group_member) }
      it { is_expected.to be_disallowed(:admin_ldap_group_links) }
    end

    context 'planners' do
      let(:current_user) { planner }

      it { is_expected.to be_disallowed(:override_group_member) }
      it { is_expected.to be_disallowed(:admin_ldap_group_links) }
    end

    context 'reporter' do
      let(:current_user) { reporter }

      it { is_expected.to be_disallowed(:override_group_member) }
      it { is_expected.to be_disallowed(:admin_ldap_group_links) }
    end

    context 'developer' do
      let(:current_user) { developer }

      it { is_expected.to be_disallowed(:override_group_member) }
      it { is_expected.to be_disallowed(:admin_ldap_group_links) }
    end

    context 'maintainer' do
      let(:current_user) { maintainer }

      it { is_expected.to be_disallowed(:override_group_member) }
      it { is_expected.to be_disallowed(:admin_ldap_group_links) }
    end

    context 'owner' do
      let(:current_user) { owner }

      context 'allow group owners to manage ldap' do
        it { is_expected.to be_allowed(:override_group_member) }
      end

      context 'does not allow group owners to manage ldap' do
        before do
          stub_application_setting(allow_group_owners_to_manage_ldap: false)
        end

        it { is_expected.to be_disallowed(:override_group_member) }
        it { is_expected.to be_disallowed(:admin_ldap_group_links) }
      end
    end

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

      context 'when admin mode is enabled', :enable_admin_mode do
        it { is_expected.to be_allowed(:override_group_member) }
        it { is_expected.to be_allowed(:admin_ldap_group_links) }
      end

      context 'when admin mode is disabled' do
        it { is_expected.to be_disallowed(:override_group_member) }
        it { is_expected.to be_disallowed(:admin_ldap_group_links) }
      end
    end

    context 'when memberships locked to LDAP' do
      before do
        stub_application_setting(allow_group_owners_to_manage_ldap: true)
        stub_application_setting(lock_memberships_to_ldap: true)
      end

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

        context 'when admin mode enabled', :enable_admin_mode do
          it { is_expected.to be_allowed(:override_group_member) }
          it { is_expected.to be_allowed(:update_group_member) }
        end

        context 'when admin mode disabled' do
          it { is_expected.to be_disallowed(:override_group_member) }
          it { is_expected.to be_disallowed(:update_group_member) }
        end
      end

      context 'owner' do
        let(:current_user) { owner }

        it { is_expected.not_to be_allowed(:admin_group_member) }
        it { is_expected.not_to be_allowed(:override_group_member) }
        it { is_expected.not_to be_allowed(:update_group_member) }

        context 'and service_accounts feature is enabled' do
          before do
            stub_licensed_features(service_accounts: true)
          end

          it { is_expected.to be_allowed(:admin_service_account_member) }
        end
      end
    end
  end

  describe 'read_group_credentials_inventory' do
    using RSpec::Parameterized::TableSyntax

    let(:non_member) { create(:user) }
    let_it_be(:read_policy) { :read_group_credentials_inventory }
    let_it_be(:admin_policy) { :admin_group_credentials_inventory }

    where(:user, :admin_mode?, :saas?, :licensed?, :allowed) do
      ref(:admin)      | false | false | false | false
      ref(:admin)      | true  | false | false | false
      ref(:admin)      | true  | true  | false | false
      ref(:admin)      | false | false | true  | false
      ref(:admin)      | false | true  | true  | false
      ref(:admin)      | true  | false | true  | false
      ref(:admin)      | false | true  | false | false
      ref(:admin)      | true  | true  | true  | true

      ref(:owner)      | nil   | false | false | false
      ref(:owner)      | nil   | true  | false | false
      ref(:owner)      | nil   | false | true  | false
      ref(:owner)      | nil   | true  | false | false
      ref(:owner)      | nil   | true  | true  | true

      ref(:maintainer) | nil   | true  | true  | false
      ref(:developer)  | nil   | true  | true  | false
      ref(:reporter)   | nil   | true  | true  | false
      ref(:guest)      | nil   | true  | true  | false
      ref(:non_member) | nil   | true  | true  | false
      nil              | nil   | true  | true  | false
    end

    with_them do
      let(:current_user) { user }

      before do
        stub_licensed_features(credentials_inventory: licensed?)
      end

      context 'for user', saas: params[:saas?], enable_admin_mode: params[:admin_mode?] do
        it { is_expected.to(allowed ? be_allowed(read_policy) : be_disallowed(read_policy)) }
        it { is_expected.to(allowed ? be_allowed(admin_policy) : be_disallowed(admin_policy)) }
      end
    end

    context 'subgroup', :saas do
      let(:current_user) { owner }
      let(:subgroup) { create(:group, :private, parent: group) }

      subject { described_class.new(current_user, subgroup) }

      before do
        stub_licensed_features(credentials_inventory: true)
      end

      it { is_expected.to be_disallowed(read_policy) }
      it { is_expected.to be_disallowed(admin_policy) }
    end
  end

  describe 'change_prevent_group_forking' do
    context 'when feature is disabled' do
      context 'with owner' do
        let(:current_user) { owner }

        it { is_expected.to be_disallowed(:change_prevent_group_forking) }
      end

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

        it { is_expected.to be_disallowed(:change_prevent_group_forking) }
      end
    end

    context 'when feature is enabled' do
      before do
        stub_licensed_features(group_forking_protection: true)
      end

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

        it { is_expected.to be_allowed(:change_prevent_group_forking) }

        context 'when group has parent' do
          let(:group) { create(:group, :private, parent: create(:group)) }

          it { is_expected.to be_disallowed(:change_prevent_group_forking) }
        end
      end

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

        it { is_expected.to be_disallowed(:change_prevent_group_forking) }
      end
    end
  end

  describe 'security orchestration policies' do
    before do
      stub_licensed_features(security_orchestration_policies: true)
    end

    context 'with developer or maintainer role' do
      where(role: %w[maintainer developer])

      with_them do
        let(:current_user) { public_send(role) }

        it { is_expected.to be_allowed(:read_security_orchestration_policies) }
      end
    end

    context 'with owner role' do
      where(role: %w[owner])

      with_them do
        let(:current_user) { public_send(role) }

        it { is_expected.to be_allowed(:read_security_orchestration_policies) }
      end
    end

    context 'with auditor role' do
      where(role: %w[auditor])

      with_them do
        let(:current_user) { public_send(role) }

        it { is_expected.to be_allowed(:read_security_orchestration_policies) }
      end
    end
  end

  describe "security dashboard policies" do
    where(:policy, :role, :admin_mode, :allowed) do
      :admin_vulnerability           | :admin      | false | false
      :admin_vulnerability           | :admin      | true  | true
      :admin_vulnerability           | :auditor    | nil   | false
      :admin_vulnerability           | :developer  | nil   | false
      :admin_vulnerability           | :guest      | nil   | false
      :admin_vulnerability           | :planner    | nil   | false
      :admin_vulnerability           | :maintainer | nil   | true
      :admin_vulnerability           | :owner      | nil   | true
      :admin_vulnerability           | :reporter   | nil   | false
      :read_dependency               | :admin      | false | false
      :read_dependency               | :admin      | true  | true
      :read_dependency               | :auditor    | nil   | true
      :read_dependency               | :developer  | nil   | true
      :read_dependency               | :guest      | nil   | false
      :read_dependency               | :planner    | nil   | false
      :read_dependency               | :maintainer | nil   | true
      :read_dependency               | :owner      | nil   | true
      :read_dependency               | :reporter   | nil   | false
      :read_group_security_dashboard | :admin      | false | false
      :read_group_security_dashboard | :admin      | true  | true
      :read_group_security_dashboard | :auditor    | nil   | true
      :read_group_security_dashboard | :developer  | nil   | true
      :read_group_security_dashboard | :guest      | nil   | false
      :read_group_security_dashboard | :planner    | nil   | false
      :read_group_security_dashboard | :maintainer | nil   | true
      :read_group_security_dashboard | :owner      | nil   | true
      :read_group_security_dashboard | :reporter   | nil   | false
      :read_licenses                 | :admin      | false | false
      :read_licenses                 | :admin      | true  | true
      :read_licenses                 | :auditor    | nil   | true
      :read_licenses                 | :developer  | nil   | true
      :read_licenses                 | :guest      | nil   | false
      :read_licenses                 | :planner    | nil   | false
      :read_licenses                 | :maintainer | nil   | true
      :read_licenses                 | :owner      | nil   | true
      :read_licenses                 | :reporter   | nil   | false
      :read_vulnerability            | :admin      | false | false
      :read_vulnerability            | :admin      | true  | true
      :read_vulnerability            | :auditor    | nil   | true
      :read_vulnerability            | :developer  | nil   | true
      :read_vulnerability            | :guest      | nil   | false
      :read_vulnerability            | :planner    | nil   | false
      :read_vulnerability            | :maintainer | nil   | true
      :read_vulnerability            | :owner      | nil   | true
      :read_vulnerability            | :reporter   | nil   | false
    end

    with_them do
      let(:current_user) { public_send(role) }

      before do
        enable_admin_mode!(current_user) if admin_mode
      end

      context "with security_dashboard enabled" do
        before do
          stub_licensed_features(security_dashboard: true)
        end

        it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
      end

      context "with security_dashboard disabled" do
        before do
          stub_licensed_features(security_dashboard: false)
        end

        it { is_expected.to be_disallowed(policy) }
      end
    end
  end

  describe 'resolve_vulnerability_with_ai' do
    before do
      stub_licensed_features(
        security_dashboard: true,
        ai_features: true
      )
      allow(current_user).to receive(:allowed_to_use?).and_return(true)
    end

    context 'when user cannot :read_security_resource' do
      let(:current_user) { guest }

      where(:duo_features_enabled, :cs_matcher) do
        true  | be_disallowed(:resolve_vulnerability_with_ai)
        false | be_disallowed(:resolve_vulnerability_with_ai)
      end

      with_them do
        before do
          group.namespace_settings.update!(duo_features_enabled: duo_features_enabled)
        end

        it { is_expected.to cs_matcher }
      end
    end

    context 'when user can?(:read_security_resource)' do
      let(:current_user) { developer }

      where(:duo_features_enabled, :cs_matcher) do
        true  | be_allowed(:resolve_vulnerability_with_ai)
        false | be_disallowed(:resolve_vulnerability_with_ai)
      end

      with_them do
        before do
          group.namespace_settings.update!(duo_features_enabled: duo_features_enabled)
        end

        it { is_expected.to cs_matcher }
      end
    end
  end

  describe 'admin_vulnerability' do
    before do
      stub_licensed_features(security_dashboard: true)
    end

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

      it { is_expected.to be_disallowed(:admin_vulnerability) }
    end

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

      context "when auditor is not a group member" do
        it { is_expected.to be_disallowed(:admin_vulnerability) }
      end

      context "when developer doesn't have developer-level access to a group" do
        before do
          group.add_reporter(auditor)
        end

        it { is_expected.to be_disallowed(:admin_vulnerability) }
      end

      context 'when auditor has developer-level access to a group' do
        before do
          group.add_developer(auditor)
        end

        it { is_expected.to be_disallowed(:admin_vulnerability) }
      end
    end
  end

  describe 'read_group_security_dashboard & create_vulnerability_export' do
    let(:abilities) do
      %i[read_group_security_dashboard create_vulnerability_export read_security_resource read_dependency read_licenses]
    end

    before do
      stub_licensed_features(security_dashboard: true)
    end

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

      context 'when admin mode is enabled', :enable_admin_mode do
        it { is_expected.to be_allowed(*abilities) }
      end

      context 'when admin mode is disabled' do
        it { is_expected.to be_disallowed(*abilities) }
      end
    end

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

      it { is_expected.to be_allowed(*abilities) }
    end

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

      it { is_expected.to be_allowed(*abilities) }
    end

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

      it { is_expected.to be_allowed(*abilities) }

      context 'when security dashboard features is not available' do
        before do
          stub_licensed_features(security_dashboard: false)
        end

        it { is_expected.to be_disallowed(*abilities) }
      end
    end

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

      it { is_expected.to be_disallowed(*abilities) }
    end

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

      it { is_expected.to be_disallowed(*abilities) }
    end

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

      it { is_expected.to be_disallowed(*abilities) }
    end

    context 'with non member' do
      let(:current_user) { create(:user) }

      it { is_expected.to be_disallowed(*abilities) }
    end

    context 'with anonymous' do
      let(:current_user) { nil }

      it { is_expected.to be_disallowed(*abilities) }
    end
  end

  describe 'private nested group use the highest access level from the group and inherited permissions' do
    let(:nested_group) { create(:group, :private, parent: group) }

    before do
      nested_group.add_guest(guest)
      nested_group.add_guest(reporter)
      nested_group.add_guest(developer)
      nested_group.add_guest(maintainer)

      group.members.all_owners.destroy_all # rubocop: disable Cop/DestroyAll

      group.add_guest(owner)
      nested_group.add_owner(owner)
    end

    subject { described_class.new(current_user, nested_group) }

    context 'auditor' do
      let(:current_user) { create(:user, :auditor) }

      before do
        stub_licensed_features(security_dashboard: true)
      end

      specify do
        expect_allowed(*auditor_permissions)
        expect_disallowed(*(reporter_permissions - auditor_permissions))
        expect_disallowed(*(developer_permissions - auditor_permissions))
        expect_disallowed(*(maintainer_permissions - auditor_permissions))
        expect_disallowed(*(owner_permissions - auditor_permissions))
      end
    end
  end

  context 'when push_rules is not enabled by the current license' do
    before do
      stub_licensed_features(push_rules: false)
    end

    let(:current_user) { maintainer }

    it { is_expected.to be_disallowed(:change_push_rules) }
  end

  context 'when push_rules is enabled by the current license' do
    before do
      stub_licensed_features(push_rules: true)
    end

    let(:current_user) { maintainer }

    context 'when the user is an admin', :enable_admin_mode do
      let(:current_user) { admin }

      it { is_expected.to be_allowed(:change_push_rules) }
    end

    context 'when the user is a maintainer' do
      let(:current_user) { maintainer }

      it { is_expected.to be_allowed(:change_push_rules) }
    end

    context 'when the user is a developer' do
      let(:current_user) { developer }

      it { is_expected.to be_disallowed(:change_push_rules) }
    end
  end

  context 'when commit_committer_check is not enabled by the current license' do
    before do
      stub_licensed_features(commit_committer_check: false)
    end

    let(:current_user) { maintainer }

    it { is_expected.not_to be_allowed(:change_commit_committer_check) }
  end

  context 'when commit_committer_check is enabled by the current license' do
    before do
      stub_licensed_features(commit_committer_check: true)
    end

    context 'when the user is an admin', :enable_admin_mode do
      let(:current_user) { admin }

      it { is_expected.to be_allowed(:change_commit_committer_check) }
    end

    context 'when the user is a maintainer' do
      let(:current_user) { maintainer }

      it { is_expected.to be_allowed(:change_commit_committer_check) }
    end

    context 'when the user is a developer' do
      let(:current_user) { developer }

      it { is_expected.not_to be_allowed(:change_commit_committer_check) }
    end
  end

  context 'when commit_committer_name_check is not enabled by the current license' do
    before do
      stub_licensed_features(commit_committer_name_check: false)
    end

    let(:current_user) { maintainer }

    it { is_expected.to be_disallowed(:change_commit_committer_name_check) }
  end

  context 'when commit_committer_name_check is enabled by the current license' do
    before do
      stub_licensed_features(commit_committer_name_check: true)
    end

    context 'when the user is an admin', :enable_admin_mode do
      let(:current_user) { admin }

      it { is_expected.to be_allowed(:change_commit_committer_name_check) }
    end

    context 'when the user is a maintainer' do
      let(:current_user) { maintainer }

      it { is_expected.to be_allowed(:change_commit_committer_name_check) }
    end

    context 'the user is a developer' do
      let(:current_user) { developer }

      it { is_expected.to be_disallowed(:change_commit_committer_name_check) }
    end
  end

  context 'when reject_unsigned_commits is not enabled by the current license' do
    before do
      stub_licensed_features(reject_unsigned_commits: false)
    end

    let(:current_user) { maintainer }

    it { is_expected.not_to be_allowed(:change_reject_unsigned_commits) }
  end

  context 'when reject_unsigned_commits is enabled by the current license' do
    before do
      stub_licensed_features(reject_unsigned_commits: true)
    end

    context 'when the user is an admin', :enable_admin_mode do
      let(:current_user) { admin }

      it { is_expected.to be_allowed(:change_reject_unsigned_commits) }
    end

    context 'when the user is a maintainer' do
      let(:current_user) { maintainer }

      it { is_expected.to be_allowed(:change_reject_unsigned_commits) }
    end

    context 'when the user is a developer' do
      let(:current_user) { developer }

      it { is_expected.not_to be_allowed(:change_reject_unsigned_commits) }
    end
  end

  context 'when reject_non_dco_commits is not enabled by the current license' do
    before do
      stub_licensed_features(reject_non_dco_commits: false)
    end

    let(:current_user) { maintainer }

    it { is_expected.to be_disallowed(:change_reject_non_dco_commits) }
  end

  context 'when reject_non_dco_commits is enabled by the current license' do
    before do
      stub_licensed_features(reject_non_dco_commits: true)
    end

    context 'when the user is an admin', :enable_admin_mode do
      let(:current_user) { admin }

      it { is_expected.to be_allowed(:change_reject_non_dco_commits) }
    end

    context 'when the user is a maintainer' do
      let(:current_user) { maintainer }

      it { is_expected.to be_allowed(:change_reject_non_dco_commits) }
    end

    context 'when the user is a developer' do
      let(:current_user) { developer }

      it { is_expected.to be_disallowed(:change_reject_non_dco_commits) }
    end
  end

  shared_examples 'analytics policy' do |action|
    shared_examples 'policy by role' do |role|
      context role do
        let(:current_user) { public_send(role) }

        it 'is allowed' do
          is_expected.to be_allowed(action)
        end
      end
    end

    %w[owner maintainer developer reporter].each do |role|
      include_examples 'policy by role', role
    end

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

      it 'is allowed when admin mode is enabled', :enable_admin_mode do
        is_expected.to be_allowed(action)
      end

      it 'is not allowed when admin mode is disabled' do
        is_expected.to be_disallowed(action)
      end
    end

    context 'guest' do
      let(:current_user) { guest }

      it 'is not allowed' do
        is_expected.to be_disallowed(action)
      end
    end

    context 'planner' do
      let(:current_user) { planner }

      it 'is not allowed' do
        is_expected.to be_disallowed(action)
      end
    end
  end

  describe 'view_productivity_analytics' do
    include_examples 'analytics policy', :view_productivity_analytics
  end

  describe '#read_group_saml_identity' do
    let_it_be(:saml_provider) { create(:saml_provider, group: group, enabled: true) }

    context 'for owner' do
      let(:current_user) { owner }

      it { is_expected.to be_allowed(:read_group_saml_identity) }

      context 'without Group SAML enabled' do
        before do
          saml_provider.update!(enabled: false)
        end

        it { is_expected.to be_disallowed(:read_group_saml_identity) }
      end
    end

    %w[maintainer developer reporter guest].each do |role|
      context "for #{role}" do
        let(:current_user) { public_send(role) }

        it { is_expected.to be_disallowed(:read_group_saml_identity) }
      end
    end
  end

  describe 'update_default_branch_protection' do
    context 'for an admin' do
      let(:current_user) { admin }

      context 'when the `default_branch_protection_restriction_in_groups` feature is available' do
        before do
          stub_licensed_features(default_branch_protection_restriction_in_groups: true)
        end

        context 'when the setting `group_owners_can_manage_default_branch_protection` is enabled' do
          before do
            stub_ee_application_setting(group_owners_can_manage_default_branch_protection: true)
          end

          context 'when admin mode is enabled', :enable_admin_mode do
            it { is_expected.to be_allowed(:update_default_branch_protection) }
          end

          context 'when admin mode is disabled' do
            it { is_expected.to be_disallowed(:update_default_branch_protection) }
          end
        end

        context 'when the setting `group_owners_can_manage_default_branch_protection` is disabled' do
          before do
            stub_ee_application_setting(group_owners_can_manage_default_branch_protection: false)
          end

          context 'when admin mode is enabled', :enable_admin_mode do
            it { is_expected.to be_allowed(:update_default_branch_protection) }
          end

          context 'when admin mode is disabled' do
            it { is_expected.to be_disallowed(:update_default_branch_protection) }
          end
        end
      end

      context 'when the `default_branch_protection_restriction_in_groups` feature is not available' do
        before do
          stub_licensed_features(default_branch_protection_restriction_in_groups: false)
        end

        context 'when the setting `group_owners_can_manage_default_branch_protection` is enabled' do
          before do
            stub_ee_application_setting(group_owners_can_manage_default_branch_protection: true)
          end

          context 'when admin mode is enabled', :enable_admin_mode do
            it { is_expected.to be_allowed(:update_default_branch_protection) }
          end

          context 'when admin mode is disabled' do
            it { is_expected.to be_disallowed(:update_default_branch_protection) }
          end
        end

        context 'when the setting `group_owners_can_manage_default_branch_protection` is disabled' do
          before do
            stub_ee_application_setting(group_owners_can_manage_default_branch_protection: false)
          end

          context 'when admin mode is enabled', :enable_admin_mode do
            it { is_expected.to be_allowed(:update_default_branch_protection) }
          end

          context 'when admin mode is disabled' do
            it { is_expected.to be_disallowed(:update_default_branch_protection) }
          end
        end
      end
    end

    context 'for an owner' do
      let(:current_user) { owner }

      context 'when the `default_branch_protection_restriction_in_groups` feature is available' do
        before do
          stub_licensed_features(default_branch_protection_restriction_in_groups: true)
        end

        context 'when the setting `group_owners_can_manage_default_branch_protection` is enabled' do
          before do
            stub_ee_application_setting(group_owners_can_manage_default_branch_protection: true)
          end

          it { is_expected.to be_allowed(:update_default_branch_protection) }
        end

        context 'when the setting `group_owners_can_manage_default_branch_protection` is disabled' do
          before do
            stub_ee_application_setting(group_owners_can_manage_default_branch_protection: false)
          end

          it { is_expected.to be_disallowed(:update_default_branch_protection) }
        end
      end

      context 'when the `default_branch_protection_restriction_in_groups` feature is not available' do
        before do
          stub_licensed_features(default_branch_protection_restriction_in_groups: false)
        end

        context 'when the setting `group_owners_can_manage_default_branch_protection` is enabled' do
          before do
            stub_ee_application_setting(group_owners_can_manage_default_branch_protection: true)
          end

          it { is_expected.to be_allowed(:update_default_branch_protection) }
        end

        context 'when the setting `group_owners_can_manage_default_branch_protection` is disabled' do
          before do
            stub_ee_application_setting(group_owners_can_manage_default_branch_protection: false)
          end

          it { is_expected.to be_allowed(:update_default_branch_protection) }
        end
      end
    end
  end

  describe ':admin_ci_minutes' do
    let(:policy) { :admin_ci_minutes }

    where(:role, :admin_mode, :allowed) do
      :guest      | nil   | false
      :planner    | nil   | false
      :reporter   | nil   | false
      :developer  | nil   | false
      :maintainer | nil   | false
      :owner      | nil   | true
      :admin      | true  | true
      :admin      | false | false
    end

    with_them do
      let(:current_user) { public_send(role) }

      before do
        enable_admin_mode!(current_user) if admin_mode
      end

      it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
    end
  end

  describe ':read_group_audit_events' do
    let(:policy) { :read_group_audit_events }

    where(:role, :admin_mode, :allowed) do
      :guest      | nil   | false
      :planner    | nil   | false
      :reporter   | nil   | false
      :developer  | nil   | true
      :maintainer | nil   | true
      :owner      | nil   | true
      :admin      | true  | true
      :admin      | false | false
    end

    with_them do
      let(:current_user) { public_send(role) }

      before do
        enable_admin_mode!(current_user) if admin_mode
      end

      it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
    end
  end

  context 'when group is read only' do
    let(:current_user) { owner }
    let(:policies) do
      %i[create_epic update_epic admin_pipeline register_group_runners add_cluster create_cluster
        update_cluster admin_cluster create_deploy_token create_subgroup create_package]
    end

    before do
      allow(group).to receive(:read_only?).and_return(read_only)
      stub_licensed_features(epics: true)
    end

    context 'when the group is read only' do
      let(:read_only) { true }

      it { is_expected.to(be_disallowed(*policies)) }
      it { is_expected.to(be_allowed(:read_billable_member)) }
    end

    context 'when the group is not read only' do
      let(:read_only) { false }

      it { is_expected.to(be_allowed(*policies)) }
    end
  end

  context 'under .com', :saas do
    it_behaves_like 'model with wiki policies' do
      let_it_be_with_refind(:container) { create(:group_with_plan, plan: :premium_plan) }
      let_it_be(:user) { owner }

      before_all do
        create(:license, plan: License::PREMIUM_PLAN)
      end

      before do
        enable_namespace_license_check!
      end

      def set_access_level(access_level)
        container.group_feature.update_attribute(:wiki_access_level, access_level)
      end

      context 'when the feature is not licensed on this group' do
        let_it_be(:container) { create(:group_with_plan, plan: :bronze_plan) }

        it 'does not include the wiki permissions' do
          expect_disallowed(*wiki_permissions[:all])
        end
      end
    end
  end

  it_behaves_like 'update namespace limit policy'

  context 'group access tokens', :saas do
    context 'GitLab.com Core resource access tokens', :saas do
      before do
        stub_ee_application_setting(should_check_namespace_plan: true)
      end

      context 'with owner access' do
        let(:current_user) { owner }

        it { is_expected.not_to be_allowed(:create_resource_access_tokens) }
        it { is_expected.not_to be_allowed(:admin_setting_to_allow_resource_access_token_creation) }
        it { is_expected.to be_allowed(:read_resource_access_tokens) }
        it { is_expected.to be_allowed(:destroy_resource_access_tokens) }
      end
    end

    context 'on GitLab.com paid' do
      let_it_be(:group) { create(:group_with_plan, plan: :bronze_plan) }

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

        before do
          group.add_owner(owner)
        end

        it_behaves_like 'GitLab.com Paid plan resource access tokens'

        context 'create resource access tokens' do
          it { is_expected.to be_allowed(:create_resource_access_tokens) }

          context 'when resource access token creation is not allowed' do
            before do
              group.namespace_settings.update_column(:resource_access_token_creation_allowed, false)
            end

            it { is_expected.not_to be_allowed(:create_resource_access_tokens) }
          end

          context 'when parent group has resource access token creation disabled' do
            let(:namespace_settings) { create(:namespace_settings, resource_access_token_creation_allowed: false) }
            let(:parent) { create(:group_with_plan, plan: :bronze_plan, namespace_settings: namespace_settings) }
            let(:group) { create(:group, parent: parent) }

            context 'cannot create resource access tokens' do
              it { is_expected.not_to be_allowed(:create_resource_access_tokens) }
            end

            context 'can render admin settings for resource access token' do
              it { is_expected.to be_allowed(:admin_setting_to_allow_resource_access_token_creation) }
            end
          end
        end

        context 'read resource access tokens' do
          it { is_expected.to be_allowed(:read_resource_access_tokens) }
        end

        context 'destroy resource access tokens' do
          it { is_expected.to be_allowed(:destroy_resource_access_tokens) }
        end

        context 'admin settings `allow resource access token` is allowed' do
          it { is_expected.to be_allowed(:admin_setting_to_allow_resource_access_token_creation) }
        end
      end

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

        before do
          group.add_developer(developer)
        end

        context 'create resource access tokens' do
          it { is_expected.not_to be_allowed(:create_resource_access_tokens) }
        end

        context 'read resource access tokens' do
          it { is_expected.not_to be_allowed(:read_resource_access_tokens) }
        end

        context 'destroy resource access tokens' do
          it { is_expected.not_to be_allowed(:destroy_resource_access_tokens) }
        end
      end
    end
  end

  describe ':read_group_release_stats' do
    shared_examples 'read_group_release_stats permissions' do
      context 'when user is logged out' do
        let(:current_user) { nil }

        it { is_expected.to be_disallowed(:read_group_release_stats) }
      end

      context 'when user is not a member of the group' do
        let(:current_user) { create(:user) }

        it { is_expected.to be_disallowed(:read_group_release_stats) }
      end

      context 'when user is guest' do
        let(:current_user) { guest }

        it { is_expected.to be_allowed(:read_group_release_stats) }
      end
    end

    context 'when group is private' do
      it_behaves_like 'read_group_release_stats permissions'
    end

    context 'when group is public' do
      let(:group) { create(:group, :public) }

      before do
        group.add_guest(guest)
      end

      it_behaves_like 'read_group_release_stats permissions'
    end

    describe ':admin_merge_request_approval_settings' do
      let(:policy) { :admin_merge_request_approval_settings }

      where(:role, :licensed, :admin_mode, :root_group, :allowed) do
        :guest      | true  | nil   | true  | false
        :guest      | false | nil   | true  | false
        :planner    | true  | nil   | true  | false
        :planner    | false | nil   | true  | false
        :reporter   | true  | nil   | true  | false
        :reporter   | false | nil   | true  | false
        :developer  | true  | nil   | true  | false
        :developer  | false | nil   | true  | false
        :maintainer | true  | nil   | true  | false
        :maintainer | false | nil   | true  | false
        :owner      | true  | nil   | true  | true
        :owner      | true  | nil   | false | false
        :owner      | false | nil   | true  | false
        :admin      | true  | true  | true  | true
        :admin      | true  | true  | false | false
        :admin      | false | true  | true  | false
        :admin      | true  | false | true  | false
        :admin      | false | false | true  | false
      end

      with_them do
        let(:current_user) { public_send(role) }

        before do
          stub_licensed_features(merge_request_approvers: licensed)
          enable_admin_mode!(current_user) if admin_mode
          group.parent = build(:group) unless root_group
        end

        it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
      end
    end

    describe 'custom roles' do
      let(:license) { :custom_roles }

      context 'when on self-managed' do
        let(:current_user) { owner }

        before do
          stub_licensed_features(license => true)
        end

        it { is_expected.to be_disallowed(:admin_member_role) }
      end

      describe ':admin_member_role', :saas do
        using RSpec::Parameterized::TableSyntax

        where(:role, :allowed) do
          :guest      | false
          :planner    | false
          :reporter   | false
          :developer  | false
          :maintainer | false
          :auditor    | false
          :owner      | true
          :admin      | true
        end

        with_them do
          let(:current_user) { public_send(role) }

          before do
            enable_admin_mode!(current_user) if role == :admin
          end

          context 'custom roles license' do
            let(:permissions) { [:admin_member_role, :view_member_roles] }

            context 'when licensed feature is enabled' do
              before do
                stub_licensed_features(license => true)
              end

              it { is_expected.to(allowed ? be_allowed(*permissions) : be_disallowed(*permissions)) }

              context 'when memberships are locked to LDAP' do
                before do
                  allow(group).to receive(:ldap_synced?).and_return(true)
                  stub_application_setting(allow_group_owners_to_manage_ldap: true)
                  stub_application_setting(lock_memberships_to_ldap: true)
                end

                it { is_expected.to(allowed ? be_allowed(*permissions) : be_disallowed(*permissions)) }
              end
            end

            context 'when licensed feature is disabled' do
              before do
                stub_licensed_features(license => false)
              end

              it { is_expected.to be_disallowed(*permissions) }
            end
          end

          context 'default roles assignees license' do
            let(:license) { :default_roles_assignees }
            let(:permissions) { [:view_member_roles] }

            context 'when licensed feature is enabled' do
              before do
                stub_licensed_features(license => true)
              end

              it { is_expected.to(allowed ? be_allowed(*permissions) : be_disallowed(*permissions)) }
            end

            context 'when licensed feature is disabled' do
              before do
                stub_licensed_features(license => false)
              end

              it { is_expected.to be_disallowed(*permissions) }
            end
          end
        end
      end

      describe ':read_member_role' do
        using RSpec::Parameterized::TableSyntax

        let(:permissions) { [:read_member_role] }

        where(:role, :allowed) do
          :guest      | true
          :planner    | true
          :reporter   | true
          :developer  | true
          :maintainer | true
          :auditor    | false
          :owner      | true
          :admin      | true
        end

        with_them do
          let(:current_user) { public_send(role) }

          before do
            enable_admin_mode!(current_user) if role == :admin
          end

          context 'when custom_roles feature is enabled' do
            before do
              stub_licensed_features(custom_roles: true)
            end

            it { is_expected.to(allowed ? be_allowed(*permissions) : be_disallowed(*permissions)) }

            context 'when memberships are locked to LDAP' do
              before do
                allow(group).to receive(:ldap_synced?).and_return(true)
                stub_application_setting(allow_group_owners_to_manage_ldap: true)
                stub_application_setting(lock_memberships_to_ldap: true)
              end

              it { is_expected.to(allowed ? be_allowed(*permissions) : be_disallowed(*permissions)) }
            end
          end

          context 'when custom_roles feature is disabled' do
            before do
              stub_licensed_features(custom_roles: false)
            end

            it { is_expected.to be_disallowed(*permissions) }
          end
        end
      end
    end

    describe ':start_trial' do
      let(:policy) { :start_trial }

      where(:role, :eligible_for_trial, :admin_mode, :allowed) do
        :guest      | true  | nil   | false
        :guest      | false | nil   | false
        :planner    | true  | nil   | false
        :planner    | false | nil   | false
        :reporter   | true  | nil   | false
        :reporter   | false | nil   | false
        :developer  | true  | nil   | false
        :developer  | false | nil   | false
        :maintainer | true  | nil   | true
        :maintainer | false | nil   | false
        :owner      | true  | nil   | true
        :owner      | false | nil   | false
        :admin      | true  | true  | true
        :admin      | false | true  | false
        :admin      | true  | false | false
        :admin      | false | false | false
      end

      with_them do
        let(:current_user) { public_send(role) }

        before do
          allow(group).to receive(:eligible_for_trial?).and_return(eligible_for_trial)
          enable_admin_mode!(current_user) if admin_mode
        end

        it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
      end
    end
  end

  describe 'compliance framework permissions' do
    shared_examples 'compliance framework permissions' do
      where(:role, :licensed, :admin_mode, :allowed) do
        :owner      | true  | nil   | true
        :owner      | false | nil   | false
        :admin      | true  | true  | true
        :admin      | true  | false | false
        :maintainer | true  | nil   | false
        :developer  | true  | nil   | false
        :reporter   | true  | nil   | false
        :planner    | true  | nil   | false
        :guest      | true  | nil   | false
      end

      with_them do
        let(:current_user) { public_send(role) }

        before do
          stub_licensed_features(licensed_feature => licensed)
          enable_admin_mode!(current_user) if admin_mode
        end

        it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
      end
    end

    context ':admin_compliance_framework' do
      let(:policy) { :admin_compliance_framework }
      let(:licensed_feature) { :custom_compliance_frameworks }
      let(:feature_flag_name) { nil }

      include_examples 'compliance framework permissions'
    end

    context ':admin_compliance_pipeline_configuration' do
      let(:policy) { :admin_compliance_pipeline_configuration }
      let(:licensed_feature) { :evaluate_group_level_compliance_pipeline }

      include_examples 'compliance framework permissions'
    end
  end

  describe 'view_devops_adoption' do
    let(:current_user) { owner }
    let(:policy) { :view_group_devops_adoption }

    context 'when license does not include the feature' do
      let(:current_user) { admin }

      before do
        stub_licensed_features(group_level_devops_adoption: false)
        enable_admin_mode!(current_user)
      end

      it { is_expected.to be_disallowed(policy) }
    end

    context 'when license includes the feature' do
      where(:role, :allowed) do
        :admin            | true
        :owner            | true
        :maintainer       | true
        :developer        | true
        :reporter         | true
        :planner          | false
        :guest            | false
        :non_group_member | false
        :auditor          | true
      end

      before do
        stub_licensed_features(group_level_devops_adoption: true)
        enable_admin_mode!(current_user) if current_user.admin?
      end

      with_them do
        let(:current_user) { public_send(role) }

        it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
      end
    end
  end

  describe 'manage_devops_adoption_namespaces' do
    let(:current_user) { owner }
    let(:policy) { :manage_devops_adoption_namespaces }

    context 'when license does not include the feature' do
      let(:current_user) { admin }

      before do
        stub_licensed_features(group_level_devops_adoption: false)
        enable_admin_mode!(current_user)
      end

      it { is_expected.to be_disallowed(policy) }
    end

    context 'when license includes the feature' do
      where(:role, :allowed) do
        :admin            | true
        :owner            | true
        :maintainer       | true
        :developer        | true
        :reporter         | true
        :planner          | false
        :guest            | false
        :non_group_member | false
      end

      before do
        stub_licensed_features(group_level_devops_adoption: true)
        enable_admin_mode!(current_user) if current_user.admin?
      end

      with_them do
        let(:current_user) { public_send(role) }

        it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
      end
    end

    context 'when license plan does not include the feature' do
      where(:role, :allowed) do
        :admin            | true
        :owner            | false
        :maintainer       | false
        :developer        | false
        :reporter         | false
        :planner          | false
        :guest            | false
        :non_group_member | false
      end

      before do
        stub_licensed_features(group_level_devops_adoption: true)
        allow(group).to receive(:feature_available?).with(:group_level_devops_adoption).and_return(false)
        enable_admin_mode!(current_user) if current_user.admin?
      end

      with_them do
        let(:current_user) { public_send(role) }

        it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
      end
    end
  end

  context 'external audit events' do
    let(:current_user) { owner }

    context 'when license is disabled' do
      before do
        stub_licensed_features(external_audit_events: false)
      end

      it { is_expected.to(be_disallowed(:admin_external_audit_events)) }
    end

    context 'when license is enabled' do
      before do
        stub_licensed_features(external_audit_events: true)
      end

      it { is_expected.to(be_allowed(:admin_external_audit_events)) }
    end

    context 'when user is not an owner' do
      let(:current_user) { build_stubbed(:user, :auditor) }

      it { is_expected.to(be_disallowed(:admin_external_audit_events)) }
    end
  end

  describe 'a pending membership' do
    let_it_be(:user) { create(:user) }

    context 'with a private group' do
      let_it_be(:private_group) { create(:group, :private) }

      subject { described_class.new(user, private_group) }

      where(:role) do
        Gitlab::Access.sym_options_with_owner.keys.map(&:to_sym)
      end

      with_them do
        it 'has permission identical to a private group in which the user is not a member' do
          create(:group_member, :awaiting, role, source: private_group, user: user)

          expect_private_group_permissions_as_if_non_member
        end
      end

      context 'with a project in the group' do
        let_it_be(:project) { create(:project, :private, namespace: private_group) }

        where(:role) do
          Gitlab::Access.sym_options_with_owner.keys.map(&:to_sym)
        end

        with_them do
          it 'has permission identical to a private group in which the user is not a member' do
            create(:group_member, :awaiting, role, source: private_group, user: user)

            expect_private_group_permissions_as_if_non_member
          end
        end
      end
    end

    context 'with a public group' do
      let_it_be(:public_group) { create(:group, :public) }

      subject { described_class.new(user, public_group) }

      where(:role) do
        Gitlab::Access.sym_options_with_owner.keys.map(&:to_sym)
      end

      with_them do
        it 'has permission identical to a public group in which the user is not a member' do
          create(:group_member, :awaiting, role, source: public_group, user: user)

          expect_allowed(*public_permissions)
          expect_disallowed(:upload_file)
          expect_disallowed(*reporter_permissions)
          expect_disallowed(*developer_permissions)
          expect_disallowed(*maintainer_permissions)
          expect_disallowed(*owner_permissions)
          expect_disallowed(:read_namespace_via_membership)
        end
      end
    end

    context 'with a group invited to another group' do
      let_it_be(:group) { create(:group, :public) }
      let_it_be(:other_group) { create(:group, :private) }

      subject { described_class.new(user, other_group) }

      before_all do
        create(:group_group_link, { shared_with_group: group, shared_group: other_group })
      end

      where(:role) do
        Gitlab::Access.sym_options_with_owner.keys.map(&:to_sym)
      end

      with_them do
        it 'has permission to the other group as if the user is not a member' do
          create(:group_member, :awaiting, role, source: group, user: user)

          expect_private_group_permissions_as_if_non_member
        end
      end
    end

    def expect_private_group_permissions_as_if_non_member
      expect_disallowed(*public_permissions)
      expect_disallowed(*guest_permissions)
      expect_disallowed(*reporter_permissions)
      expect_disallowed(*developer_permissions)
      expect_disallowed(*maintainer_permissions)
      expect_disallowed(*owner_permissions)
    end
  end

  describe 'security complience policy' do
    context 'when licensed feature is available' do
      before do
        stub_licensed_features(security_orchestration_policies: false)
      end

      context 'with developer or maintainer role' do
        where(role: %w[maintainer developer])

        with_them do
          let(:current_user) { public_send(role) }

          it { is_expected.to be_disallowed(:read_security_orchestration_policies) }
          it { is_expected.to be_disallowed(:update_security_orchestration_policy_project) }
        end
      end

      context 'with owner role' do
        where(role: %w[owner])

        with_them do
          let(:current_user) { public_send(role) }

          it { is_expected.to be_disallowed(:read_security_orchestration_policies) }
          it { is_expected.to be_disallowed(:update_security_orchestration_policy_project) }
          it { is_expected.to be_disallowed(:modify_security_policy) }
        end
      end
    end

    context 'when licensed feature is available' do
      before do
        stub_licensed_features(security_orchestration_policies: true)
      end

      context 'when security_orchestration_policy_configuration is not present' do
        context 'with developer or maintainer role' do
          where(role: %w[maintainer developer])

          with_them do
            let(:current_user) { public_send(role) }

            it { is_expected.to be_allowed(:read_security_orchestration_policies) }
            it { is_expected.to be_disallowed(:update_security_orchestration_policy_project) }
          end
        end

        context 'with owner role' do
          where(role: %w[owner])

          with_them do
            let(:current_user) { public_send(role) }

            it { is_expected.to be_allowed(:read_security_orchestration_policies) }
            it { is_expected.to be_allowed(:update_security_orchestration_policy_project) }
            it { is_expected.to be_allowed(:modify_security_policy) }

            context 'when security_orchestration_policy_configuration is present' do
              let_it_be(:security_policy_management_project) { create(:project) }

              before do
                create(:security_orchestration_policy_configuration, project: nil, namespace: group, security_policy_management_project: security_policy_management_project)
              end

              it { is_expected.to be_disallowed(:modify_security_policy) }
            end
          end
        end
      end

      context 'when security_orchestration_policy_configuration is present' do
        let_it_be(:security_policy_management_project) { create(:project) }
        let(:current_user) { developer }

        before do
          create(:security_orchestration_policy_configuration, project: nil, namespace: group, security_policy_management_project: security_policy_management_project)
        end

        context 'when current_user is developer of security_policy_management_project' do
          before do
            security_policy_management_project.add_developer(developer)
          end

          it { is_expected.to be_allowed(:modify_security_policy) }
        end

        context 'when current_user is not developer of security_policy_management_project' do
          it { is_expected.to be_disallowed(:modify_security_policy) }
        end
      end
    end
  end

  describe 'read_usage_quotas policy' do
    context 'reading usage quotas' do
      let(:policy) { :read_usage_quotas }

      where(:role, :admin_mode, :allowed) do
        :owner      | nil   | true
        :admin      | true  | true
        :admin      | false | false
        :maintainer | nil   | false
        :developer  | nil   | false
        :reporter   | nil   | false
        :planner    | nil   | false
        :guest      | nil   | false
      end

      with_them do
        let(:current_user) { public_send(role) }

        before do
          enable_admin_mode!(current_user) if admin_mode
        end

        it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
      end
    end
  end

  describe 'dependency proxy' do
    context 'feature enabled' do
      before do
        stub_config(dependency_proxy: { enabled: true })
      end

      context 'auditor' do
        let(:current_user) { auditor }

        it { is_expected.to be_allowed(:read_dependency_proxy) }
        it { is_expected.to be_disallowed(:admin_dependency_proxy) }
      end
    end
  end

  describe 'read wiki' do
    context 'feature enabled' do
      before do
        stub_licensed_features(group_wikis: true)
      end

      context 'auditor' do
        let(:current_user) { auditor }

        it { is_expected.to be_allowed(:read_wiki) }
        it { is_expected.to be_disallowed(:admin_wiki) }
      end
    end

    context 'feature disabled' do
      before do
        stub_licensed_features(group_wikis: false)
      end

      context 'auditor' do
        let(:current_user) { auditor }

        it { is_expected.to be_disallowed(:read_wiki) }
        it { is_expected.to be_disallowed(:admin_wiki) }
      end
    end
  end

  describe 'group level compliance features' do
    shared_examples 'group level compliance feature' do |feature, permission|
      context 'when enabled' do
        before do
          stub_licensed_features({ feature => true })
        end

        context 'when user is eligible for access' do
          where(role: %w[owner auditor])

          with_them do
            let(:current_user) { public_send(role) }

            it { is_expected.to be_allowed(permission) }
          end
        end

        context 'allows admin', :enable_admin_mode do
          let(:current_user) { admin }

          it { is_expected.to be_allowed(permission) }
        end
      end

      context 'when disabled' do
        before do
          stub_licensed_features({ feature => false })
        end

        context 'when user is eligible for access' do
          where(role: %w[owner auditor])

          with_them do
            let(:current_user) { public_send(role) }

            it { is_expected.to be_disallowed(permission) }
          end
        end

        context 'disallows admin', :enable_admin_mode do
          let(:current_user) { admin }

          it { is_expected.to be_disallowed(permission) }
        end
      end
    end

    describe 'group level compliance dashboard' do
      it_behaves_like 'group level compliance feature', :group_level_compliance_dashboard, :read_compliance_dashboard
    end

    describe 'group level compliance adherence report' do
      it_behaves_like 'group level compliance feature', :group_level_compliance_adherence_report, :read_compliance_adherence_report
    end

    describe 'group level compliance violations report' do
      it_behaves_like 'group level compliance feature', :group_level_compliance_violations_report, :read_compliance_violations_report
    end
  end

  describe 'user banned from namespace' do
    let_it_be_with_reload(:current_user) { create(:user) }
    let_it_be(:group) { create(:group, :private) }

    subject { described_class.new(current_user, group) }

    before do
      stub_licensed_features(unique_project_download_limit: true)
      group.add_developer(current_user)
    end

    context 'when user is not banned' do
      it { is_expected.to be_allowed(:read_group) }
    end

    context 'when user is banned' do
      before do
        create(:namespace_ban, user: current_user, namespace: group.root_ancestor)
      end

      it { is_expected.to be_disallowed(*described_class.own_ability_map.map.keys) }

      context 'inside a subgroup' do
        let_it_be(:group) { create(:group, :private, :nested) }

        it { is_expected.to be_disallowed(*described_class.own_ability_map.map.keys) }

        context 'as an owner of the subgroup' do
          before do
            group.add_owner(current_user)
          end

          it { is_expected.to be_disallowed(*described_class.own_ability_map.map.keys) }
        end
      end

      context 'as an admin' do
        let_it_be(:current_user) { admin }

        context 'when admin mode is enabled', :enable_admin_mode do
          it { is_expected.to be_allowed(:read_group) }
        end
      end

      context 'when group is public' do
        let_it_be(:group) { create(:group, :public) }

        it { is_expected.to be_disallowed(:read_group) }
      end

      context 'when licensed feature unique_project_download_limit is not available' do
        before do
          stub_licensed_features(unique_project_download_limit: false)
        end

        it { is_expected.to be_allowed(:read_group) }
      end
    end
  end

  describe 'ban_group_member' do
    let_it_be(:user) { create(:user) }

    let(:group) { create(:group) }

    subject(:policy) { described_class.new(user, group) }

    where(:unique_project_download_limit_enabled, :is_owner, :enabled) do
      false | false | false
      false | true  | false
      true  | false | false
      true  | true  | true
    end

    with_them do
      before do
        allow(group).to receive(:unique_project_download_limit_enabled?)
          .and_return(unique_project_download_limit_enabled)
        group.add_owner(user) if is_owner
      end

      it 'has the correct value' do
        if enabled
          expect(policy).to be_allowed(:ban_group_member)
        else
          expect(policy).to be_disallowed(:ban_group_member)
        end
      end
    end
  end

  describe 'group cicd runners' do
    context 'auditor' do
      let(:current_user) { auditor }

      it { is_expected.to be_allowed(:read_group_runners) }
      it { is_expected.to be_allowed(:read_group_all_available_runners) }
      it { is_expected.to be_disallowed(:register_group_runners) }
      it { is_expected.to be_disallowed(:create_runner) }
    end
  end

  describe 'group container registry' do
    context 'auditor' do
      let(:current_user) { auditor }

      it { is_expected.to be_allowed(:read_container_image) }
      it { is_expected.to be_disallowed(:admin_container_image) }
    end
  end

  describe 'admin_service_accounts' do
    context 'when the feature is not enabled' do
      let(:current_user) { owner }

      it { is_expected.to be_disallowed(:admin_service_accounts) }
      it { is_expected.to be_disallowed(:admin_service_account_member) }
      it { is_expected.to be_disallowed(:create_service_account) }
      it { is_expected.to be_disallowed(:delete_service_account) }
    end

    context 'when feature is enabled' do
      before do
        stub_licensed_features(service_accounts: true)
      end

      context 'when the user is a maintainer' do
        let(:current_user) { maintainer }

        it { is_expected.to be_disallowed(:admin_service_accounts) }
        it { is_expected.to be_disallowed(:admin_service_account_member) }
        it { is_expected.to be_disallowed(:create_service_account) }
        it { is_expected.to be_disallowed(:delete_service_account) }
      end

      context 'when the user is an owner' do
        let(:current_user) { owner }

        context 'when application setting allow_top_level_group_owners_to_create_service_accounts is disabled' do
          before do
            stub_ee_application_setting(allow_top_level_group_owners_to_create_service_accounts: false)
          end

          it { is_expected.to be_allowed(:admin_service_accounts) }
          it { is_expected.to be_allowed(:admin_service_account_member) }
          it { is_expected.to be_disallowed(:create_service_account) }
          it { is_expected.to be_disallowed(:delete_service_account) }

          context 'when saas', :saas do
            it { is_expected.to be_allowed(:admin_service_accounts) }
            it { is_expected.to be_allowed(:admin_service_account_member) }
            it { is_expected.to be_disallowed(:create_service_account) }
            it { is_expected.to be_disallowed(:delete_service_account) }
          end
        end

        context 'when application setting allow_top_level_group_owners_to_create_service_accounts is enabled ' do
          before do
            stub_ee_application_setting(allow_top_level_group_owners_to_create_service_accounts: true)
          end

          it { is_expected.to be_allowed(:admin_service_accounts) }
          it { is_expected.to be_allowed(:admin_service_account_member) }
          it { is_expected.to be_allowed(:create_service_account) }
          it { is_expected.to be_allowed(:delete_service_account) }

          context 'when saas', :saas do
            it { is_expected.to be_allowed(:admin_service_accounts) }
            it { is_expected.to be_allowed(:admin_service_account_member) }
            it { is_expected.to be_allowed(:create_service_account) }
            it { is_expected.to be_allowed(:delete_service_account) }

            context 'when trial is active' do
              before do
                allow(group).to receive_messages(trial_active?: true)
              end

              it { is_expected.to be_disallowed(:admin_service_accounts) }
              it { is_expected.to be_disallowed(:admin_service_account_member) }
              it { is_expected.to be_disallowed(:create_service_account) }
              it { is_expected.to be_disallowed(:delete_service_account) }
            end
          end

          context 'when a trial is active' do
            before do
              allow(group).to receive_messages(gitlab_subscription: nil)
            end

            it { is_expected.to be_allowed(:admin_service_accounts) }
            it { is_expected.to be_allowed(:admin_service_account_member) }
            it { is_expected.to be_allowed(:create_service_account) }
            it { is_expected.to be_allowed(:delete_service_account) }
          end

          context 'for subgroup' do
            let_it_be(:subgroup) { create(:group, :private, parent: group) }

            subject { described_class.new(current_user, subgroup) }

            it { is_expected.to be_allowed(:admin_service_accounts) }
            it { is_expected.to be_allowed(:admin_service_account_member) }
            it { is_expected.to be_disallowed(:create_service_account) }
            it { is_expected.to be_disallowed(:delete_service_account) }
          end
        end

        context 'for subgroup' do
          let_it_be(:subgroup) { create(:group, :private, parent: group) }

          subject { described_class.new(current_user, subgroup) }

          it { is_expected.to be_allowed(:admin_service_accounts) }
          it { is_expected.to be_allowed(:admin_service_account_member) }
          it { is_expected.to be_disallowed(:create_service_account) }
          it { is_expected.to be_disallowed(:delete_service_account) }

          context 'when a trial is active in GitLab.com', :saas do
            before do
              allow(subgroup.root_ancestor).to receive_messages(trial_active?: true)
            end

            it { is_expected.to be_disallowed(:admin_service_accounts) }
            it { is_expected.to be_disallowed(:admin_service_account_member) }
            it { is_expected.to be_disallowed(:create_service_account) }
            it { is_expected.to be_disallowed(:delete_service_account) }
          end
        end
      end

      context 'when the user is an instance admin' do
        let(:current_user) { admin }

        context 'when admin mode is enabled', :enable_admin_mode do
          it { is_expected.to be_allowed(:admin_service_accounts) }
          it { is_expected.to be_allowed(:admin_service_account_member) }
          it { is_expected.to be_allowed(:create_service_account) }
          it { is_expected.to be_allowed(:delete_service_account) }

          context 'when a trial is active' do
            before do
              allow(group).to receive_messages(gitlab_subscription: nil)
            end

            it { is_expected.to be_allowed(:admin_service_accounts) }
            it { is_expected.to be_allowed(:admin_service_account_member) }
            it { is_expected.to be_allowed(:create_service_account) }
            it { is_expected.to be_allowed(:delete_service_account) }
          end

          context 'for subgroup' do
            let_it_be(:subgroup) { create(:group, :private, parent: group) }

            subject { described_class.new(current_user, subgroup) }

            it { is_expected.to be_allowed(:admin_service_accounts) }
            it { is_expected.to be_allowed(:admin_service_account_member) }
            it { is_expected.to be_disallowed(:create_service_account) }
            it { is_expected.to be_disallowed(:delete_service_account) }
          end
        end

        context 'when admin mode is not enabled' do
          it { is_expected.to be_disallowed(:admin_service_accounts) }
          it { is_expected.to be_disallowed(:admin_service_account_member) }
          it { is_expected.to be_disallowed(:create_service_account) }
          it { is_expected.to be_disallowed(:delete_service_account) }
        end
      end
    end
  end

  describe 'access_duo_chat' do
    let_it_be(:current_user) { create(:user) }

    subject { described_class.new(current_user, group) }

    context 'when on SaaS instance', :saas do
      let_it_be_with_reload(:group) { create(:group_with_plan, plan: :premium_plan) }

      context 'when container is a group with AI enabled' do
        include_context 'with duo features enabled and ai chat available for group on SaaS'

        context 'when user is a member of the group' do
          before do
            group.add_guest(current_user)
          end

          it { is_expected.to be_allowed(:access_duo_chat) }

          context 'when the group does not have an Premium SaaS license' do
            let_it_be(:group) { create(:group) }

            it { is_expected.to be_disallowed(:access_duo_chat) }
          end
        end

        context 'when the user is not a member but has AI enabled via another group' do
          context 'user can view group' do
            it 'is allowed' do
              is_expected.to be_allowed(:access_duo_chat)
            end
          end

          context 'user cannot view group' do
            before do
              group.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
            end

            it 'is not allowed' do
              is_expected.to be_disallowed(:access_duo_chat)
            end
          end
        end
      end
    end

    context 'for self-managed', :with_cloud_connector do
      let_it_be_with_reload(:group) { create(:group) }
      let(:policy) { :access_duo_chat }

      context 'when not on .org or .com' do
        where(:enabled_for_user, :duo_features_enabled, :cs_matcher) do
          true  | false | be_disallowed(policy)
          true  | true  | be_allowed(policy)
          false | false | be_disallowed(policy)
          false | true  | be_disallowed(policy)
        end

        with_them do
          before do
            allow(::Gitlab).to receive(:org_or_com?).and_return(false)
            stub_ee_application_setting(duo_features_enabled: duo_features_enabled, lock_duo_features_enabled: true)
            allow(Ability).to receive(:allowed?).and_call_original
            allow(Ability).to receive(:allowed?).with(current_user, :access_duo_chat).and_return(enabled_for_user)
          end

          it { is_expected.to cs_matcher }
        end
      end
    end
  end

  context 'access_duo_features' do
    where(:current_user, :duo_features_enabled, :cs_matcher) do
      ref(:guest) | true | be_allowed(:access_duo_features)
      ref(:guest) | false | be_disallowed(:access_duo_features)
      nil | true | be_disallowed(:access_duo_features)
      nil | false | be_disallowed(:access_duo_features)
    end

    with_them do
      before do
        group.namespace_settings.update!(duo_features_enabled: duo_features_enabled)
      end

      it do
        is_expected.to cs_matcher
      end
    end

    context 'when the group is not yet persisted' do
      subject { described_class.new(admin, build(:group)) }

      it { is_expected.to be_disallowed(:access_duo_features) }
    end
  end

  describe 'access to group for duo workflow' do
    let_it_be_with_reload(:group) { create(:group, :public) }

    where(:current_user, :token_info, :duo_features_enabled, :cs_matcher) do
      ref(:guest)      | nil                               | true  | be_allowed(:read_group)
      ref(:guest)      | { token_scopes: [:ai_workflows] } | true  | be_allowed(:read_group)
      ref(:guest)      | { token_scopes: [:ai_workflows] } | false | be_disallowed(:read_group, :admin_group)
      ref(:guest)      | { token_scopes: [:other_scope] }  | true  | be_allowed(:read_group)
      ref(:maintainer) | { token_scopes: [:ai_workflows] } | false | be_disallowed(:read_group, :admin_group)
    end

    with_them do
      before do
        group.namespace_settings.update!(duo_features_enabled: duo_features_enabled)
        ::Current.token_info = token_info
      end

      it { is_expected.to cs_matcher }
    end
  end

  describe 'access_duo_core_features' do
    let_it_be(:current_user) { create(:user) }

    context 'when on GitLab.com', :saas do
      before do
        stub_ee_application_setting(should_check_namespace_plan: true)
      end

      subject { described_class.new(current_user, group) }

      context 'with a group with Duo Core enabled' do
        before do
          group.namespace_settings.reload.update!(duo_core_features_enabled: true)
        end

        context 'with duo core addon' do
          include_context 'with duo core addon'

          context 'when the group is not yet persisted' do
            subject { described_class.new(admin, build(:group)) }

            it { is_expected.to be_disallowed(:access_duo_core_features) }
          end

          context 'when user is a guest' do
            before do
              group.add_guest(current_user)
            end

            it { is_expected.to be_disallowed(:access_duo_core_features) }
          end

          context 'when user is a member of the group' do
            where(:access_level) { %i[reporter developer maintainer owner] }

            with_them do
              before do
                group.add_member(current_user, access_level)
              end

              it { is_expected.to be_allowed(:access_duo_core_features) }
            end
          end

          context 'when user is a member of a subgroup' do
            let(:subgroup) { create(:group, :private, parent: group) }

            subject { described_class.new(current_user, subgroup) }

            before do
              subgroup.add_reporter(current_user)
            end

            it { is_expected.to be_allowed(:access_duo_core_features) }
          end
        end

        ['with duo pro addon', 'with duo enterprise addon'].each do |context|
          context context do
            include_context context

            it { is_expected.to be_disallowed(:access_duo_core_features) }
          end
        end
      end

      context 'with a group with Duo Core disabled' do
        before do
          group.namespace_settings.reload.update!(duo_core_features_enabled: false)
          group.add_member(current_user, :owner)
        end

        ['with duo core addon', 'with duo pro addon', 'with duo enterprise addon'].each do |context|
          context context do
            include_context context

            it { is_expected.to be_disallowed(:access_duo_core_features) }
          end
        end
      end
    end
  end

  describe ':read_saml_user' do
    let_it_be(:user) { non_group_member }
    let_it_be(:subgroup) { create(:group, :private, parent: group) }

    subject(:policy) { described_class.new(user, the_group) }

    context 'when a SAML provider does not exist' do
      let_it_be(:the_group) { subgroup }

      before do
        stub_licensed_features(group_saml: true)
        the_group.add_member(user, Gitlab::Access::OWNER)
      end

      it { is_expected.to be_disallowed(:read_saml_user) }
    end

    context 'when a SAML provider exists' do
      before_all do
        create(:saml_provider, group: group)
      end
      where(:the_group, :licensed, :saml_enabled, :sso_enforced, :role, :allowed) do
        ref(:group)     | false | false | false | Gitlab::Access::OWNER      | false
        ref(:group)     | false | false | false | Gitlab::Access::MAINTAINER | false

        ref(:group)     | true  | false | false | Gitlab::Access::OWNER      | false
        ref(:group)     | true  | false | false | Gitlab::Access::MAINTAINER | false

        ref(:group)     | true  | true  | false | Gitlab::Access::OWNER      | false
        ref(:group)     | true  | true  | false | Gitlab::Access::MAINTAINER | false

        ref(:group)     | true  | true  | true  | Gitlab::Access::OWNER      | true
        ref(:group)     | true  | true  | true  | Gitlab::Access::MAINTAINER | false

        ref(:subgroup)  | false | false | false | Gitlab::Access::OWNER      | false
        ref(:subgroup)  | false | false | false | Gitlab::Access::MAINTAINER | false

        ref(:subgroup)  | true  | false | false | Gitlab::Access::OWNER      | false
        ref(:subgroup)  | true  | false | false | Gitlab::Access::MAINTAINER | false

        ref(:subgroup)  | true  | true  | false | Gitlab::Access::OWNER      | false
        ref(:subgroup)  | true  | true  | false | Gitlab::Access::MAINTAINER | false

        ref(:subgroup)  | true  | true  | true  | Gitlab::Access::OWNER      | true
        ref(:subgroup)  | true  | true  | true  | Gitlab::Access::MAINTAINER | false
      end

      with_them do
        before do
          stub_licensed_features(group_saml: licensed)
          the_group.add_member(user, role)
          the_group.root_ancestor.saml_provider.update!(enabled: saml_enabled)
          the_group.root_ancestor.saml_provider.update!(enforced_sso: sso_enforced)
        end

        it { expect(policy.allowed?(:read_saml_user)).to eq(allowed) }
      end
    end
  end

  context 'custom role' do
    let_it_be(:guest) { create(:user) }
    let_it_be(:parent_group) { create(:group) }
    let_it_be(:group) { create(:group, parent: parent_group) }

    let_it_be(:parent_group_member_guest) do
      create(
        :group_member,
        user: guest,
        source: parent_group,
        access_level: Gitlab::Access::GUEST
      )
    end

    let_it_be(:group_member_guest) do
      create(
        :group_member,
        user: guest,
        source: group,
        access_level: Gitlab::Access::GUEST
      )
    end

    let(:member_role_abilities) { {} }
    let(:allowed_abilities) { [] }
    let(:disallowed_abilities) { [] }
    let(:licensed_features) { {} }
    let(:current_user) { guest }

    def create_member_role(member, abilities = member_role_abilities)
      params = abilities.merge(namespace: parent_group)

      create(:member_role, :guest, params).tap do |role|
        role.members << member
      end
    end

    shared_examples 'custom roles abilities' do
      subject { described_class.new(current_user, group) }

      context 'without custom_roles license disabled' do
        before do
          create_member_role(group_member_guest)

          stub_licensed_features(licensed_features.merge(custom_roles: false))
        end

        it { expect_disallowed(*allowed_abilities) }
      end

      context 'with custom_roles license enabled' do
        before do
          stub_licensed_features(licensed_features.merge(custom_roles: true))
        end

        context 'custom role for parent group membership' do
          context 'when a role enables the abilities' do
            before do
              create_member_role(parent_group_member_guest)
            end

            it { expect_allowed(*allowed_abilities) }
            it { expect_disallowed(*disallowed_abilities) }
          end

          context 'when a role does not enable the abilities' do
            it { expect_disallowed(*allowed_abilities) }
          end
        end

        context 'custom role on group membership' do
          context 'when a role enables the abilities' do
            before do
              create_member_role(group_member_guest)
            end

            it { expect_allowed(*allowed_abilities) }
            it { expect_disallowed(*disallowed_abilities) }
          end

          context 'when a role does not enable the abilities' do
            it { expect_disallowed(*allowed_abilities) }
          end
        end
      end
    end

    context 'for a member role with read_vulnerability true' do
      let(:member_role_abilities) { { read_vulnerability: true } }
      let(:allowed_abilities) { [:read_group_security_dashboard] }

      it_behaves_like 'custom roles abilities'

      it 'does not enable to admin_vulnerability' do
        expect(subject).to be_disallowed(:admin_vulnerability)
      end

      it { is_expected.to be_disallowed(:read_dependency) }
    end

    context 'for a member role with admin_vulnerability true' do
      let(:member_role_abilities) { { read_vulnerability: true, admin_vulnerability: true } }
      let(:allowed_abilities) { [:read_group_security_dashboard, :read_vulnerability, :admin_vulnerability] }

      it_behaves_like 'custom roles abilities'
    end

    context 'for a member role with read_dependency true' do
      let(:member_role_abilities) { { read_dependency: true } }
      let(:allowed_abilities) { [:read_dependency, :read_licenses] }

      it_behaves_like 'custom roles abilities'
    end

    context 'for a member role with admin_group_member true' do
      let(:member_role_abilities) { { admin_group_member: true } }
      let(:allowed_abilities) { [:admin_group_member] }
      let(:disallowed_abilities) { [:activate_group_member] }

      it_behaves_like 'custom roles abilities'

      context 'admin_service_account_member' do
        let_it_be(:guest) { create(:user) }
        let_it_be(:group) { create(:group) }

        let_it_be(:group_member_guest) do
          create(
            :group_member,
            user: guest,
            source: group,
            access_level: Gitlab::Access::GUEST
          )
        end

        let(:role) do
          create(
            :member_role,
            :guest,
            namespace: group,
            admin_group_member: true
          )
        end

        before do
          role.members << group_member_guest
          stub_licensed_features(custom_roles: true)
        end

        it { is_expected.to be_disallowed(:admin_service_account_member) }

        context 'when service accounts feature enabled' do
          before do
            stub_licensed_features(custom_roles: true, service_accounts: true)
          end

          it { is_expected.to be_allowed(:admin_service_account_member) }
        end
      end
    end

    context 'for a member role with manage_group_access_tokens true' do
      let(:member_role_abilities) { { manage_group_access_tokens: true } }
      let(:allowed_abilities) do
        [:read_resource_access_tokens, :destroy_resource_access_tokens,
         :create_resource_access_tokens, :manage_resource_access_tokens]
      end

      it_behaves_like 'custom roles abilities'

      context 'when resource access token creation is not allowed' do
        before do
          create_member_role(group_member_guest)
          stub_licensed_features(custom_roles: true)
          group.root_ancestor.namespace_settings.update_column(:resource_access_token_creation_allowed, false)
        end

        it { is_expected.to be_allowed(:read_resource_access_tokens, :destroy_resource_access_tokens) }
        it { is_expected.to be_disallowed(:create_resource_access_tokens, :manage_resource_access_tokens) }
      end

      context 'when resource access tokens feature is unavailable' do
        before do
          create_member_role(group_member_guest)
          stub_licensed_features(custom_roles: true)
          stub_ee_application_setting(personal_access_tokens_disabled?: true)
        end

        it { is_expected.to be_disallowed(*allowed_abilities) }
      end
    end

    context 'for a custom role with the `admin_cicd_variables` ability' do
      let(:member_role_abilities) { { admin_cicd_variables: true } }
      let(:allowed_abilities) { [:admin_cicd_variables] }

      it_behaves_like 'custom roles abilities'
    end

    context 'for a custom role with the `admin_protected_environments` ability' do
      let(:member_role_abilities) { { admin_protected_environments: true } }
      let(:allowed_abilities) { [:admin_protected_environments] }

      it_behaves_like 'custom roles abilities'
    end

    context 'for a member role with admin_compliance_framework true' do
      let(:member_role_abilities) { { read_compliance_dashboard: true, admin_compliance_framework: true } }

      let(:allowed_abilities) do
        [
          :admin_compliance_framework,
          :admin_compliance_pipeline_configuration,
          :read_compliance_dashboard,
          :read_compliance_adherence_report,
          :read_compliance_violations_report
        ]
      end

      context 'when compliance framework feature is available' do
        let(:licensed_features) do
          {
            compliance_framework: true,
            custom_compliance_frameworks: true,
            evaluate_group_level_compliance_pipeline: true,
            group_level_compliance_dashboard: true,
            group_level_compliance_adherence_report: true,
            group_level_compliance_violations_report: true
          }
        end

        it_behaves_like 'custom roles abilities'
      end

      context 'when compliance framework features are unavailable' do
        before do
          create_member_role(group_member_guest)

          stub_licensed_features(
            custom_roles: true,
            custom_compliance_frameworks: false,
            evaluate_group_level_compliance_pipeline: false,
            group_level_compliance_dashboard: false,
            group_level_compliance_adherence_report: false,
            group_level_compliance_violations_report: false
          )
        end

        it { is_expected.to be_disallowed(*allowed_abilities) }
      end
    end

    context 'for a member role with read_compliance_dashboard true' do
      let(:member_role_abilities) { { read_compliance_dashboard: true } }

      let(:allowed_abilities) do
        [
          :read_compliance_dashboard,
          :read_compliance_adherence_report,
          :read_compliance_violations_report
        ]
      end

      context 'when compliance framework feature is available' do
        let(:licensed_features) do
          {
            group_level_compliance_dashboard: true,
            group_level_compliance_adherence_report: true,
            group_level_compliance_violations_report: true
          }
        end

        it_behaves_like 'custom roles abilities'
      end

      context 'for a custom role with the `admin_security_testing` ability' do
        let(:member_role_abilities) { { admin_security_testing: true } }
        let(:licensed_features) do
          { security_dashboard: true,
            secret_push_protection: true,
            group_level_compliance_dashboard: true }
        end

        let(:allowed_abilities) do
          [
            :access_security_and_compliance,
            :read_security_configuration,
            :read_group_security_dashboard,
            :read_security_resource,
            :enable_secret_push_protection
          ]
        end

        it_behaves_like 'custom roles abilities'
      end

      context 'when compliance framework feature is unavailable' do
        before do
          create_member_role(group_member_guest)

          stub_licensed_features(
            custom_roles: true,
            group_level_compliance_dashboard: false,
            group_level_compliance_adherence_report: false,
            group_level_compliance_violations_report: false
          )
        end

        it { is_expected.to be_disallowed(*allowed_abilities) }
      end
    end

    context 'for a custom role with the `remove_group` ability' do
      let(:member_role_abilities) { { remove_group: true } }
      let(:allowed_abilities) { [:remove_group, :view_edit_page] }

      it_behaves_like 'custom roles abilities'

      context 'when the group is a top level group' do
        before do
          create_member_role(parent_group_member_guest)
          stub_licensed_features(custom_roles: true)
        end

        subject { described_class.new(current_user, parent_group) }

        it { is_expected.to be_disallowed(*allowed_abilities) }
      end
    end

    context 'for a custom role with the `admin_push_rules` ability' do
      let(:member_role_abilities) { { admin_push_rules: true } }
      let(:allowed_abilities) { [:admin_push_rules] }

      it_behaves_like 'custom roles abilities'

      context 'when push rules feature is enabled' do
        before do
          stub_licensed_features(
            custom_roles: true,
            push_rules: true,
            commit_committer_check: true,
            commit_committer_name_check: true,
            reject_unsigned_commits: true,
            reject_non_dco_commits: true
          )

          create_member_role(group_member_guest)
        end

        it do
          is_expected.to be_allowed(
            :change_push_rules,
            :change_commit_committer_check,
            :change_commit_committer_name_check,
            :change_reject_unsigned_commits,
            :change_reject_non_dco_commits
          )
        end
      end
    end

    context 'for a custom role with the `manage_security_policy_link` ability' do
      let(:member_role_abilities) { { manage_security_policy_link: true } }
      let(:licensed_features) { { security_orchestration_policies: true } }

      let(:allowed_abilities) do
        [:read_security_orchestration_policies, :read_security_orchestration_policy_project,
         :update_security_orchestration_policy_project]
      end

      let(:disallowed_abilities) do
        [:modify_security_policy]
      end

      it_behaves_like 'custom roles abilities'
    end

    context 'for a member role with admin_web_hook true' do
      let(:member_role_abilities) { { admin_web_hook: true } }
      let(:allowed_abilities) { [:admin_web_hook, :read_web_hook] }

      it_behaves_like 'custom roles abilities'
    end

    context 'for a custom role with the `manage_deploy_tokens` permission' do
      let(:member_role_abilities) { { manage_deploy_tokens: true } }

      let(:allowed_abilities) do
        [:manage_deploy_tokens, :read_deploy_token, :create_deploy_token, :destroy_deploy_token, :view_edit_page]
      end

      it_behaves_like 'custom roles abilities'
    end

    context 'for a custom role with the `manage_merge_request_settings` ability' do
      let(:member_role_abilities) { { read_code: true, manage_merge_request_settings: true } }
      let(:allowed_abilities) { [:manage_merge_request_settings, :view_edit_page] }

      it_behaves_like 'custom roles abilities'

      context 'when the group is a top level group and the `merge_request_approvers` feature is available' do
        before do
          create_member_role(parent_group_member_guest)
          stub_licensed_features(custom_roles: true, merge_request_approvers: true)
        end

        subject { described_class.new(current_user, parent_group) }

        it { is_expected.to be_allowed(:admin_merge_request_approval_settings) }
      end
    end

    context 'for a member role with `admin_runners` true' do
      let(:member_role_abilities) { { admin_runners: true } }
      let(:allowed_abilities) do
        [
          :admin_runner,
          :create_runner,
          :read_group_all_available_runners,
          :read_group_runners
        ]
      end

      it_behaves_like 'custom roles abilities'
    end

    context 'for a custom role with the `admin_integrations` permission' do
      let(:member_role_abilities) { { admin_integrations: true } }

      let(:allowed_abilities) do
        [:admin_integrations]
      end

      it_behaves_like 'custom roles abilities'
    end

    context 'for a member role with read_crm_contact true' do
      let(:member_role_abilities) { { read_crm_contact: true } }
      let(:allowed_abilities) { [:read_crm_contact] }

      it_behaves_like 'custom roles abilities'
    end

    context 'for a member role with read_runners true' do
      let(:member_role_abilities) { { read_runners: true } }
      let(:allowed_abilities) { [:read_group_runners] }

      it_behaves_like 'custom roles abilities'
    end
  end

  describe ':destroy_group policy' do
    context 'when default_project_deletion_protection is set to true' do
      before do
        stub_application_setting(default_project_deletion_protection: true)
        stub_licensed_features(custom_roles: true)
      end

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

        it { is_expected.to be_allowed(:remove_group) }
      end

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

        context 'when group is empty' do
          it { is_expected.to be_allowed(:remove_group) }
        end

        context 'when group has only inactive project' do
          let_it_be(:project_marked_for_deletion) do
            create(:project, group: group, marked_for_deletion_at: Time.current)
          end

          let_it_be(:project_archived) do
            create(:project, group: group, archived: true)
          end

          it { is_expected.to be_allowed(:remove_group) }
        end

        context 'when group has at least one active project' do
          let_it_be(:project) do
            create(:project, group: group)
          end

          it { is_expected.to be_disallowed(:remove_group) }
        end
      end
    end
  end

  context 'for :read_limit_alert' do
    context 'when the user is a guest member of the group' do
      let(:current_user) { guest }

      it { is_expected.to be_allowed(:read_limit_alert) }
    end

    context 'when the user is not a member of the group' do
      let(:current_user) { non_group_member }

      it { is_expected.to be_disallowed(:read_limit_alert) }
    end
  end

  context 'saved replies permissions' do
    let(:current_user) { owner }

    context 'when no license is present' do
      before do
        stub_licensed_features(group_saved_replies: false)
      end

      it { is_expected.to be_disallowed(:read_saved_replies, :create_saved_replies, :update_saved_replies, :destroy_saved_replies) }
    end

    context 'with correct license' do
      before do
        stub_licensed_features(group_saved_replies: true)
      end

      it { is_expected.to be_allowed(:read_saved_replies, :create_saved_replies, :update_saved_replies, :destroy_saved_replies) }

      context 'when the user is a guest' do
        let(:current_user) { guest }

        it { is_expected.to be_allowed(:read_saved_replies) }

        it { is_expected.to be_disallowed(:create_saved_replies, :update_saved_replies, :destroy_saved_replies) }
      end

      context 'when the user is a reporter' do
        let(:current_user) { reporter }

        it { is_expected.to be_allowed(:read_saved_replies) }

        it { is_expected.to be_disallowed(:create_saved_replies, :update_saved_replies, :destroy_saved_replies) }
      end

      context 'when the user is a developer' do
        let(:current_user) { developer }

        it { is_expected.to be_allowed(:read_saved_replies, :create_saved_replies, :update_saved_replies, :destroy_saved_replies) }
      end

      context 'when the user is a planner' do
        let(:current_user) { planner }

        it { is_expected.to be_disallowed(:create_saved_replies, :update_saved_replies, :destroy_saved_replies) }
      end

      context 'when the user is a guest member of the group' do
        let(:current_user) { guest }

        it { is_expected.to be_disallowed(:create_saved_replies, :update_saved_replies, :destroy_saved_replies) }
      end
    end
  end

  describe 'read_runner_cloud_provisioning_info policy' do
    let(:current_user) { maintainer }

    it { is_expected.to be_disallowed(:read_runner_cloud_provisioning_info) }

    context 'when SaaS-only feature is available' do
      before do
        stub_saas_features(google_cloud_support: true)
      end

      context 'when the user is a maintainer' do
        let(:current_user) { maintainer }

        it { is_expected.to be_allowed(:read_runner_cloud_provisioning_info) }
      end

      context 'the user is a guest' do
        let(:current_user) { guest }

        it { is_expected.to be_disallowed(:read_runner_cloud_provisioning_info) }
      end
    end
  end

  describe 'read_runner_gke_provisioning_info policy' do
    let(:current_user) { maintainer }

    it { is_expected.to be_disallowed(:read_runner_gke_provisioning_info) }

    context 'when SaaS-only feature is available' do
      before do
        stub_saas_features(google_cloud_support: true)
      end

      context 'when the user is a maintainer' do
        let(:current_user) { maintainer }

        it { is_expected.to be_allowed(:read_runner_gke_provisioning_info) }
      end

      context 'the user is a guest' do
        let(:current_user) { guest }

        it { is_expected.to be_disallowed(:read_runner_gke_provisioning_info) }
      end
    end
  end

  describe 'provision_cloud_runner policy' do
    let(:current_user) { maintainer }

    it { is_expected.to be_disallowed(:provision_cloud_runner) }

    context 'when SaaS-only feature is available' do
      before do
        stub_saas_features(google_cloud_support: true)
      end

      context 'the user is a maintainer' do
        let(:current_user) { maintainer }

        it { is_expected.to be_allowed(:provision_cloud_runner) }
      end

      context 'the user is a guest' do
        let(:current_user) { guest }

        it { is_expected.to be_disallowed(:provision_cloud_runner) }
      end
    end
  end

  describe 'provision_gke_runner policy' do
    let(:current_user) { maintainer }

    it { is_expected.to be_disallowed(:provision_gke_runner) }

    context 'when SaaS-only feature is available' do
      before do
        stub_saas_features(google_cloud_support: true)
      end

      context 'the user is a maintainer' do
        let(:current_user) { maintainer }

        it { is_expected.to be_allowed(:provision_gke_runner) }
      end

      context 'the user is a guest' do
        let(:current_user) { guest }

        it { is_expected.to be_disallowed(:provision_gke_runner) }
      end
    end
  end

  describe 'read_jobs_statistics' do
    let(:current_user) { developer }

    it { is_expected.to be_disallowed(:read_jobs_statistics) }

    context 'when runner performance insights feature is available' do
      before do
        stub_licensed_features(runner_performance_insights_for_namespace: true)
      end

      it { is_expected.to be_disallowed(:read_jobs_statistics) }

      context 'when user is a maintainer' do
        let(:current_user) { maintainer }

        it { is_expected.to be_allowed(:read_jobs_statistics) }
      end
    end
  end

  describe 'read_runner_usage' do
    where(:licensed, :current_user, :enable_admin_mode, :clickhouse_configured, :expected) do
      true  | ref(:admin)      | true  | true  | true
      false | ref(:maintainer) | false | true  | false
      true  | ref(:maintainer) | false | false | false
      true  | ref(:maintainer) | false | true  | true
      true  | ref(:auditor)    | false | true  | false
      true  | ref(:developer)  | false | true  | false
    end

    with_them do
      before do
        stub_licensed_features(runner_performance_insights_for_namespace: licensed)

        enable_admin_mode!(admin) if enable_admin_mode

        allow(::Gitlab::ClickHouse).to receive(:configured?).and_return(clickhouse_configured)
      end

      it 'matches expectation' do
        if expected
          is_expected.to be_allowed(:read_runner_usage)
        else
          is_expected.to be_disallowed(:read_runner_usage)
        end
      end
    end
  end

  describe 'web_hooks' do
    let(:current_user) { maintainer }

    it { is_expected.to be_disallowed(:read_web_hook, :admin_web_hook) }

    context 'when user is an owner' do
      let(:current_user) { owner }

      it { is_expected.to be_allowed(:read_web_hook, :admin_web_hook) }
    end
  end

  describe 'enable_secret_push_protection' do
    using RSpec::Parameterized::TableSyntax

    where(:current_user, :licensed, :match_expected_result) do
      ref(:owner)      | true  | be_allowed(:enable_secret_push_protection)
      ref(:maintainer) | true  | be_allowed(:enable_secret_push_protection)
      ref(:developer)  | true  | be_disallowed(:enable_secret_push_protection)
      ref(:owner)      | false | be_disallowed(:enable_secret_push_protection)
      ref(:maintainer) | false | be_disallowed(:enable_secret_push_protection)
      ref(:developer)  | false | be_disallowed(:enable_secret_push_protection)
    end

    with_them do
      before do
        stub_licensed_features(secret_push_protection: licensed)
      end

      it { is_expected.to match_expected_result }
    end

    describe 'when the group does not have the correct license' do
      let(:current_user) { owner }

      it { is_expected.to be_disallowed(:enable_secret_push_protection) }
    end
  end

  describe 'admin_licensed_seat' do
    context 'when user is an owner' do
      let(:current_user) { owner }

      it { is_expected.to be_allowed(:admin_licensed_seat) }
    end

    context 'when user is not an owner' do
      let(:current_user) { maintainer }

      it { is_expected.to be_disallowed(:admin_licensed_seat) }
    end
  end

  describe 'bulk_admin_epic' do
    context 'when bulk_edit_feature_available is true' do
      before do
        stub_licensed_features(epics: true, group_bulk_edit: true)
      end

      context 'when user is planner or reporter' do
        where(role: %w[planner reporter])

        with_them do
          let(:current_user) { public_send(role) }

          it { is_expected.to be_allowed(:bulk_admin_epic) }
        end
      end

      context 'when user is not reporter or better' do
        let(:current_user) { guest }

        it { is_expected.to be_disallowed(:bulk_admin_epic) }
      end
    end

    context 'when bulk_edit_feature_available is false' do
      before do
        stub_licensed_features(epics: true, group_bulk_edit: false)
      end

      context 'when user is guest, planner or reporter' do
        where(role: %w[guest planner reporter])

        with_them do
          let(:current_user) { public_send(role) }

          it { is_expected.to be_disallowed(:bulk_admin_epic) }
        end
      end
    end
  end

  describe 'generate_description' do
    context "when feature is authorized" do
      before do
        stub_licensed_features(epics: true)

        allow_next_instance_of(::Gitlab::Llm::FeatureAuthorizer) do |instance|
          allow(instance).to receive(:allowed?).and_return(true)
        end
      end

      context 'with planner+' do
        let(:current_user) { planner }

        context 'when user can create issue' do
          it { is_expected.to be_allowed(:generate_description) }
        end
      end

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

        it { is_expected.to be_disallowed(:generate_description) }
      end
    end

    context "when feature is not authorized" do
      let(:current_user) { owner }

      before do
        allow_next_instance_of(::Gitlab::Llm::FeatureAuthorizer) do |instance|
          allow(instance).to receive(:allowed?).and_return(false)
        end
      end

      it { is_expected.to be_disallowed(:generate_description) }
    end
  end

  describe 'access_ai_review_mr' do
    let(:current_user) { owner }

    where(:duo_features_enabled, :allowed_to_use, :enabled_for_user) do
      false  | false | be_disallowed(:access_ai_review_mr)
      true   | false | be_disallowed(:access_ai_review_mr)
      false  | true  | be_disallowed(:access_ai_review_mr)
      true   | true  | be_allowed(:access_ai_review_mr)
    end

    with_them do
      before do
        allow(group).to receive(:duo_features_enabled).and_return(duo_features_enabled)

        allow(current_user).to receive(:allowed_to_use?)
          .with(:review_merge_request, licensed_feature: :review_merge_request).and_return(allowed_to_use)
      end

      it { is_expected.to enabled_for_user }
    end
  end

  describe 'admin custom roles', :enable_admin_mode do
    let_it_be(:group) { create(:group, :private) }
    let_it_be(:user) { create(:user) }

    subject { described_class.new(user, group) }

    before do
      create(:admin_member_role, :read_admin_cicd, user: user)
    end

    context 'when user can read_admin_cicd' do
      context 'when custom roles feature is unavailable' do
        before do
          stub_licensed_features(custom_roles: false)
        end

        it { is_expected.to be_disallowed(:read_group_metadata) }
      end

      context 'when custom roles feature is available' do
        before do
          stub_licensed_features(custom_roles: true)
        end

        it { is_expected.to be_allowed(:read_group_metadata) }
      end
    end
  end
end
