groups_controller_spec.rb 23.9 KB
Newer Older
1
require 'spec_helper'
2 3

describe GroupsController do
4
  let(:user) { create(:user) }
5
  let(:admin) { create(:admin) }
6
  let(:group) { create(:group, :public) }
7
  let(:project) { create(:project, namespace: group) }
8
  let!(:group_member) { create(:group_member, group: group, user: user) }
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
  let!(:owner) { group.add_owner(create(:user)).user }
  let!(:master) { group.add_master(create(:user)).user }
  let!(:developer) { group.add_developer(create(:user)).user }
  let!(:guest) { group.add_guest(create(:user)).user }

  shared_examples 'member with ability to create subgroups' do
    it 'renders the new page' do
      sign_in(member)

      get :new, parent_id: group.id

      expect(response).to render_template(:new)
    end
  end

  shared_examples 'member without ability to create subgroups' do
    it 'renders the 404 page' do
      sign_in(member)

      get :new, parent_id: group.id

      expect(response).not_to render_template(:new)
      expect(response.status).to eq(404)
    end
  end

  describe 'GET #new' do
    context 'when creating subgroups', :nested_groups do
      [true, false].each do |can_create_group_status|
        context "and can_create_group is #{can_create_group_status}" do
          before do
            User.where(id: [admin, owner, master, developer, guest]).update_all(can_create_group: can_create_group_status)
          end

          [:admin, :owner].each do |member_type|
            context "and logged in as #{member_type.capitalize}" do
              it_behaves_like 'member with ability to create subgroups' do
                let(:member) { send(member_type) }
              end
            end
          end

          [:guest, :developer, :master].each do |member_type|
            context "and logged in as #{member_type.capitalize}" do
              it_behaves_like 'member without ability to create subgroups' do
                let(:member) { send(member_type) }
              end
            end
          end
        end
      end
    end
  end

  describe 'POST #create' do
    context 'when creating subgroups', :nested_groups do
      [true, false].each do |can_create_group_status|
        context "and can_create_group is #{can_create_group_status}" do
          context 'and logged in as Owner' do
            it 'creates the subgroup' do
              owner.update_attribute(:can_create_group, can_create_group_status)
              sign_in(owner)

              post :create, group: { parent_id: group.id, path: 'subgroup' }

              expect(response).to be_redirect
              expect(response.body).to match(%r{http://test.host/#{group.path}/subgroup})
            end
          end

          context 'and logged in as Developer' do
            it 'renders the new template' do
              developer.update_attribute(:can_create_group, can_create_group_status)
              sign_in(developer)

              previous_group_count = Group.count

              post :create, group: { parent_id: group.id, path: 'subgroup' }

              expect(response).to render_template(:new)
              expect(Group.count).to eq(previous_group_count)
            end
          end
        end
      end
    end

    context 'when creating a top level group' do
      before do
        sign_in(developer)
      end

      context 'and can_create_group is enabled' do
        before do
          developer.update_attribute(:can_create_group, true)
        end

        it 'creates the Group' do
          original_group_count = Group.count

          post :create, group: { path: 'subgroup' }

          expect(Group.count).to eq(original_group_count + 1)
          expect(response).to be_redirect
        end
      end

      context 'and can_create_group is disabled' do
        before do
          developer.update_attribute(:can_create_group, false)
        end

        it 'does not create the Group' do
          original_group_count = Group.count

          post :create, group: { path: 'subgroup' }

          expect(Group.count).to eq(original_group_count)
          expect(response).to render_template(:new)
        end
      end
    end
  end
132 133

  describe 'GET #index' do
134 135
    context 'as a user' do
      it 'redirects to Groups Dashboard' do
136
        sign_in(user)
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151

        get :index

        expect(response).to redirect_to(dashboard_groups_path)
      end
    end

    context 'as a guest' do
      it 'redirects to Explore Groups' do
        get :index

        expect(response).to redirect_to(explore_groups_path)
      end
    end
  end
152

153 154
  describe 'GET #show' do
    context 'pagination' do
155 156 157 158
      before do
        allow(Kaminari.config).to receive(:default_per_page).and_return(2)
      end

159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
      context 'with only projects' do
        let!(:other_project) { create(:project, :public, namespace: group) }
        let!(:first_page_projects) { create_list(:project, Kaminari.config.default_per_page, :public, namespace: group ) }

        it 'has projects on the first page' do
          get :show, id: group.to_param, sort: 'id_desc'

          expect(assigns(:children)).to contain_exactly(*first_page_projects)
        end

        it 'has projects on the second page' do
          get :show, id: group.to_param, sort: 'id_desc', page: 2

          expect(assigns(:children)).to contain_exactly(other_project)
        end
      end

      context 'with subgroups and projects', :nested_groups do
        let!(:other_subgroup) { create(:group, :public, parent: group) }
        let!(:project) { create(:project, :public, namespace: group) }
        let!(:first_page_subgroups) { create_list(:group, Kaminari.config.default_per_page, parent: group) }

        it 'contains all subgroups' do
          get :children, id: group.to_param, sort: 'id_desc', format: :json

          expect(assigns(:children)).to contain_exactly(*first_page_subgroups)
        end

        it 'contains the project and group on the second page' do
          get :children, id: group.to_param, sort: 'id_desc', page: 2, format: :json

          expect(assigns(:children)).to contain_exactly(other_subgroup, project)
        end
      end
    end
  end

196 197 198 199
  describe 'GET #children' do
    context 'for projects' do
      let!(:public_project) { create(:project, :public, namespace: group) }
      let!(:private_project) { create(:project, :private, namespace: group) }
200

201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
      context 'as a user' do
        before do
          sign_in(user)
        end

        it 'shows all children' do
          get :children, id: group.to_param, format: :json

          expect(assigns(:children)).to contain_exactly(public_project, private_project)
        end

        context 'being member of private subgroup' do
          it 'shows public and private children the user is member of' do
            group_member.destroy!
            private_project.add_guest(user)

            get :children, id: group.to_param, format: :json

            expect(assigns(:children)).to contain_exactly(public_project, private_project)
          end
        end
222 223
      end

224 225 226
      context 'as a guest' do
        it 'shows the public children' do
          get :children, id: group.to_param, format: :json
227

228 229
          expect(assigns(:children)).to contain_exactly(public_project)
        end
230
      end
231
    end
232

233 234 235 236 237
    context 'for subgroups', :nested_groups do
      let!(:public_subgroup) { create(:group, :public, parent: group) }
      let!(:private_subgroup) { create(:group, :private, parent: group) }
      let!(:public_project) { create(:project, :public, namespace: group) }
      let!(:private_project) { create(:project, :private, namespace: group) }
238

239 240 241 242
      context 'as a user' do
        before do
          sign_in(user)
        end
243

244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
        it 'shows all children' do
          get :children, id: group.to_param, format: :json

          expect(assigns(:children)).to contain_exactly(public_subgroup, private_subgroup, public_project, private_project)
        end

        context 'being member of private subgroup' do
          it 'shows public and private children the user is member of' do
            group_member.destroy!
            private_subgroup.add_guest(user)
            private_project.add_guest(user)

            get :children, id: group.to_param, format: :json

            expect(assigns(:children)).to contain_exactly(public_subgroup, private_subgroup, public_project, private_project)
          end
260 261 262
        end
      end

263 264 265
      context 'as a guest' do
        it 'shows the public children' do
          get :children, id: group.to_param, format: :json
266

267 268
          expect(assigns(:children)).to contain_exactly(public_subgroup, public_project)
        end
269
      end
270 271 272 273 274

      context 'filtering children' do
        it 'expands the tree for matching projects' do
          project = create(:project, :public, namespace: public_subgroup, name: 'filterme')

275
          get :children, id: group.to_param, filter: 'filter', format: :json
276 277 278 279 280 281 282 283 284 285 286

          group_json = json_response.first
          project_json = group_json['children'].first

          expect(group_json['id']).to eq(public_subgroup.id)
          expect(project_json['id']).to eq(project.id)
        end

        it 'expands the tree for matching subgroups' do
          matched_group = create(:group, :public, parent: public_subgroup, name: 'filterme')

287
          get :children, id: group.to_param, filter: 'filter', format: :json
288 289 290 291 292 293 294

          group_json = json_response.first
          matched_group_json = group_json['children'].first

          expect(group_json['id']).to eq(public_subgroup.id)
          expect(matched_group_json['id']).to eq(matched_group.id)
        end
295

296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
        it 'merges the trees correctly' do
          shared_subgroup = create(:group, :public, parent: group, path: 'hardware')
          matched_project_1 = create(:project, :public, namespace: shared_subgroup, name: 'mobile-soc')

          l2_subgroup = create(:group, :public, parent: shared_subgroup, path: 'broadcom')
          l3_subgroup = create(:group, :public,  parent: l2_subgroup, path: 'wifi-group')
          matched_project_2 = create(:project, :public, namespace: l3_subgroup, name: 'mobile')

          get :children, id: group.to_param, filter: 'mobile', format: :json

          shared_group_json = json_response.first
          expect(shared_group_json['id']).to eq(shared_subgroup.id)

          matched_project_1_json = shared_group_json['children'].detect { |child| child['type'] == 'project' }
          expect(matched_project_1_json['id']).to eq(matched_project_1.id)

          l2_subgroup_json = shared_group_json['children'].detect { |child| child['type'] == 'group' }
          expect(l2_subgroup_json['id']).to eq(l2_subgroup.id)

          l3_subgroup_json = l2_subgroup_json['children'].first
          expect(l3_subgroup_json['id']).to eq(l3_subgroup.id)

          matched_project_2_json = l3_subgroup_json['children'].first
          expect(matched_project_2_json['id']).to eq(matched_project_2.id)
        end

322 323 324 325 326 327 328
        it 'includes pagination headers' do
          2.times { |i| create(:group, :public, parent: public_subgroup, name: "filterme#{i}") }

          get :children, id: group.to_param, filter: 'filter', per_page: 1, format: :json

          expect(response).to include_pagination_headers
        end
329
      end
330

331
      context 'queries per rendered element', :request_store do
332 333
        # We need to make sure the following counts are preloaded
        # otherwise they will cause an extra query
B
Bob Van Landuyt 已提交
334 335
        # 1. Count of visible projects in the element
        # 2. Count of visible subgroups in the element
336 337
        # 3. Count of members of a group
        let(:expected_queries_per_group) { 0 }
338 339 340 341 342 343 344
        let(:expected_queries_per_project) { 0 }

        def get_list
          get :children, id: group.to_param, format: :json
        end

        it 'queries the expected amount for a group row' do
345
          control = ActiveRecord::QueryRecorder.new { get_list }
B
Bob Van Landuyt 已提交
346

347 348
          _new_group = create(:group, :public, parent: group)

349
          expect { get_list }.not_to exceed_query_limit(control).with_threshold(expected_queries_per_group)
350 351 352
        end

        it 'queries the expected amount for a project row' do
353
          control = ActiveRecord::QueryRecorder.new { get_list }
354 355
          _new_project = create(:project, :public, namespace: group)

356
          expect { get_list }.not_to exceed_query_limit(control).with_threshold(expected_queries_per_project)
357 358 359
        end

        context 'when rendering hierarchies' do
360 361 362
          # When loading hierarchies we load the all the ancestors for matched projects
          # in 1 separate query
          let(:extra_queries_for_hierarchies) { 1 }
B
Bob Van Landuyt 已提交
363

364 365 366 367
          def get_filtered_list
            get :children, id: group.to_param, filter: 'filter', format: :json
          end

B
Bob Van Landuyt 已提交
368 369
          it 'queries the expected amount when nested rows are increased for a group' do
            matched_group = create(:group, :public, parent: group, name: 'filterme')
370

371
            control = ActiveRecord::QueryRecorder.new { get_filtered_list }
372

B
Bob Van Landuyt 已提交
373 374
            matched_group.update!(parent: public_subgroup)

375
            expect { get_filtered_list }.not_to exceed_query_limit(control).with_threshold(extra_queries_for_hierarchies)
376 377 378 379 380
          end

          it 'queries the expected amount when a new group match is added' do
            create(:group, :public, parent: public_subgroup, name: 'filterme')

381 382
            control = ActiveRecord::QueryRecorder.new { get_filtered_list }

383
            create(:group, :public, parent: public_subgroup, name: 'filterme2')
384
            create(:group, :public, parent: public_subgroup, name: 'filterme3')
385

386
            expect { get_filtered_list }.not_to exceed_query_limit(control).with_threshold(extra_queries_for_hierarchies)
387 388
          end

B
Bob Van Landuyt 已提交
389 390
          it 'queries the expected amount when nested rows are increased for a project' do
            matched_project = create(:project, :public, namespace: group, name: 'filterme')
391

392
            control = ActiveRecord::QueryRecorder.new { get_filtered_list }
393

B
Bob Van Landuyt 已提交
394
            matched_project.update!(namespace: public_subgroup)
395

396
            expect { get_filtered_list }.not_to exceed_query_limit(control).with_threshold(extra_queries_for_hierarchies)
397 398 399
          end
        end
      end
400 401 402
    end
  end

403 404 405 406 407
  describe 'GET #issues' do
    let(:issue_1) { create(:issue, project: project) }
    let(:issue_2) { create(:issue, project: project) }

    before do
408 409
      create_list(:award_emoji, 3, awardable: issue_2)
      create_list(:award_emoji, 2, awardable: issue_1)
410
      create_list(:award_emoji, 2, :downvote, awardable: issue_2)
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432

      sign_in(user)
    end

    context 'sorting by votes' do
      it 'sorts most popular issues' do
        get :issues, id: group.to_param, sort: 'upvotes_desc'
        expect(assigns(:issues)).to eq [issue_2, issue_1]
      end

      it 'sorts least popular issues' do
        get :issues, id: group.to_param, sort: 'downvotes_desc'
        expect(assigns(:issues)).to eq [issue_2, issue_1]
      end
    end
  end

  describe 'GET #merge_requests' do
    let(:merge_request_1) { create(:merge_request, source_project: project) }
    let(:merge_request_2) { create(:merge_request, :simple, source_project: project) }

    before do
433 434
      create_list(:award_emoji, 3, awardable: merge_request_2)
      create_list(:award_emoji, 2, awardable: merge_request_1)
Z
Z.J. van de Weg 已提交
435
      create_list(:award_emoji, 2, :downvote, awardable: merge_request_2)
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451

      sign_in(user)
    end

    context 'sorting by votes' do
      it 'sorts most popular merge requests' do
        get :merge_requests, id: group.to_param, sort: 'upvotes_desc'
        expect(assigns(:merge_requests)).to eq [merge_request_2, merge_request_1]
      end

      it 'sorts least popular merge requests' do
        get :merge_requests, id: group.to_param, sort: 'downvotes_desc'
        expect(assigns(:merge_requests)).to eq [merge_request_2, merge_request_1]
      end
    end
  end
452 453 454 455 456 457

  describe 'DELETE #destroy' do
    context 'as another user' do
      it 'returns 404' do
        sign_in(create(:user))

458
        delete :destroy, id: group.to_param
459 460 461 462 463 464 465 466 467 468 469

        expect(response.status).to eq(404)
      end
    end

    context 'as the group owner' do
      before do
        sign_in(user)
      end

      it 'schedules a group destroy' do
470
        Sidekiq::Testing.fake! do
471
          expect { delete :destroy, id: group.to_param }.to change(GroupDestroyWorker.jobs, :size).by(1)
472
        end
473 474 475
      end

      it 'redirects to the root path' do
476
        delete :destroy, id: group.to_param
477 478 479 480 481

        expect(response).to redirect_to(root_path)
      end
    end
  end
482 483 484 485 486 487

  describe 'PUT update' do
    before do
      sign_in(user)
    end

488
    it 'updates the path successfully' do
489 490 491 492 493 494 495 496 497 498
      post :update, id: group.to_param, group: { path: 'new_path' }

      expect(response).to have_http_status(302)
      expect(controller).to set_flash[:notice]
    end

    it 'does not update the path on error' do
      allow_any_instance_of(Group).to receive(:move_dir).and_raise(Gitlab::UpdatePathError)
      post :update, id: group.to_param, group: { path: 'new_path' }

J
James Lopez 已提交
499 500
      expect(assigns(:group).errors).not_to be_empty
      expect(assigns(:group).path).not_to eq('new_path')
501
    end
502 503 504 505 506 507 508 509 510 511 512 513 514
  end

  describe '#ensure_canonical_path' do
    before do
      sign_in(user)
    end

    context 'for a GET request' do
      context 'when requesting groups at the root path' do
        before do
          allow(request).to receive(:original_fullpath).and_return("/#{group_full_path}")
          get :show, id: group_full_path
        end
515

516 517
        context 'when requesting the canonical path with different casing' do
          let(:group_full_path) { group.to_param.upcase }
518

519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
          it 'redirects to the correct casing' do
            expect(response).to redirect_to(group)
            expect(controller).not_to set_flash[:notice]
          end
        end

        context 'when requesting a redirected path' do
          let(:redirect_route) { group.redirect_routes.create(path: 'old-path') }
          let(:group_full_path) { redirect_route.path }

          it 'redirects to the canonical path' do
            expect(response).to redirect_to(group)
            expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
          end

          context 'when the old group path is a substring of the scheme or host' do
            let(:redirect_route) { group.redirect_routes.create(path: 'http') }

            it 'does not modify the requested host' do
              expect(response).to redirect_to(group)
              expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
            end
          end

          context 'when the old group path is substring of groups' do
            # I.e. /groups/oups should not become /grfoo/oups
            let(:redirect_route) { group.redirect_routes.create(path: 'oups') }

            it 'does not modify the /groups part of the path' do
              expect(response).to redirect_to(group)
              expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
            end
          end
        end
553 554
      end

555
      context 'when requesting groups under the /groups path' do
556 557 558 559 560 561 562 563 564
        context 'when requesting the canonical path' do
          context 'non-show path' do
            context 'with exactly matching casing' do
              it 'does not redirect' do
                get :issues, id: group.to_param

                expect(response).not_to have_http_status(301)
              end
            end
565

566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
            context 'with different casing' do
              it 'redirects to the correct casing' do
                get :issues, id: group.to_param.upcase

                expect(response).to redirect_to(issues_group_path(group.to_param))
                expect(controller).not_to set_flash[:notice]
              end
            end
          end

          context 'show path' do
            context 'with exactly matching casing' do
              it 'does not redirect' do
                get :show, id: group.to_param

                expect(response).not_to have_http_status(301)
              end
            end

            context 'with different casing' do
              it 'redirects to the correct casing at the root path' do
                get :show, id: group.to_param.upcase

                expect(response).to redirect_to(group)
                expect(controller).not_to set_flash[:notice]
              end
            end
593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
          end
        end

        context 'when requesting a redirected path' do
          let(:redirect_route) { group.redirect_routes.create(path: 'old-path') }

          it 'redirects to the canonical path' do
            get :issues, id: redirect_route.path

            expect(response).to redirect_to(issues_group_path(group.to_param))
            expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
          end

          context 'when the old group path is a substring of the scheme or host' do
            let(:redirect_route) { group.redirect_routes.create(path: 'http') }

            it 'does not modify the requested host' do
              get :issues, id: redirect_route.path

              expect(response).to redirect_to(issues_group_path(group.to_param))
              expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
            end
          end

          context 'when the old group path is substring of groups' do
            # I.e. /groups/oups should not become /grfoo/oups
            let(:redirect_route) { group.redirect_routes.create(path: 'oups') }

            it 'does not modify the /groups part of the path' do
              get :issues, id: redirect_route.path

              expect(response).to redirect_to(issues_group_path(group.to_param))
              expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
            end
          end

          context 'when the old group path is substring of groups plus the new path' do
            # I.e. /groups/oups/oup should not become /grfoos
            let(:redirect_route) { group.redirect_routes.create(path: 'oups/oup') }

            it 'does not modify the /groups part of the path' do
              get :issues, id: redirect_route.path

              expect(response).to redirect_to(issues_group_path(group.to_param))
              expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
            end
          end
        end
641
      end
B
Bob Van Landuyt 已提交
642

643 644 645 646
      context 'for a POST request' do
        context 'when requesting the canonical path with different casing' do
          it 'does not 404' do
            post :update, id: group.to_param.upcase, group: { path: 'new_path' }
647

648 649
            expect(response).not_to have_http_status(404)
          end
650

651 652
          it 'does not redirect to the correct casing' do
            post :update, id: group.to_param.upcase, group: { path: 'new_path' }
653

654 655
            expect(response).not_to have_http_status(301)
          end
656 657
        end

658 659
        context 'when requesting a redirected path' do
          let(:redirect_route) { group.redirect_routes.create(path: 'old-path') }
660

661 662
          it 'returns not found' do
            post :update, id: redirect_route.path, group: { path: 'new_path' }
663

664 665
            expect(response).to have_http_status(404)
          end
666
        end
667
      end
668

669 670 671 672
      context 'for a DELETE request' do
        context 'when requesting the canonical path with different casing' do
          it 'does not 404' do
            delete :destroy, id: group.to_param.upcase
673

674 675
            expect(response).not_to have_http_status(404)
          end
676

677 678
          it 'does not redirect to the correct casing' do
            delete :destroy, id: group.to_param.upcase
679

680 681
            expect(response).not_to have_http_status(301)
          end
682 683
        end

684 685
        context 'when requesting a redirected path' do
          let(:redirect_route) { group.redirect_routes.create(path: 'old-path') }
686

687 688
          it 'returns not found' do
            delete :destroy, id: redirect_route.path
689

690 691
            expect(response).to have_http_status(404)
          end
692
        end
693 694 695
      end
    end

696 697 698
    def group_moved_message(redirect_route, group)
      "Group '#{redirect_route.path}' was moved to '#{group.full_path}'. Please update any links and bookmarks that may still have the old path."
    end
699
  end
700
end