milestones_controller_spec.rb 8.2 KB
Newer Older
1 2
# frozen_string_literal: true

3 4 5
require 'spec_helper'

describe Projects::MilestonesController do
6
  let(:project) { create(:project, :repository) }
7 8 9
  let(:user)    { create(:user) }
  let(:milestone) { create(:milestone, project: project) }
  let(:issue) { create(:issue, project: project, milestone: milestone) }
10
  let!(:label) { create(:label, project: project, title: 'Issue Label', issues: [issue]) }
11
  let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) }
Phil Hughes's avatar
Phil Hughes committed
12
  let(:milestone_path) { namespace_project_milestone_path }
13 14 15

  before do
    sign_in(user)
16
    project.add_maintainer(user)
17 18 19
    controller.instance_variable_set(:@project, project)
  end

Phil Hughes's avatar
Phil Hughes committed
20 21
  it_behaves_like 'milestone tabs'

22 23 24
  describe "#show" do
    render_views

25 26
    def view_milestone(options = {})
      params = { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid }
blackst0ne's avatar
blackst0ne committed
27
      get :show, params: params.merge(options)
28 29 30 31 32
    end

    it 'shows milestone page' do
      view_milestone

33
      expect(response).to have_gitlab_http_status(200)
34 35 36 37 38 39 40 41
      expect(response.content_type).to eq 'text/html'
    end

    it 'returns milestone json' do
      view_milestone format: :json

      expect(response).to have_http_status(404)
      expect(response.content_type).to eq 'application/json'
42 43 44
    end
  end

Felipe Artur's avatar
Felipe Artur committed
45 46
  describe "#index" do
    context "as html" do
Jacopo's avatar
Jacopo committed
47
      def render_index(project:, page:, search_title: '')
blackst0ne's avatar
blackst0ne committed
48 49 50
        get :index, params: {
                      namespace_id: project.namespace.id,
                      project_id: project.id,
Jacopo's avatar
Jacopo committed
51
                      search_title: search_title,
blackst0ne's avatar
blackst0ne committed
52 53
                      page: page
                    }
Felipe Artur's avatar
Felipe Artur committed
54 55 56
      end

      it "queries only projects milestones" do
57 58
        render_index project: project, page: 1

Felipe Artur's avatar
Felipe Artur committed
59 60 61 62 63
        milestones = assigns(:milestones)

        expect(milestones.count).to eq(1)
        expect(milestones.where(project_id: nil)).to be_empty
      end
64

Jacopo's avatar
Jacopo committed
65 66 67 68 69 70 71 72 73
      it 'searches milestones by title when search_title is given' do
        milestone1 = create(:milestone, title: 'Project milestone title', project: project)

        render_index project: project, page: 1, search_title: 'Project mile'

        milestones = assigns(:milestones)
        expect(milestones).to eq([milestone1])
      end

74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
      it 'renders paginated milestones without missing or duplicates' do
        allow(Milestone).to receive(:default_per_page).and_return(2)
        create_list(:milestone, 5, project: project)

        render_index project: project, page: 1
        page_1_milestones = assigns(:milestones)
        expect(page_1_milestones.size).to eq(2)

        render_index project: project, page: 2
        page_2_milestones = assigns(:milestones)
        expect(page_2_milestones.size).to eq(2)

        render_index project: project, page: 3
        page_3_milestones = assigns(:milestones)
        expect(page_3_milestones.size).to eq(2)

        rendered_milestone_ids =
          page_1_milestones.pluck(:id) +
          page_2_milestones.pluck(:id) +
          page_3_milestones.pluck(:id)

        expect(rendered_milestone_ids)
          .to match_array(project.milestones.pluck(:id))
      end
Felipe Artur's avatar
Felipe Artur committed
98 99 100 101 102 103
    end

    context "as json" do
      let!(:group) { create(:group, :public) }
      let!(:group_milestone) { create(:milestone, group: group) }

104 105 106
      context 'with a single group ancestor' do
        before do
          project.update(namespace: group)
blackst0ne's avatar
blackst0ne committed
107
          get :index, params: { namespace_id: project.namespace.id, project_id: project.id }, format: :json
108 109 110 111 112 113 114 115
        end

        it "queries projects milestones and groups milestones" do
          milestones = assigns(:milestones)

          expect(milestones.count).to eq(2)
          expect(milestones).to match_array([milestone, group_milestone])
        end
Felipe Artur's avatar
Felipe Artur committed
116 117
      end

