# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Namespaces::ProjectNamespacePolicy, feature_category: :groups_and_projects do
  include ExternalAuthorizationServiceHelpers
  include AdminModeHelper

  let_it_be(:public_project) { create(:project, :public) }
  let_it_be(:private_project) { create(:project, :private) }

  subject { described_class.new(current_user, namespace) }

  it_behaves_like 'checks timelog categories permissions' do
    let(:project) { create(:project) }
    let(:namespace) { project.project_namespace }
    let(:users_container) { project }
  end

  context 'with read_namespace permissions' do
    let(:project) { public_project }
    let(:owner) { project.creator }
    let(:developer) { create(:user) }
    let(:admin) { create(:admin) }
    let(:namespace) { project.project_namespace }

    before do
      project.add_developer(developer)
    end

    it 'allows access when a user has read access to the project' do
      expect(described_class.new(owner, project.project_namespace)).to be_allowed(:read_project, :read_namespace)
      expect(described_class.new(developer, project.project_namespace)).to be_allowed(:read_project, :read_namespace)
      expect(described_class.new(admin, project.project_namespace)).to be_allowed(:read_project, :read_namespace)
    end

    it 'never checks the external service' do
      expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)

      expect(described_class.new(owner, namespace)).to be_allowed(:read_project, :read_namespace)
    end

    context 'with an external authorization service' do
      before do
        enable_external_authorization_service_check
      end

      it 'allows access when the external service allows it' do
        external_service_allow_access(owner, project)
        external_service_allow_access(developer, project)

        expect(described_class.new(owner, namespace)).to be_allowed(:read_project, :read_namespace)
        expect(described_class.new(developer, namespace)).to be_allowed(:read_project, :read_namespace)
      end

      context 'with an admin' do
        context 'when admin mode is enabled', :enable_admin_mode do
          it 'does not check the external service and allows access' do
            expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)

            expect(described_class.new(admin, namespace)).to be_allowed(:read_project, :read_namespace)
          end
        end

        context 'when admin mode is disabled' do
          it 'checks the external service and allows access' do
            external_service_allow_access(admin, project)

            expect(::Gitlab::ExternalAuthorization).to receive(:access_allowed?)

            expect(described_class.new(admin, namespace)).to be_allowed(:read_project, :read_namespace)
          end
        end
      end

      it 'prevents all but seeing a public project in a list when access is denied' do
        [developer, owner, build(:user), nil].each do |user|
          external_service_deny_access(user, project)
          policy = described_class.new(user, namespace)

          expect(policy).not_to be_allowed(:read_project, :read_namespace)
        end
      end

      it 'passes the full path to external authorization for logging purposes' do
        expect(::Gitlab::ExternalAuthorization)
          .to receive(:access_allowed?).with(owner, 'default_label', project.full_path).and_call_original

        described_class.new(owner, namespace).allowed?(:read_project, :read_namespace)
      end
    end

    context 'with support bot user' do
      let(:current_user) { Users::Internal.support_bot }

      context 'with service desk disabled' do
        it { expect(described_class.new(current_user, namespace)).not_to be_allowed(:read_project, :read_namespace) }
      end
    end

    context 'with deploy key access actor' do
      context 'when project is private' do
        let(:project) { private_project }
        let!(:deploy_key) { create(:deploy_key, user: owner) }

        subject { described_class.new(deploy_key, project.project_namespace) }

        context 'when a read deploy key is enabled in the project' do
          let!(:deploy_keys_project) { create(:deploy_keys_project, project: project, deploy_key: deploy_key) }

          it { is_expected.to be_disallowed(:read_project, :read_namespace) }
        end

        context 'when a write deploy key is enabled in the project' do
          let!(:deploy_keys_project) do
            create(:deploy_keys_project, :write_access, project: project, deploy_key: deploy_key)
          end

          it { is_expected.to be_disallowed(:read_project, :read_namespace) }
        end

        context 'when the deploy key is not enabled in the project' do
          it { is_expected.to be_disallowed(:read_project, :read_namespace) }
        end
      end
    end
  end
end
