groups_controller_spec.rb 24.3 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
      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
177
        let!(:first_page_subgroups) { create_list(:group, Kaminari.config.default_per_page, parent: group) }
178 179 180 181
        let!(:other_subgroup) { create(:group, :public, parent: group) }
        let!(:project) { create(:project, :public, namespace: group) }

        it 'contains all subgroups' do
182
          get :children, id: group.to_param, sort: 'id_asc', format: :json
183 184 185 186 187

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

        it 'contains the project and group on the second page' do
188
          get :children, id: group.to_param, sort: 'id_asc', page: 2, format: :json
189 190 191 192 193 194 195

          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 329 330 331
        it 'expands the tree upto a specified parent' do
          subgroup = create(:group, :public, parent: group)
          l2_subgroup = create(:group, :public, parent: subgroup)
          create(:project, :public, namespace: l2_subgroup, name: 'test')

          get :children, id: subgroup.to_param, filter: 'test', format: :json

          expect(response).to have_http_status(200)
        end

332 333 334 335 336 337 338
        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
339
      end
340

341
      context 'queries per rendered element', :request_store do
342 343
        # We need to make sure the following counts are preloaded
        # otherwise they will cause an extra query
B
Bob Van Landuyt 已提交
344 345
        # 1. Count of visible projects in the element
        # 2. Count of visible subgroups in the element
346 347
        # 3. Count of members of a group
        let(:expected_queries_per_group) { 0 }
348 349 350 351 352 353 354
        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
355
          control = ActiveRecord::QueryRecorder.new { get_list }
B
Bob Van Landuyt 已提交
356

357 358
          _new_group = create(:group, :public, parent: group)

359
          expect { get_list }.not_to exceed_query_limit(control).with_threshold(expected_queries_per_group)
360 361 362
        end

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

366
          expect { get_list }.not_to exceed_query_limit(control).with_threshold(expected_queries_per_project)
367 368 369
        end

        context 'when rendering hierarchies' do
370 371 372
          # 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 已提交
373

374 375 376 377
          def get_filtered_list
            get :children, id: group.to_param, filter: 'filter', format: :json
          end

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

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

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

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

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

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

393
            create(:group, :public, parent: public_subgroup, name: 'filterme2')
394
            create(:group, :public, parent: public_subgroup, name: 'filterme3')
395

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

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

402
            control = ActiveRecord::QueryRecorder.new { get_filtered_list }
403

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

406
            expect { get_filtered_list }.not_to exceed_query_limit(control).with_threshold(extra_queries_for_hierarchies)
407 408 409
          end
        end
      end
410 411 412
    end
  end

413 414 415 416 417
  describe 'GET #issues' do
    let(:issue_1) { create(:issue, project: project) }
    let(:issue_2) { create(:issue, project: project) }

    before do
418 419
      create_list(:award_emoji, 3, awardable: issue_2)
      create_list(:award_emoji, 2, awardable: issue_1)
420
      create_list(:award_emoji, 2, :downvote, awardable: issue_2)
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442

      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
443 444
      create_list(:award_emoji, 3, awardable: merge_request_2)
      create_list(:award_emoji, 2, awardable: merge_request_1)
Z
Z.J. van de Weg 已提交
445
      create_list(:award_emoji, 2, :downvote, awardable: merge_request_2)
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461

      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
462 463 464 465 466 467

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

468
        delete :destroy, id: group.to_param
469 470 471 472 473 474 475 476 477 478 479

        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
480
        Sidekiq::Testing.fake! do
481
          expect { delete :destroy, id: group.to_param }.to change(GroupDestroyWorker.jobs, :size).by(1)
482
        end
483 484 485
      end

      it 'redirects to the root path' do
486
        delete :destroy, id: group.to_param
487 488 489 490 491

        expect(response).to redirect_to(root_path)
      end
    end
  end
492 493 494 495 496 497

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

498
    it 'updates the path successfully' do
499 500 501 502 503 504 505 506 507 508
      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 已提交
509 510
      expect(assigns(:group).errors).not_to be_empty
      expect(assigns(:group).path).not_to eq('new_path')
511
    end
512 513 514 515 516 517 518 519 520 521 522 523 524
  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
525

526 527
        context 'when requesting the canonical path with different casing' do
          let(:group_full_path) { group.to_param.upcase }
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 553 554 555 556 557 558 559 560 561 562
          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
563 564
      end

565
      context 'when requesting groups under the /groups path' do
566 567 568 569 570 571 572 573 574
        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
575

576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602
            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
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 641 642 643 644 645 646 647 648 649 650
          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
651
      end
B
Bob Van Landuyt 已提交
652

653 654 655 656
      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' }
657

658 659
            expect(response).not_to have_http_status(404)
          end
660

661 662
          it 'does not redirect to the correct casing' do
            post :update, id: group.to_param.upcase, group: { path: 'new_path' }
663

664 665
            expect(response).not_to have_http_status(301)
          end
666 667
        end

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

671 672
          it 'returns not found' do
            post :update, id: redirect_route.path, group: { path: 'new_path' }
673

674 675
            expect(response).to have_http_status(404)
          end
676
        end
677
      end
678

679 680 681 682
      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
683

684 685
            expect(response).not_to have_http_status(404)
          end
686

687 688
          it 'does not redirect to the correct casing' do
            delete :destroy, id: group.to_param.upcase
689

690 691
            expect(response).not_to have_http_status(301)
          end
692 693
        end

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

697 698
          it 'returns not found' do
            delete :destroy, id: redirect_route.path
699

700 701
            expect(response).to have_http_status(404)
          end
702
        end
703 704 705
      end
    end

706 707 708
    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
709
  end
710
end