118 119 120 121 122 123
      context 'with nested groups', :nested_groups do
        let!(:subgroup) { create(:group, :public, parent: group) }
        let!(:subgroup_milestone) { create(:milestone, group: subgroup) }

        before do
          project.update(namespace: subgroup)
blackst0ne's avatar
blackst0ne committed
124
          get :index, params: { namespace_id: project.namespace.id, project_id: project.id }, format: :json
125 126 127 128
        end

        it "queries projects milestones and all ancestors milestones" do
          milestones = assigns(:milestones)
Felipe Artur's avatar
Felipe Artur committed
129

130 131 132
          expect(milestones.count).to eq(3)
          expect(milestones).to match_array([milestone, group_milestone, subgroup_milestone])
        end
Felipe Artur's avatar
Felipe Artur committed
133 134 135 136
      end
    end
  end

137
  describe "#destroy" do
138
    it "removes milestone" do
139
      expect(issue.milestone_id).to eq(milestone.id)
140

blackst0ne's avatar
blackst0ne committed
141
      delete :destroy, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid }, format: :js
142
      expect(response).to be_success
143

144
      expect(Event.recent.first.action).to eq(Event::DESTROYED)
145

146 147 148
      expect { Milestone.find(milestone.id) }.to raise_exception(ActiveRecord::RecordNotFound)
      issue.reload
      expect(issue.milestone_id).to eq(nil)
149 150 151 152

      merge_request.reload
      expect(merge_request.milestone_id).to eq(nil)

153 154
      # Check system note left for milestone removal
      last_note = project.issues.find(issue.id).notes[-1].note
155
      expect(last_note).to eq('removed milestone')
156 157
    end
  end
158 159

  describe '#promote' do
160 161 162 163 164 165 166 167 168 169 170 171
    let(:group) { create(:group) }

    before do
      project.update(namespace: group)
    end

    context 'when user does not have permission to promote milestone' do
      before do
        group.add_guest(user)
      end

      it 'renders 404' do
blackst0ne's avatar
blackst0ne committed
172
        post :promote, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid }
173 174 175 176 177

        expect(response).to have_gitlab_http_status(404)
      end
    end

178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
    describe '#labels' do
      render_views

      context 'as json' do
        let!(:guest) { create(:user, username: 'guest1') }
        let!(:group) { create(:group, :public) }
        let!(:project) { create(:project, :public, group: group) }
        let!(:label) { create(:label, title: 'test_label_on_private_issue', project: project) }
        let!(:confidential_issue) { create(:labeled_issue, confidential: true, project: project, milestone: milestone, labels: [label]) }

        it 'does not render labels of private issues if user has no access' do
          sign_in(guest)

          get :labels, params: { namespace_id: group.id, project_id: project.id, id: milestone.iid }, format: :json

          expect(response).to have_gitlab_http_status(200)
          expect(response.content_type).to eq 'application/json'

          expect(json_response['html']).not_to include(label.title)
        end

        it 'does render labels of private issues if user has access' do
          sign_in(user)

          get :labels, params: { namespace_id: group.id, project_id: project.id, id: milestone.iid }, format: :json

          expect(response).to have_gitlab_http_status(200)
          expect(response.content_type).to eq 'application/json'

          expect(json_response['html']).to include(label.title)
        end
      end
    end

212 213 214 215 216 217
    context 'promotion succeeds' do
      before do
        group.add_developer(user)
      end

      it 'shows group milestone' do
blackst0ne's avatar
blackst0ne committed
218
        post :promote, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid }
219

220
        expect(flash[:notice]).to eq("#{milestone.title} promoted to <a href=\"#{group_milestone_path(project.group, milestone.iid)}\"><u>group milestone</u></a>.")
221
        expect(response).to redirect_to(project_milestones_path(project))
222
      end
223 224 225 226

      it 'renders milestone name without parsing it as HTML' do
        milestone.update!(name: 'CCC&lt;img src=x onerror=alert(document.domain)&gt;')

blackst0ne's avatar
blackst0ne committed
227
        post :promote, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid }
228

229
        expect(flash[:notice]).to eq("CCC promoted to <a href=\"#{group_milestone_path(project.group, milestone.iid)}\"><u>group milestone</u></a>.")
230
      end
231 232
    end

233 234 235 236 237 238 239 240
    context 'when user cannot admin group milestones' do
      before do
        project.add_developer(user)
      end

      it 'renders 404' do
        project.update(namespace: user.namespace)

blackst0ne's avatar
blackst0ne committed
241
        post :promote, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid }
242

243
        expect(response).to have_gitlab_http_status(404)
244 245 246
      end
    end
  end
247
end