# frozen_string_literal: true

require 'spec_helper'

describe Projects::IssuesController do
  let(:namespace) { create(:group, :public) }
  let(:project)   { create(:project_empty_repo, :public, namespace: namespace) }
  let(:user) { create(:user) }

  describe 'licensed features' do
    let(:project) { create(:project, group: namespace) }
    let(:user) { create(:user) }
    let(:epic) { create(:epic, group: namespace) }
    let(:issue) { create(:issue, project: project, weight: 5) }
    let(:issue2) { create(:issue, project: project, weight: 1) }
    let(:new_issue) { build(:issue, project: project, weight: 5) }

    before do
      namespace.add_developer(user)
      sign_in(user)
    end

    def perform(method, action, opts = {})
      send(method, action, params: opts.merge(namespace_id: project.namespace.to_param, project_id: project.to_param))
    end

    context 'licensed' do
      before do
        stub_licensed_features(issue_weights: true, epics: true)
      end

      describe '#index' do
        it 'allows sorting by weight' do
          expected = [issue, issue2].sort_by(&:weight)

          perform :get, :index, sort: 'weight'

          expect(response).to have_gitlab_http_status(:ok)
          expect(assigns(:issues)).to eq(expected)
        end

        it 'allows filtering by weight' do
          _ = issue
          _ = issue2

          perform :get, :index, weight: 1

          expect(response).to have_gitlab_http_status(:ok)
          expect(assigns(:issues)).to eq([issue2])
        end
      end

      describe '#update' do
        it 'sets issue weight and epic' do
          perform :put, :update, id: issue.to_param, issue: { weight: 6, epic_id: epic.id }, format: :json

          expect(response).to have_gitlab_http_status(:ok)
          expect(issue.reload.weight).to eq(6)
          expect(issue.epic).to eq(epic)
        end
      end

      describe '#create' do
        it 'sets issue weight and epic' do
          perform :post, :create, issue: new_issue.attributes.merge(epic_id: epic.id)

          expect(response).to have_gitlab_http_status(:found)
          expect(Issue.count).to eq(1)

          issue = Issue.first
          expect(issue.weight).to eq(new_issue.weight)
          expect(issue.epic).to eq(epic)
        end
      end
    end

    context 'unlicensed' do
      before do
        stub_licensed_features(issue_weights: false, epics: false)
      end

      describe '#index' do
        it 'ignores filtering by weight' do
          expected = [issue, issue2]

          perform :get, :index, weight: 1

          expect(response).to have_gitlab_http_status(:ok)
          expect(assigns(:issues)).to match_array(expected)
        end
      end

      describe '#update' do
        it 'does not set issue weight' do
          perform :put, :update, id: issue.to_param, issue: { weight: 6 }, format: :json

          expect(response).to have_gitlab_http_status(:ok)
          expect(issue.reload.weight).to be_nil
          expect(issue.reload.read_attribute(:weight)).to eq(5) # pre-existing data is not overwritten
        end
      end

      describe '#create' do
        it 'does not set issue weight ane epic' do
          perform :post, :create, issue: new_issue.attributes

          expect(response).to have_gitlab_http_status(:found)
          expect(Issue.count).to eq(1)

          issue = Issue.first
          expect(issue.weight).to be_nil
          expect(issue.epic).to be_nil
        end
      end
    end
  end

  describe 'GET service_desk' do
    def get_service_desk(extra_params = {})
      get :service_desk, params: extra_params.merge(namespace_id: project.namespace, project_id: project)
    end

    context 'when Service Desk is available on the project' do
      let(:support_bot) { User.support_bot }
      let(:other_user) { create(:user) }
      let!(:service_desk_issue_1) { create(:issue, project: project, author: support_bot) }
      let!(:service_desk_issue_2) { create(:issue, project: project, author: support_bot, assignees: [other_user]) }
      let!(:other_user_issue) { create(:issue, project: project, author: other_user) }

      before do
        stub_licensed_features(service_desk: true)
      end

      it 'adds an author filter for the support bot user' do
        get_service_desk

        expect(assigns(:issues)).to contain_exactly(service_desk_issue_1, service_desk_issue_2)
      end

      it 'does not allow any other author to be set' do
        get_service_desk(author_username: other_user.username)

        expect(assigns(:issues)).to contain_exactly(service_desk_issue_1, service_desk_issue_2)
      end

      it 'supports other filters' do
        get_service_desk(assignee_username: other_user.username)

        expect(assigns(:issues)).to contain_exactly(service_desk_issue_2)
      end

      it 'allows an assignee to be specified by id' do
        get_service_desk(assignee_id: other_user.id)

        expect(assigns(:users)).to contain_exactly(other_user, support_bot)
      end
    end

    context 'when Service Desk is not available on the project' do
      before do
        stub_licensed_features(service_desk: false)
      end

      it 'returns a 404' do
        get_service_desk

        expect(response).to have_gitlab_http_status(:not_found)
      end
    end
  end

  describe 'GET #designs' do
    context 'when project has moved' do
      let(:new_project) { create(:project) }
      let(:issue) { create(:issue, project: new_project) }

      before do
        sign_in(user)

        project.route.destroy
        new_project.redirect_routes.create!(path: project.full_path)
        new_project.add_developer(user)
      end

      it 'redirects from an old issue/designs correctly' do
        get :designs,
            params: {
              namespace_id: project.namespace,
              project_id: project,
              id: issue
            }

        expect(response).to redirect_to(designs_project_issue_path(new_project, issue))
        expect(response).to have_gitlab_http_status(:found)
      end
    end
  end

  describe 'GET #discussions' do
    let(:issue) { create(:issue, project: project) }
    let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) }

    context 'with a related system note' do
      let(:confidential_issue) { create(:issue, :confidential, project: project) }
      let!(:system_note) { SystemNoteService.relate_issue(issue, confidential_issue, user) }

      shared_examples 'user can see confidential issue' do |access_level|
        context "when a user is a #{access_level}" do
          before do
            project.add_user(user, access_level)
          end

          it 'displays related notes' do
            get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }

            discussions = json_response
            notes = discussions.flat_map {|d| d['notes']}

            expect(discussions.count).to equal(2)
            expect(notes).to include(a_hash_including('id' => system_note.id.to_s))
          end
        end
      end

      shared_examples 'user cannot see confidential issue' do |access_level|
        context "when a user is a #{access_level}" do
          before do
            project.add_user(user, access_level)
          end

          it 'redacts note related to a confidential issue' do
            get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }

            discussions = json_response
            notes = discussions.flat_map {|d| d['notes']}

            expect(discussions.count).to equal(1)
            expect(notes).not_to include(a_hash_including('id' => system_note.id.to_s))
          end
        end
      end

      context 'when authenticated' do
        before do
          sign_in(user)
        end

        %i(reporter developer maintainer).each do |access|
          it_behaves_like 'user can see confidential issue', access
        end

        it_behaves_like 'user cannot see confidential issue', :guest
      end

      context 'when unauthenticated' do
        let(:project) { create(:project, :public) }

        it_behaves_like 'user cannot see confidential issue', Gitlab::Access::NO_ACCESS
      end
    end

    context 'is_gitlab_employee attribute' do
      subject { get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } }

      before do
        allow(Gitlab).to receive(:com?).and_return(true)
        note_user = discussion.author
        note_user.update(email: email)
        note_user.confirm
      end

      shared_examples 'non inclusion of gitlab employee badge' do
        it 'does not render the is_gitlab_employee attribute' do
          subject

          note_json = json_response.first['notes'].first

          expect(note_json['author']['is_gitlab_employee']).to be nil
        end
      end

      context 'when user is a gitlab employee' do
        let(:email) { 'test@gitlab.com' }

        it 'renders the is_gitlab_employee attribute' do
          subject

          note_json = json_response.first['notes'].first

          expect(note_json['author']['is_gitlab_employee']).to be true
        end

        context 'when feature flag is disabled' do
          before do
            stub_feature_flags(gitlab_employee_badge: false)
          end

          it_behaves_like 'non inclusion of gitlab employee badge'
        end
      end

      context 'when user is not a gitlab employee' do
        let(:email) { 'test@example.com' }

        it_behaves_like 'non inclusion of gitlab employee badge'

        context 'when feature flag is disabled' do
          before do
            stub_feature_flags(gitlab_employee_badge: false)
          end

          it_behaves_like 'non inclusion of gitlab employee badge'
        end
      end
    end
  end

  it_behaves_like DescriptionDiffActions do
    let_it_be(:project)  { create(:project_empty_repo, :public) }
    let_it_be(:issuable) { create(:issue, project: project) }
  end
end
